In the past, We used Spring Security filter to implement captcha authentication. Today, we will improve the configuration of captcha authentication, which is more consistent with the design style of Spring Security, and more internal.

CaptchaAuthenticationFilter by imitating UsernamePasswordAuthenticationFilter. In the same way, as a result of UsernamePasswordAuthenticationFilter configuration is done by FormLoginConfigurer, should also can imitate the FormLoginConfigurer, Write a configuration class CaptchaAuthenticationFilter CaptchaAuthenticationFilterConfigurer to configuration.

public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractAuthenticationFilterConfigurer<H.FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
 
    / / to omit
}    
Copy the code

AbstractAuthenticationFilterConfigurer

Inheritance FormLoginConfigurer looks a bit complicated, but is not complicated, just inherited AbstractAuthenticationFilterConfigurer.

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

In theory, let’s emulate and inherit this class, but you’ll see that doesn’t work that way. Because AbstractAuthenticationFilterConfigurer only Spring Security for internal use, the custom is not recommended. The reason is that it finally adds a Filter to HttpSecurity using the httpSecurity.addFilter (Filter) method, which is only available with built-in filters (see FilterOrderRegistration). Once we understand this mechanism, we can only go up one level and modify its parent class AbstractHttpConfigurer.

Transformation process

AbstractAuthenticationFilterConfigurer < B, T, F > HttpSecurity the B is practical in, so that to keep;

T refers to itself, we don’t need to configure CaptchaAuthenticationFilter sinking a layer to FormLoginConfigurer level of this inheritance, Directly in the inheritance AbstractAuthenticationFilterConfigurer level, therefore T here means the need to configure the class itself, also no longer need to abstraction, so don’t need; For the same reason F don’t need, is CaptchaAuthenticationFilter is clear, no longer need to generalization. Such CaptchaAuthenticationFilter configuration class structure can be defined like this:

public class CaptchaAuthenticationFilterConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<CaptchaAuthenticationFilterConfigurer<H>, H> {
    // No more generalization concretization
    private final CaptchaAuthenticationFilter authFilter;
    // Specific captcha user service
    private CaptchaUserDetailsService captchaUserDetailsService;
    // Captcha processing service
    private CaptchaService captchaService;
    // The policy to save the authentication request details
    privateAuthenticationDetailsSource<HttpServletRequest, ? > authenticationDetailsSource;// The save request authentication success handler is used by default
    private SavedRequestAwareAuthenticationSuccessHandler defaultSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    // Authenticate the successful handler
    private AuthenticationSuccessHandler successHandler = this.defaultSuccessHandler;
     // Log in to the authentication endpoint
    private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;
    // Whether to customize the page
    private boolean customLoginPage;
    // Login page
    private String loginPage;
    // Successful login URL
    private String loginProcessingUrl;
    // Failed to authenticate the processor
    private AuthenticationFailureHandler failureHandler;
    // Whether to release the authentication path
    private boolean permitAll;
    // Failed authentication URL
    private String failureUrl;

    /** * Creates a new instance with minimal defaults */
    public CaptchaAuthenticationFilterConfigurer(a) {
        setLoginPage("/login/captcha");
        this.authFilter = new CaptchaAuthenticationFilter();
    }

    public CaptchaAuthenticationFilterConfigurer<H> formLoginDisabled(a) {
        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);
        // Disable the default page filter here. If you want to customize the login page, you can implement it yourself
        // initDefaultLoginFilter(http);
        // Write the corresponding Provider to HttpSecurity in init
        initProvider(http);
    }
     @Override
    public void configure(H http) throws Exception {
        
        // Use the forward-inserted filter method instead
         http.addFilterBefore(filter, LogoutFilter.class);
    }
    
     / / other methods with AbstractAuthenticationFilterConfigurer
}  
Copy the code

Is actually imitating AbstractAuthenticationFilterConfigurer and its implementation class style make use of configuration items. It is worth noting that the configuration of CaptchaService can also be found in Spring IoC (refer to the getBeanOrNull method, which is ubiquitous in Spring Security), which is more flexible and can be either configured from the method or injected automatically.

    private void initProvider(H http) {

        ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
        / / no configuration CaptchaUserDetailsService Spring IoC access
        if (captchaUserDetailsService == null) {
            captchaUserDetailsService = getBeanOrNull(applicationContext, CaptchaUserDetailsService.class);
        }
        // Go to Spring IoC without CaptchaService
        if (captchaService == null) {
            captchaService = getBeanOrNull(applicationContext, CaptchaService.class);
        } 
        // Initialize the Provider
        CaptchaAuthenticationProvider captchaAuthenticationProvider = this.postProcess(new CaptchaAuthenticationProvider(captchaUserDetailsService, captchaService));
        // will be added to the ProviderManager registration list
        http.authenticationProvider(captchaAuthenticationProvider);
    }
Copy the code

Configuration Class Effect

Let’s take a look at CaptchaAuthenticationFilterConfigurer configuration effect:

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {


        http.csrf().disable()
                .authorizeRequests()
                .mvcMatchers("/foo/**").access("hasAuthority('ROLE_USER')")
                .anyRequest().authenticated()
                .and()
                // All AbstractHttpConfigurer AbstractHttpConfigurer can be added to HttpSecurity using the Apply method
                .apply(new CaptchaAuthenticationFilterConfigurer<>())
                // Configure the captcha processing service
                .captchaService((phone, rawCode) -> true)
                // Get the verification code from the phone number
                .captchaUserDetailsService(phone -> userDetailsService.loadUserByUsername("felord"))
                // By default, the authentication information is returned directly to json
                .successHandler((request, response, authentication) -> {
                // The authentication information is returned as JSON
                    ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);
                    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
                           mappingJackson2HttpMessageConverter.write(authentication, MediaType.APPLICATION_JSON,servletServerHttpResponse);
                });

        return http.build();
    }
Copy the code

Is it a lot more elegant, to solve your own configuration of the filter a lot of difficult problems. Learning must imitate, first imitate success, and then analyze why to imitate success, and finally form their own creativity. Don’t be fooled by some unfamiliar concepts. Some modifications don’t require in-depth knowledge of the details.

Follow our public id: Felordcn for more information

Personal blog: https://felord.cn