我正在使用Spring Security 3.2.1.RELEASE和Spring MVC 4.0.4.RELEASE
我正在尝试为具有两个不同登录条目页面的Web应用程序设置Spring Security.我需要将页面区分开来,因为它们的样式和访问方式不同.
首次登录页面适用于管理员用户并保护管理员页面/ admin/**
第二个登录页面适用于客户用户并保护客户页面/客户/**.
我试图设置两个WebSecurityConfigurerAdapter子类来配置各个HttpSecurity对象.
CustomerFormLoginWebSecurity正在保护客户页面,如果未经授权,则会重定向到客户登录页面.如果未经授权,AdminFormLoginWebSecurity正在保护管理页面重定向到管理员登录页面.
不幸的是,似乎只强制执行第一个配置.我认为我错过了一些额外的东西来使这两者都起作用.
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("customer").password("password").roles("CUSTOMER").and() .withUser("admin").password("password").roles("ADMIN"); } @Configuration @Order(1) public static class CustomerFormLoginWebSecurity extends WebSecurityConfigurerAdapter { @Override public void configure(WebSecurity web) throws Exception { web .ignoring() .antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**"); } protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/customer/**").hasRole("CUSTOMER") .and() .formLogin() .loginPage("/customer_signin") .failureUrl("/customer_signin?error=1") .defaultSuccessUrl("/customer/home") .loginProcessingUrl("/j_spring_security_check") .usernameParameter("j_username").passwordParameter("j_password") .and() .logout() .permitAll(); http.exceptionHandling().accessDeniedPage("/customer_signin"); } } @Configuration public static class AdminFormLoginWebSecurity extends WebSecurityConfigurerAdapter { @Override public void configure(WebSecurity web) throws Exception { web .ignoring() .antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .and() .formLogin() .loginPage("/admin_signin") .failureUrl("/admin_signin?error=1") .defaultSuccessUrl("/admin/home") .loginProcessingUrl("/j_spring_security_check") .usernameParameter("j_username").passwordParameter("j_password") .and() .logout() .permitAll(); http.exceptionHandling().accessDeniedPage("/admin_signin"); } } }
Angular Univ.. 7
重定向到登录页面春季登陆链的组成部分是认证过滤器,并得到了过滤器的使用时,插上电源http.formLogin()
是DefaultLoginPageGeneratingFilter
.
如果没有提供登录页面URL,则此过滤器重定向到登录URL或构建默认基本登录页面.
您需要的是一个自定义身份验证筛选器,其中包含定义所需登录页面的逻辑,然后将其插入spring安全链中以代替单页身份验证筛选器.
考虑创建一个TwoPageLoginAuthenticationFilter
子类DefaultLoginPageGeneratingFilter
和重写getLoginPageUrl()
,如果这还不够,那么复制代码并修改它以满足您的需求.
这个过滤器是a GenericFilterBean
,所以你可以像这样声明它:
@Bean public Filter twoPageLoginAuthenticationFilter() { return new TwoPageLoginAuthenticationFilter(); }
然后尝试只构建一个http配置并且不设置formLogin()
,而是执行:
http.addFilterBefore(twoPageLoginAuthenticationFilter, ConcurrentSessionFilter.class);
这将把两个表单身份验证过滤器插入链中的正确位置.
重定向到登录页面春季登陆链的组成部分是认证过滤器,并得到了过滤器的使用时,插上电源http.formLogin()
是DefaultLoginPageGeneratingFilter
.
如果没有提供登录页面URL,则此过滤器重定向到登录URL或构建默认基本登录页面.
您需要的是一个自定义身份验证筛选器,其中包含定义所需登录页面的逻辑,然后将其插入spring安全链中以代替单页身份验证筛选器.
考虑创建一个TwoPageLoginAuthenticationFilter
子类DefaultLoginPageGeneratingFilter
和重写getLoginPageUrl()
,如果这还不够,那么复制代码并修改它以满足您的需求.
这个过滤器是a GenericFilterBean
,所以你可以像这样声明它:
@Bean public Filter twoPageLoginAuthenticationFilter() { return new TwoPageLoginAuthenticationFilter(); }
然后尝试只构建一个http配置并且不设置formLogin()
,而是执行:
http.addFilterBefore(twoPageLoginAuthenticationFilter, ConcurrentSessionFilter.class);
这将把两个表单身份验证过滤器插入链中的正确位置.
我为多个登录页面提出的解决方案涉及单个http身份验证,但我提供了自己的实现
AuthenticationEntryPoint
AuthenticationFailureHandler
LogoutSuccessHandler
我需要的是这些实现能够依赖于请求路径中的令牌进行切换.
在我的网站中,URL中包含客户令牌的页面受到保护,并且需要用户在customer_signin页面上作为CUSTOMER进行身份验证.因此,如果想要转到页面/客户/主页,那么我需要重定向到customer_signin页面以进行首先进行身份验证.如果我无法在customer_signin上进行身份验证,那么我应该返回带有错误参数的customer_signin.这样就可以显示一条消息.
当我成功通过CUSTOMER认证然后希望注销时,LogoutSuccessHandler会将我带回customer_signin页面.
我对管理员需要在admin_signin页面进行身份验证以访问网址中包含管理员令牌的页面有类似的要求.
首先,我定义了一个允许我获取令牌列表的类(每种类型的登录页面一个)
public class PathTokens { private final Listtokens = new ArrayList<>(); public PathTokens(){}; public PathTokens(final List tokens) { this.tokens.addAll(tokens); } public boolean isTokenInPath(String path) { if (path != null) { for (String s : tokens) { if (path.contains(s)) { return true; } } } return false; } public String getTokenFromPath(String path) { if (path != null) { for (String s : tokens) { if (path.contains(s)) { return s; } } } return null; } public List getTokens() { return tokens; } }
然后我PathLoginAuthenticationEntryPoint
根据请求uri中的令牌使用它来更改登录URL.
@Component public class PathLoginAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { private final PathTokens tokens; @Autowired public PathLoginAuthenticationEntryPoint(PathTokens tokens) { // LoginUrlAuthenticationEntryPoint requires a default super("/"); this.tokens = tokens; } /** * @param request the request * @param response the response * @param exception the exception * @return the URL (cannot be null or empty; defaults to {@link #getLoginFormUrl()}) */ @Override protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) { return getLoginUrlFromPath(request); } private String getLoginUrlFromPath(HttpServletRequest request) { String requestUrl = request.getRequestURI(); if (tokens.isTokenInPath(requestUrl)) { return "/" + tokens.getTokenFromPath(requestUrl) + "_signin"; } throw new PathTokenNotFoundException("Token not found in request URL " + requestUrl + " when retrieving LoginUrl for login form"); } }
PathTokenNotFoundException扩展AuthenticationException,以便您可以通常的方式处理它.
public class PathTokenNotFoundException extends AuthenticationException { public PathTokenNotFoundException(String msg) { super(msg); } public PathTokenNotFoundException(String msg, Throwable t) { super(msg, t); } }
接下来,我提供了一个实现,AuthenticationFailureHandler
它查看请求标头中的referer url,以确定将用户定向到哪个登录错误页面.
@Component public class PathUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { private final PathTokens tokens; @Autowired public PathUrlAuthenticationFailureHandler(PathTokens tokens) { super(); this.tokens = tokens; } /** * Performs the redirect or forward to the {@code defaultFailureUrl associated with this path} if set, otherwise returns a 401 error code. * * If redirecting or forwarding, {@code saveException} will be called to cache the exception for use in * the target view. */ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { setDefaultFailureUrl(getFailureUrlFromPath(request)); super.onAuthenticationFailure(request, response, exception); } private String getFailureUrlFromPath(HttpServletRequest request) { String refererUrl = request.getHeader("Referer"); if (tokens.isTokenInPath(refererUrl)) { return "/" + tokens.getTokenFromPath(refererUrl) + "_signin?error=1"; } throw new PathTokenNotFoundException("Token not found in referer URL " + refererUrl + " when retrieving failureUrl for login form"); } }
接下来,我提供了一个实现LogoutSuccessHandler
,它将注销用户并将它们重定向到正确的登录页面,具体取决于请求标头中的参考URL中的令牌.
@Component public class PathUrlLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { private final PathTokens tokens; @Autowired public PathUrlLogoutSuccessHandler(PathTokens tokens) { super(); this.tokens = tokens; } @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { setDefaultTargetUrl(getTargetUrlFromPath(request)); setAlwaysUseDefaultTargetUrl(true); handle(request, response, authentication); } private String getTargetUrlFromPath(HttpServletRequest request) { String refererUrl = request.getHeader("Referer"); if (tokens.isTokenInPath(refererUrl)) { return "/" + tokens.getTokenFromPath(refererUrl) + "_signin"; } throw new PathTokenNotFoundException("Token not found in referer URL " + refererUrl + " when retrieving logoutUrl."); } }
最后一步是在安全配置中将它们连接在一起.
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired PathLoginAuthenticationEntryPoint loginEntryPoint; @Autowired PathUrlAuthenticationFailureHandler loginFailureHandler; @Autowired PathUrlLogoutSuccessHandler logoutSuccessHandler; @Bean public PathTokens pathTokens(){ return new PathTokens(Arrays.asList("customer", "admin")); } @Autowired public void registerGlobalAuthentication( AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("customer").password("password").roles("CUSTOMER").and() .withUser("admin").password("password").roles("ADMIN"); } @Override public void configure(WebSecurity web) throws Exception { web .ignoring() .antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/customer/**").hasRole("CUSTOMER") .and() .formLogin() .loginProcessingUrl("/j_spring_security_check") .usernameParameter("j_username").passwordParameter("j_password") .failureHandler(loginFailureHandler); http.logout().logoutSuccessHandler(logoutSuccessHandler); http.exceptionHandling().authenticationEntryPoint(loginEntryPoint); http.exceptionHandling().accessDeniedPage("/accessDenied"); } }
配置完成后,您需要一个控制器来指向实际的登录页面.下面的SigninControiller检查queryString是否有指示signin错误的值,然后设置用于控制错误消息的属性.
@Controller @SessionAttributes("userRoles") public class SigninController { @RequestMapping(value = "customer_signin", method = RequestMethod.GET) public String customerSignin(Model model, HttpServletRequest request) { SetuserRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("userRole", userRoles); if(request.getQueryString() != null){ model.addAttribute("error", "1"); } return "signin/customer_signin"; } @RequestMapping(value = "admin_signin", method = RequestMethod.GET) public String adminSignin(Model model, HttpServletRequest request) { Set userRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("userRole", userRoles); if(request.getQueryString() != null){ model.addAttribute("error", "1"); } return "signin/admin_signin"; } }