This is how Spring Security filters should be configured

CaptchaAuthenticationFilter this verification code filter is implemented by imitating UsernamePasswordAuthenticationFilter. Similarly, since the configuration of UsernamePasswordAuthenticationFilter is completed by FormLoginConfigurer, it should also be able to imitate FormLoginConfigurer and write a configuration class CaptchaAuthenticationFilterConfigurer to configure CaptchaAuthenticationFilter.

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

AbstractAuthenticationFilterConfigurer

FormLoginConfigurer looks a little complicated, but the inheritance relationship is not complicated. It only inherits AbstractAuthenticationFilterConfigurer.

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

In theory, let's imitate and inherit this class, but you'll find that this method doesn't work. Because AbstractAuthenticationFilterConfigurer can only be used internally in Spring Security, customization is not recommended. The reason is that it finally adds a filter to HttpSecurity using HttpSecurity Addfilter (filter) method, which can only be used with built-in filter (see FilterOrderRegistration). After understanding this mechanism, we can only abstract it one more level to transform its parent class abstracthttpconfigurator.

If you are learning Spring Boot, I recommend this free tutorial originally created by the program and serialized for many years (still updated): http://blog.didispace.com/spring-boot-learning-2x/ 3. Plagiarized, loved, but X version we will continue to go further! So, collect it quickly!

Transformation process

B in abstractauthenticationfilterconfigurer < B, t, f > actually refers to HttpSecurity, so this should be retained;

T refers to the implementation of CaptchaAuthenticationFilter itself. We don't need to sink a layer to the inheritance level of FormLoginConfigurer when configuring CaptchaAuthenticationFilter. We can directly implement it at the inheritance level of AbstractAuthenticationFilterConfigurer. Therefore, t here refers to the configuration of the class itself, and it doesn't need to be abstracted, so it's unnecessary; The same reason is not required for F. it is clear that CaptchaAuthenticationFilter does not need to be generalized. In this way, the configuration class structure of CaptchaAuthenticationFilter can be defined as follows:

public class CaptchaAuthenticationFilterConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<CaptchaAuthenticationFilterConfigurer<H>, H> {
    // No more generalization and concretization 
    private final CaptchaAuthenticationFilter authFilter;
    // Specific authentication code user service
    private CaptchaUserDetailsService captchaUserDetailsService;
    // Verification code processing service
    private CaptchaService captchaService;
    // Policy for saving authentication request details 
    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
    // By default, the save request authentication successful processor is used 
    private SavedRequestAwareAuthenticationSuccessHandler defaultSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    // Authentication successful processor
    private AuthenticationSuccessHandler successHandler = this.defaultSuccessHandler;
     // Login authentication endpoint
    private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;
    // Customize page 
    private boolean customLoginPage;
    // Login page
    private String loginPage;
    // Login success url
    private String loginProcessingUrl;
    // Authentication failure processor
    private AuthenticationFailureHandler failureHandler;
    // Whether the authentication path is released
    private boolean permitAll;
    //  Authentication failed 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);
        // The default page filter is disabled here. If you want to customize the login page, you can implement it yourself, which may conflict with FormLogin
        // initDefaultLoginFilter(http);
        // Write the corresponding Provider to HttpSecurity during init
        initProvider(http);
    }
     @Override
    public void configure(H http) throws Exception {
        
        //Here, the method of pre inserting filter is used instead
         http.addFilterBefore(filter, LogoutFilter.class);
    }
    
     // Other methods are the same as AbstractAuthenticationFilterConfigurer
}  

In fact, it is to imitate the style of AbstractAuthenticationFilterConfigurer and its implementation class to implement the configuration items used. It is worth mentioning here that the configuration of CaptchaService can also be found in Spring IoC (refer to the getBeanOrNull method, which can be seen everywhere in Spring Security. It is recommended to learn from it). This is more flexible and can be configured from the method and injected automatically.

    private void initProvider(H http) {

        ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
        // Go to Spring IoC to get CaptchaUserDetailsService without configuring it
        if (captchaUserDetailsService == null) {
            captchaUserDetailsService = getBeanOrNull(applicationContext, CaptchaUserDetailsService.class);
        }
        // Go to Spring IoC to get CaptchaService without configuring it
        if (captchaService == null) {
            captchaService = getBeanOrNull(applicationContext, CaptchaService.class);
        } 
        // Initialize Provider
        CaptchaAuthenticationProvider captchaAuthenticationProvider = this.postProcess(new CaptchaAuthenticationProvider(captchaUserDetailsService, captchaService));
        // Will be added to the registration list of ProviderManager
        http.authenticationProvider(captchaAuthenticationProvider);
    }

Configuration class effect

Let's take a look at the configuration effect of CaptchaAuthenticationFilterConfigurer:

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


        http.csrf().disable()
                .authorizeRequests()
                .mvcMatchers("/foo/**").access("hasAuthority('ROLE_USER')")
                .anyRequest().authenticated()
                .and()
                // All abstracthttpconfigurers can join HttpSecurity through the apply method
                .apply(new CaptchaAuthenticationFilterConfigurer<>())
                // Configure the verification code processing service, which is directly true here to facilitate testing
                .captchaService((phone, rawCode) -> true)
                // Get the verification code through the mobile phone number. In order to facilitate the direct writing, the actual phone and username are mapped  
                .captchaUserDetailsService(phone -> userDetailsService.loadUserByUsername("felord"))
                // The default authentication successfully jumps to the / path, where the authentication information is directly returned to json
                .successHandler((request, response, authentication) -> {
                // The authentication information is returned in JSON
                    ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);
                    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
                           mappingJackson2HttpMessageConverter.write(authentication, MediaType.APPLICATION_JSON,servletServerHttpResponse);
                });

        return http.build();
    }

Is it a lot more elegant, which solves many difficulties and complications of configuring filters yourself. Learning must imitate. First imitate success, then analyze and think about why it is successful, and finally form your own creativity. Don't be fooled by some strange concepts. Some transformations don't need to understand the details in depth.

Keywords: Java Back-end

Added by Rabioza123 on Fri, 18 Feb 2022 14:19:40 +0200