我针对多个登录页面使用的解决方案涉及单个http身份验证,但我提供了自己的实现
AuthenticationEntryPoint
AuthenticationFailureHandler
LogoutSuccessHandler
我需要这些实现能够根据请求路径中的令牌进行切换。
在我的网站中,URL中带有客户令牌的页面受到保护,并要求用户在customer_signin页面上以CUSTOMER身份进行身份验证。因此,如果要转到页面/
customer /
home,则需要先重定向到customer_signin页面以进行身份验证。如果我无法通过customer_signin进行身份验证,则应将我带错误参数返回到customer_signin。这样就可以显示一条消息。
当我成功通过身份验证为CUSTOMER并希望注销后,LogoutSuccessHandler应将我带回到customer_signin页面。
对于管理员需要在admin_signin页面进行身份验证以访问URL中带有管理令牌的页面,我也有类似的要求。
首先,我定义了一个类,使我可以获取令牌列表(每种登录页面类型一个)
public class PathTokens { private final List<String> tokens = new ArrayList<>(); public PathTokens(){}; public PathTokens(final List<String> 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<String> getTokens() { return tokens; }}然后,我
PathLoginAuthenticationEntryPoint根据请求uri中的令牌使用它来更改登录URL。
@Componentpublic class PathLoginAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { private final PathTokens tokens; @Autowired public PathLoginAuthenticationEntryPoint(PathTokens tokens) { // LoginUrlAuthenticationEntryPoint requires a default super("/"); this.tokens = tokens; } @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该方法查看请求标头中的引荐来源网址,以确定将用户定向到哪个登录错误页面。
@Componentpublic class PathUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { private final PathTokens tokens; @Autowired public PathUrlAuthenticationFailureHandler(PathTokens tokens) { super(); this.tokens = tokens; } @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该实现将注销用户并将其重定向到正确的登录页面,具体取决于请求标头中引用网址中的令牌。
@Componentpublic 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中是否有指示登录错误的值,然后设置用于控制错误消息的属性。
@Controller@SessionAttributes("userRoles")public class SigninController { @RequestMapping(value = "customer_signin", method = RequestMethod.GET) public String customerSignin(Model model, HttpServletRequest request) { Set<String> 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<String> userRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("userRole", userRoles); if(request.getQueryString() != null){ model.addAttribute("error", "1"); } return "signin/admin_signin"; }}


