以前胖哥带大家用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