七、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;
        }
    }
    

最后请求时在请求头带上平台即可

在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐