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 获取更多资讯
Spring Security配置个过滤器也这么卷的更多相关文章
- spring boot 之 spring security 配置
Spring Security简介 之前项目都是用shiro,但是时过境迁,spring security变得越来越流行.spring security的前身是Acegi, acegi 我也玩过,那都 ...
- Spring Security 中的过滤器
本文基于 spring-security-core-5.1.1 和 tomcat-embed-core-9.0.12. Spring Security 的本质是一个过滤器链(filter chain) ...
- Spring Security(2):过滤器链(filter chain)的介绍
上一节中,主要讲了Spring Security认证和授权的核心组件及核心方法.但是,什么时候调用这些方法呢?答案就是Filter和AOP.Spring Security在我们进行用户认证以及授予权限 ...
- Spring Security(四) —— 核心过滤器源码分析
摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...
- Spring Security:Servlet 过滤器(三)
3)Servlet 过滤器 Spring Security 过滤器链是一个非常复杂且灵活的引擎.Spring Security 的 Servlet 支持基于 Servlet 过滤器,因此通常首先了解过 ...
- spring security 11种过滤器介绍
1.HttpSessionContextIntegrationFilter 位于过滤器顶端,第一个起作用的过滤器. 用途一,在执行其他过滤器之前,率先判断用户的session中是否已经存在一个Secu ...
- spring mvc 和spring security配置 web.xml设置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmln ...
- spring security 配置多个AuthenticationProvider
前言 发现很少关于spring security的文章,基本都是入门级的,配个UserServiceDetails或者配个路由控制就完事了,而且很多还是xml配置,国内通病...so,本文里的配置都是 ...
- Spring学习日志之Spring Security配置
依赖引入 <dependency> <groupId>org.springframework.security</groupId> <artifactId&g ...
随机推荐
- POJ 3617 Best Cow Line (字典序最小问题 & 贪心)
原题链接:http://poj.org/problem?id=3617 问题梗概:给定长度为 的字符串 , 要构造一个长度为 的字符串 .起初, 是一个空串,随后反复进行下列任意操作. 从 的头部删除 ...
- 二维数组与稀疏数组的转换---dataStructures
首先我们看一个需求 在11 * 11 的五子棋的棋盘中 我们使用0代表十字交叉点也是无效的数据 用1代表黑棋 用2代表蓝棋 那么所看到的棋盘如下 改用数字显示后就如一下样式 现在我们需要将怎个棋盘存储 ...
- Unity3D开发入门教程(四)——用Lua实现组件
五邑隐侠,本名关健昌,12年游戏生涯. 本教程以 Unity 3D + VS Code + C# + tolua 为例. 一.Lua组件基类 1.在 Assets/Lua 目录下新建com目录用于存放 ...
- RHCSA 第八天
1.查询ip的几种方式: ip, ifconfig, nmcli,nmtui 2.nmcli命令使用: a.在ens160网卡上新建连接static_con,并配置静态ip b.在ens160网卡上新 ...
- 《剑指offer》面试题09. 用两个栈实现队列
问题描述 用两个栈实现一个队列.队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能.(若队列中没有元素,dele ...
- win+ r 命令
Win 键+R calc:计算器 notepad:记事本 mspaint:画图 cmd:控制台 control:控制面板 desk.cpl:打开控制面板中的桌面设置 main.cpl:鼠标设置 ine ...
- 【机器学习】VAE
机器学习算法-VAE 目录 机器学习算法-VAE 1. VAE模型推导 1.1 算法引入 1.2 模型推导 1.3 损失函数 1.4 重参数技巧 2. 实现 2.1 模型定义 2.2 实验 1. VA ...
- node.js在Linux下执行shell命令、.sh脚本
首先,引入子进程模块 var process = require('child_process'); 执行shell命令 调用该模块暴露出来的方法exec process.exec('shutdown ...
- MyCms 自媒体 CMS 系统 v2.8,支持织梦数据导入
MyCms 是一款基于Laravel开发的开源免费的自媒体博客CMS系统,助力开发者知识技能变现. MyCms 基于Apache2.0开源协议发布,免费且不限制商业使用,欢迎持续关注我们. V2.8 ...
- golang中自定义一些类型和对应类型的指针方法
package main import "fmt" // 项目开发中可以为type声明的类型编写一些方法,从而实现对象.方法的操作 // 声明类型 type myInt int / ...