七、SpringSecurity多端登录实现方案
此方案适用于多个平台、分多表登录使用一个权限认证的情况
1、自定义AbstractAuthenticationProcessingFilter(仿UsernamePasswordAuthenticationFilter)
-
public class MyAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true; public MyAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); } public MyAuthenticationFilter(String loginProcessingUrl) { super(new AntPathRequestMatcher(loginProcessingUrl, "POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // 此处从请求中获取所有的参数和请求头放入UsernamePasswordAuthenticationToken setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } @Nullable protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); } @Nullable protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { Map<String, Object> params = new HashMap<>(); Map<String, String[]> parameterMap = request.getParameterMap(); if (!CollectionUtils.isEmpty(parameterMap)) { params.putAll(parameterMap); } Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); params.put(key, value); } authRequest.setDetails(params); } public void setUsernameParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.usernameParameter = usernameParameter; } public void setPasswordParameter(String passwordParameter) { Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); this.passwordParameter = passwordParameter; } }
2、自定义AbstractUserDetailsAuthenticationProvider(仿DaoAuthenticationProvider,只重写了核心方法)
-
public class MyAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { private volatile String userNotFoundEncodedPassword; //这里是定制的UserUserDetailService, 分为Admin和Front private List<CustomUserDetailService> userDetailsServices; private PasswordEncoder passwordEncoder; public MyAuthenticationProvider() { this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()); } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { this.logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { String presentedPassword = authentication.getCredentials().toString(); if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { this.logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } } @Override protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { this.prepareTimingAttackProtection(); try { //在Filter中获取的请求中的所有参数,在此处拿出 Map detail = (Map) authentication.getDetails(); UserDetails loadedUser = null; //枚举所有自定义的userDetailsService for (CustomUserDetailService userDetailsService : userDetailsServices) { //在请求中获取平台参数 Object platform = detail.get("platform"); //如果不为null则与userDetailsService匹配,配对成功则使用该userDetailsService的loadUserByUsername if (Objects.nonNull(platform) && userDetailsService.supports(platform.toString())) { loadedUser = userDetailsService.loadUserByUsername(username); break; } } //如果为null,没有配对该平台的userDetailsService if (loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); } else { return loadedUser; } } catch (UsernameNotFoundException var4) { this.mitigateAgainstTimingAttack(authentication); throw var4; } catch (InternalAuthenticationServiceException var5) { throw var5; } catch (Exception var6) { throw new InternalAuthenticationServiceException(var6.getMessage(), var6); } } public void setPasswordEncoder(PasswordEncoder passwordEncoder) { Assert.notNull(passwordEncoder, "passwordEncoder cannot be null"); this.passwordEncoder = passwordEncoder; this.userNotFoundEncodedPassword = null; } private void prepareTimingAttackProtection() { if (this.userNotFoundEncodedPassword == null) { this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword"); } } private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) { if (authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword); } } public List<CustomUserDetailService> getUserDetailsServices() { return userDetailsServices; } public void setUserDetailsServices(List<CustomUserDetailService> userDetailsServices) { this.userDetailsServices = userDetailsServices; } }
3、定制一个继承UserDetailsService的适配器接口CustomUserDetailService, 提供多个自定义的UserDetailsService适配
-
public interface CustomUserDetailService extends UserDetailsService { //该方法需自定义的UserDetailsService实现,表示该UserDetailsService匹配什么平台 Boolean supports(String platform); }
4、自定义UserDetailsService,这里我定义了两个平台的UserDetailsService,举一反三即可,其中SecurityUser的继承了的对象,不了解自行学习之前的博客
-
//此UserDetailsService适用于后台管理 @Service public class AdminUserDetailService implements CustomUserDetailService { private final String PLAT_FORM = "admin"; @Override public Boolean supports(String platform) { return PLAT_FORM.equals(platform); } @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { SecurityUser securityUser = new SecurityUser(); securityUser.setNickName("admin"); securityUser.setUsername("admin"); securityUser.setPassword("123456"); System.out.println("there is admin"); return securityUser; } }
-
//此UserDetailsService适用于前台 @Service public class FrontUserDetailService implements CustomUserDetailService { private final String PLAT_FORM = "front"; @Override public Boolean supports(String platform) { return PLAT_FORM.equals(platform); } @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { SecurityUser securityUser = new SecurityUser(); securityUser.setNickName("front"); securityUser.setUsername("front"); securityUser.setPassword("123456"); System.out.println("there is front"); return securityUser; } }
5、最后配置WebSecurityConfigurerAdapter配置,此处为重点
-
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //自定义的登录成功时的处理器 @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; //自定义的登录失败时的处理器 @Autowired private AuthenticationFailureHandler authenticationFailureHandler; //自定义的无权限时的处理器 @Autowired private AuthenticationEntryPoint authenticationEntryPoint; //自定义的退出成功时的处理器 @Autowired private LogoutSuccessHandler logoutSuccessHandler; //这个为前置过滤器,不了解学习之前的博客 @Autowired private AuthFilter authFilter; //注入我们自定义的后台管理的UserDetailService @Autowired private CustomUserDetailService adminUserDetailService; //注入我们自定义的前台的UserDetailService @Autowired private CustomUserDetailService frontUserDetailService; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); // 基于token,所以不需要session http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); //所有请求都需要认证 http.authorizeRequests() .anyRequest() .authenticated(); //配置无权限时的处理器 http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); //配置退出登录成功的处理器 http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); //用自定义AbstractAuthenticationProcessingFilter覆盖UsernamePasswordAuthenticationFilter http.addFilterAt(authentication(), UsernamePasswordAuthenticationFilter.class); //配置前置过滤器 http.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //创建一个自定义的AbstractUserDetailsAuthenticationProvider对象 MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider(); //把我们自定义的userDetailService,放入这个对象 List<CustomUserDetailService> userDetailServices = new ArrayList<>(); userDetailServices.add(adminUserDetailService); userDetailServices.add(frontUserDetailService); myAuthenticationProvider.setUserDetailsServices(userDetailServices); myAuthenticationProvider.setPasswordEncoder(new PasswordEncoder() { @Override public String encode(CharSequence password) { return password.toString(); } @Override public boolean matches(CharSequence password, String encodedPassword) { return password.equals(encodedPassword); } }); //配置我们自定义的AbstractUserDetailsAuthenticationProvider auth.authenticationProvider(myAuthenticationProvider); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/api/**", "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**" ); } //注入我们自定义的AbstractAuthenticationProcessingFilter @Bean public MyAuthenticationFilter authentication() throws Exception { //此处生成一个自定义的AbstractAuthenticationProcessingFilter对象,并配置登录请求的路径 MyAuthenticationFilter myAuthenticationFilter = new MyAuthenticationFilter("/test/login"); myAuthenticationFilter.setAuthenticationManager(this.authenticationManager()); //登录成功时处理器 myAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler); //登录失败时的处理器 myAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler); return myAuthenticationFilter; } }