乐趣区

关于java: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

退出移动版