Spring Security 3.2.1具有不同WebSecurityConfigurerAdapters的多个登录表单

我正在使用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"); } } } 

重定向到登录页面的spring登录链的组件是身份validationfilter,使用http.formLogin()时插入的filter是DefaultLoginPageGeneratingFilter

如果未提供登录页面URL,则此filter将重定向到登录URL或构建默认基本登录页面。

您需要的是一个自定义身份validation筛选器,其逻辑用于定义所需的登录页面,然后将其插入spring安全链中以代替单页身份validation筛选器。

考虑通过TwoPageLoginAuthenticationFilter DefaultLoginPageGeneratingFilter并覆盖getLoginPageUrl()创建TwoPageLoginAuthenticationFilter ,如果这还不够,则复制代码并对其进行修改以满足您的需求。

此filter是GenericFilterBean ,因此您可以像这样声明它:

 @Bean public Filter twoPageLoginAuthenticationFilter() { return new TwoPageLoginAuthenticationFilter(); } 

然后尝试只构建一个http配置,不要设置formLogin() ,而是:

 http.addFilterBefore(twoPageLoginAuthenticationFilter, ConcurrentSessionFilter.class); 

这将把两个表单身份validationfilter插入链中的正确位置。

我为多个登录页面提供的解决方案涉及单个http身份validation,但我提供了自己的实现

  • AuthenticationEntryPoint
  • AuthenticationFailureHandler
  • LogoutSuccessHandler

我需要的是这些实现能够依赖于请求路径中的令牌进行切换。

在我的网站中,URL中包含客户令牌的页面受到保护,并要求用户在customer_signin页面上作为CUSTOMER进行身份validation。 因此,如果想要转到页面/客户/家,那么我需要重定向到customer_signin页面以进行首先进行身份validation。 如果我无法在customer_signin上进行身份validation,那么我应该返回带有错误参数的customer_signin。 这样就可以显示一条消息。
当我成功通过CUSTOMER认证然后希望注销时,LogoutSuccessHandler会将我带回customer_signin页面。

我对管理员需要在admin_signin页面进行身份validation以访问url中包含管理员令牌的页面有类似的要求。

首先,我定义了一个允许我获取令牌列表的类(每种类型的登录页面一个)

 public class PathTokens { private final List tokens = 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) { Set userRoles = 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"; } } 

也许这篇文章可以帮到你: 多个登录表单

它是弹簧安全性的不同版本,但同样的问题:只采取了第一种配置。

它似乎已经通过更改两个登录页面之一的login-processing-url来解决,但人们建议使用相同的URL处理但使用ViewResolver的不同布局。 如果您使用相同的机制对用户进行身份validation(validation机制是负责处理浏览器正在发送的凭据的事物),则它是一种解决方案。

这篇文章似乎也说如果你改变你的loginProcessingUrl你会成功: 配置Spring Security 3.x有多个入口点

我也遇到了这个问题,发现我错过了第一个过滤部分。

这个:

 http.csrf().disable() .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") 

应该:

 http.csrf().disable() .antMatcher("/admin/**") .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") 

添加第一个过滤.antMatcher(“/ admin / **”)将首先过滤它,以便它将使用AdminFormLoginWebSecurity而不是另一个。