Spring Security 注册过滤器注意事项

前两天和小伙伴聊了 Spring Security+JWT 实现无状态登录,然后有小伙伴反馈了一个问题,感觉这是一个我们平时写代码容易忽略的问题,写一篇文章和小伙伴们聊一聊。

一 问题复原

先来说问题吧,在 Spring Security+JWT 登录中,整体上的思路就是用户登录成功之后返回 JWT 字符串,然后以后用户每次请求都携带上 JWT 字符串,服务端进行校验,校验通过之后,请求继续执行。

按照上面的思路,我们的项目中需要有一个 JwtFilter 用来从请求中提取请求传来的 Jwt 字符串进行校验,类似下面这样:

java 复制代码
@Component
public class JwtFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        String requestURI = req.getRequestURI();
        if ("/login".equals(requestURI)) {
            //登录请求,无需校验令牌,请求继续执行
            filterChain.doFilter(xxx,xxx);
            return;
        }
        //令牌校验
    }
}

然后有一个小伙伴反馈,在项目中使用了 WebSecurityCustomizer 给 Swagger 相关的请求都放行了,结果这些被放行的请求都被 JwtFilter 拦截了,这是咋回事呢?

首先小伙伴们要知道,使用 WebSecurityCustomizer 放行的请求,都不再经过 SecurityFilter 了,所以按理不该再被 JwtFilter 拦截了,因为 JwtFilter 是隶属于 SecurityFilter 这个过滤器链中的,并非原生的跟 Servlet 平级的那种 Filter。

但是为什么又拦截了呢?

松哥看了下代码,发现问题出在 @Component 这个注解上。

二 原理分析

在 Spring Boot 项目启动的时候,有一个环节就是把 Spring 容器中所有类型为 Filter 的 Bean 找出来,并且自动添加到容器的过滤器链条中(注意不是添加到 Spring Security 过滤器链中)。

这段代码的逻辑位于 ServletContextInitializerBeans#addAdaptableBeans 方法中,在该方法中,会调用 addAsRegistrationBean 方法完成以上事情:

java 复制代码
private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
		Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
	List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
	for (Entry<String, B> entry : entries) {
		String beanName = entry.getKey();
		B bean = entry.getValue();
		if (this.seen.add(bean)) {
			// One that we haven't already seen
			RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
			int order = getOrder(bean);
			registration.setOrder(order);
			this.initializers.add(type, registration);
		}
	}
}

可以看到,这里传入的参数 type 和 beanType 都是 Filter,从 Spring 容器中找到 Filter 类型的 Bean 存入到 initializers 集合中。不过注意,添加到集合中的实际上是封装之后的 registration 对象,这个对象通过 adapter.createRegistrationBean 方法创建出来,在该方法中,由于我们没有为当前过滤器设置拦截的请求地址,所以默认拦截所有请求,拦截规则是 /*

最后在 ServletWebServerApplicationContext#selfInitialize 方法中遍历上一步找到的过滤器,并逐个进行配置,相关代码如下:

DynamicRegistrationBean#register:

java 复制代码
@Override
protected final void register(String description, ServletContext servletContext) {
    //注册过滤器
	D registration = addRegistration(description, servletContext);
	//省略
}

AbstractFilterRegistrationBean#addRegistration:

java 复制代码
@Override
protected Dynamic addRegistration(String description, ServletContext servletContext) {
	Filter filter = getFilter();
	return servletContext.addFilter(getOrDeduceName(filter), filter);
}

可以看到,这最终就是大家熟知的添加过滤器的代码了。

三 解决方案

找到问题的原因,那么问题就好解决了。

问题的产生,主要是因为 Spring 自动查找容器中所有 Filter 类型的 Bean,并进行配置,那么我们的解决方案就是不要把这个 Bean 注册到 Spring 容器中,即不要添加 @Component 注解,而是直接自己 new 出来就行了,在配置过滤器链的时候,像下面这样配置即可:

java 复制代码
http.addFilterAfter(new JwtFilter(redisTemplate), SecurityContextHolderFilter.class);

经过上面这样配置之后,JwtFilter 就不存在于原生过滤器链中了,只是单纯的存在于 SecurityFilter 中。

理解了 Spring Security 原理,那么日常开发中各种奇奇怪怪的情况,我们就都能轻车熟路的解决了。

如果小伙伴们想要彻底掌握 Spring Security+OAuth2,那么可以看看松哥最近录制的这套全新的视频教程。

相关推荐
㳺三才人子2 小时前
初探 Flask
后端·python·flask·html
星栈独行2 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下2 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.2 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易3 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0073 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶3 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl4 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
身如柳絮随风扬4 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar
云烟成雨TD4 小时前
Spring AI Alibaba 1.x 系列【62】时光旅行(Time-Travel)
java·人工智能·spring