• Spring Security配置个过滤器也这么卷


    以前胖哥带大家用Spring Security过滤器实现了验证码认证,今天我们来改良一下验证码认证的配置方式,更符合Spring Security的设计风格,也更加内卷。

    CaptchaAuthenticationFilter是通过模仿UsernamePasswordAuthenticationFilter实现的。同样的道理,由于UsernamePasswordAuthenticationFilter的配置是由FormLoginConfigurer来完成的,应该也能模仿一下FormLoginConfigurer,写一个配置类CaptchaAuthenticationFilterConfigurer去配置CaptchaAuthenticationFilter

    public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
    		AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
     
        // 省略
    }    
    

    AbstractAuthenticationFilterConfigurer

    FormLoginConfigurer看起来有点复杂,不过继承关系并不复杂,只继承了AbstractAuthenticationFilterConfigurer

    public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter>
    		extends AbstractHttpConfigurer<T, B> {
    }    
    

    理论上我们模仿一下,也继承一下这个类,但是你会发现这种方式行不通。因为AbstractAuthenticationFilterConfigurer只能Spring Security内部使用,不建议自定义。原因在于它最终向HttpSecurity添加过滤器使用的是HttpSecurity.addFilter(Filter)方法,这个方法只有内置过滤器(参见FilterOrderRegistration)才能使用。了解了这个机制之后,我们只能往上再抽象一层,去改造其父类AbstractHttpConfigurer

    改造过程

    AbstractAuthenticationFilterConfigurer<B,T,F>中的B是实际指的HttpSecurity,因此这个要保留;

    T指的是它本身的实现,我们配置CaptchaAuthenticationFilter不需要下沉一层到FormLoginConfigurer这个继承级别,直接在AbstractAuthenticationFilterConfigurer这个继承级别实现即可,因此T这里指的就是需要配置类本身,也不需要再抽象化,因此是不需要的;同样的原因F也不需要,很明确是CaptchaAuthenticationFilter,不需要再泛化。这样CaptchaAuthenticationFilter的配置类结构可以这样定义:

    public class CaptchaAuthenticationFilterConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<CaptchaAuthenticationFilterConfigurer<H>, H> {
        // 不再泛化  具体化 
        private final CaptchaAuthenticationFilter authFilter;
        // 特定的验证码用户服务
        private CaptchaUserDetailsService captchaUserDetailsService;
        // 验证码处理服务
        private CaptchaService captchaService;
        // 保存认证请求细节的策略 
        private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
        // 默认使用保存请求认证成功处理器 
        private SavedRequestAwareAuthenticationSuccessHandler defaultSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        // 认证成功处理器
        private AuthenticationSuccessHandler successHandler = this.defaultSuccessHandler;
         // 登录认证端点
        private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;
        // 是否 自定义页面 
        private boolean customLoginPage;
        // 登录页面
        private String loginPage;
        // 登录成功url
        private String loginProcessingUrl;
        // 认证失败处理器
        private AuthenticationFailureHandler failureHandler;
        // 认证路径是否放开
        private boolean permitAll;
        //  认证失败的url
        private String failureUrl;
    
        /**
         * Creates a new instance with minimal defaults
         */
        public CaptchaAuthenticationFilterConfigurer() {
            setLoginPage("/login/captcha");
            this.authFilter = new CaptchaAuthenticationFilter();
        }
    
        public CaptchaAuthenticationFilterConfigurer<H> formLoginDisabled() {
            this.formLoginEnabled = false;
            return this;
        }
    
        public CaptchaAuthenticationFilterConfigurer<H> captchaUserDetailsService(CaptchaUserDetailsService captchaUserDetailsService) {
            this.captchaUserDetailsService = captchaUserDetailsService;
            return this;
        }
    
        public CaptchaAuthenticationFilterConfigurer<H> captchaService(CaptchaService captchaService) {
            this.captchaService = captchaService;
            return this;
        }
    
        public CaptchaAuthenticationFilterConfigurer<H> usernameParameter(String usernameParameter) {
            authFilter.setUsernameParameter(usernameParameter);
            return this;
        }
    
        public CaptchaAuthenticationFilterConfigurer<H> captchaParameter(String captchaParameter) {
            authFilter.setCaptchaParameter(captchaParameter);
            return this;
        }
    
        public CaptchaAuthenticationFilterConfigurer<H> parametersConverter(Converter<HttpServletRequest, CaptchaAuthenticationToken> converter) {
            authFilter.setConverter(converter);
            return this;
        }
        @Override
        public void init(H http) throws Exception {
            updateAuthenticationDefaults();
            updateAccessDefaults(http);
            registerDefaultAuthenticationEntryPoint(http);
            // 这里禁用默认页面过滤器 如果你想自定义登录页面 可以自行实现 可能和FormLogin冲突
            // initDefaultLoginFilter(http);
            // 把对应的Provider也在init时写入HttpSecurity
            initProvider(http);
        }
         @Override
        public void configure(H http) throws Exception {
            
            //这里改为使用前插过滤器方法
             http.addFilterBefore(filter, LogoutFilter.class);
        }
        
         // 其它方法 同AbstractAuthenticationFilterConfigurer
    }  
    

    其实就是模仿AbstractAuthenticationFilterConfigurer及其实现类的风格把用的配置项实现一边。这里值得一提的是CaptchaService的配置也可以从Spring IoC中查找(参考getBeanOrNull方法,这个方法在Spring Security中随处可见,建议借鉴),这样更加灵活,既能从方法配置也能自动注入。

        private void initProvider(H http) {
    
            ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
            // 没有配置CaptchaUserDetailsService就去Spring IoC获取
            if (captchaUserDetailsService == null) {
                captchaUserDetailsService = getBeanOrNull(applicationContext, CaptchaUserDetailsService.class);
            }
            // 没有配置CaptchaService就去Spring IoC获取
            if (captchaService == null) {
                captchaService = getBeanOrNull(applicationContext, CaptchaService.class);
            } 
            // 初始化 Provider
            CaptchaAuthenticationProvider captchaAuthenticationProvider = this.postProcess(new CaptchaAuthenticationProvider(captchaUserDetailsService, captchaService));
            // 会增加到ProviderManager的注册列表中
            http.authenticationProvider(captchaAuthenticationProvider);
        }
    

    配置类效果

    我们来看看CaptchaAuthenticationFilterConfigurer的配置效果:

        @Bean
        SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
    
    
            http.csrf().disable()
                    .authorizeRequests()
                    .mvcMatchers("/foo/**").access("hasAuthority('ROLE_USER')")
                    .anyRequest().authenticated()
                    .and()
                    // 所有的 AbstractHttpConfigurer 都可以通过apply方法加入HttpSecurity
                    .apply(new CaptchaAuthenticationFilterConfigurer<>())
                    // 配置验证码处理服务   这里直接true 方便测试
                    .captchaService((phone, rawCode) -> true)
                    // 通过手机号去拿验证码,这里为了方便直接写死了,实际phone和username做个映射  
                    .captchaUserDetailsService(phone -> userDetailsService.loadUserByUsername("felord"))
                    // 默认认证成功跳转到/路径  这里改造成把认证信息直接返回json
                    .successHandler((request, response, authentication) -> {
                    // 这里把认证信息以JSON形式返回
                        ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);
                        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
                               mappingJackson2HttpMessageConverter.write(authentication, MediaType.APPLICATION_JSON,servletServerHttpResponse);
                    });
    
            return http.build();
        }
    

    是不是要优雅很多,解决了你自己配置过滤器的很多疑难杂症。学习一定要模仿,先模仿成功,然后再分析思考为什么会模仿成功,最后形成自己的创造力。千万不要被一些陌生概念唬住,有些改造是不需要去深入了解细节的。

    关注公众号:Felordcn 获取更多资讯

    个人博客:https://felord.cn

  • 相关阅读:
    【云笔记篇】Microsoft OneNote笔记分区数据删除方法
    Godot 学习笔记(1):环境配置
    java计算机毕业设计点餐系统源代码+程序+lw文档+mysql数据库+远程部署
    PAT1021 个位数统计
    气相催化还原二氧化硫影响因素实验研究
    树状数组,线段树,容斥,P3801 红色的幻想乡
    【Java基础】方法重写、修饰符、权限修饰符及final、static关键字
    神经网络做什么比较合适,神经网络和计算机网络
    21天挑战赛算法学习打卡——顺序查找
    有关使用HttpServletRequest的Cookie的设置和获取
  • 原文地址:https://www.cnblogs.com/felordcn/p/15921243.html