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.