Spring Security 注册过滤器注意事项
前两天和小伙伴聊了 Spring Security+JWT 实现无状态登录,然后有小伙伴反馈了一个问题,感觉这是一个我们平时写代码容易忽略的问题,写一篇文章和小伙伴们聊一聊。
一 问题复原
先来说问题吧,在 Spring Security+JWT 登录中,整体上的思路就是用户登录成功之后返回 JWT 字符串,然后以后用户每次请求都携带上 JWT 字符串,服务端进行校验,校验通过之后,请求继续执行。
按照上面的思路,我们的项目中需要有一个 JwtFilter 用来从请求中提取请求传来的 Jwt 字符串进行校验,类似下面这样:
1 |
|
然后有一个小伙伴反馈,在项目中使用了 WebSecurityCustomizer 给 Swagger 相关的请求都放行了,结果这些被放行的请求都被 JwtFilter 拦截了,这是咋回事呢?
首先小伙伴们要知道,使用 WebSecurityCustomizer 放行的请求,都不再经过 SecurityFilter 了,所以按理不该再被 JwtFilter 拦截了,因为 JwtFilter 是隶属于 SecurityFilter 这个过滤器链中的,并非原生的跟 Servlet 平级的那种 Filter。
但是为什么又拦截了呢?
松哥看了下代码,发现问题出在 @Component 这个注解上。
二 原理分析
在 Spring Boot 项目启动的时候,有一个环节就是把 Spring 容器中所有类型为 Filter 的 Bean 找出来,并且自动添加到容器的过滤器链条中(注意不是添加到 Spring Security 过滤器链中)。
这段代码的逻辑位于 ServletContextInitializerBeans#addAdaptableBeans 方法中,在该方法中,会调用 addAsRegistrationBean 方法完成以上事情:
1 | private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type, |
可以看到,这里传入的参数 type 和 beanType 都是 Filter,从 Spring 容器中找到 Filter 类型的 Bean 存入到 initializers 集合中。不过注意,添加到集合中的实际上是封装之后的 registration 对象,这个对象通过 adapter.createRegistrationBean
方法创建出来,在该方法中,由于我们没有为当前过滤器设置拦截的请求地址,所以默认拦截所有请求,拦截规则是 /*
。
最后在 ServletWebServerApplicationContext#selfInitialize 方法中遍历上一步找到的过滤器,并逐个进行配置,相关代码如下:
DynamicRegistrationBean#register:
1 |
|
AbstractFilterRegistrationBean#addRegistration:
1 |
|
可以看到,这最终就是大家熟知的添加过滤器的代码了。
三 解决方案
找到问题的原因,那么问题就好解决了。
问题的产生,主要是因为 Spring 自动查找容器中所有 Filter 类型的 Bean,并进行配置,那么我们的解决方案就是不要把这个 Bean 注册到 Spring 容器中,即不要添加 @Component 注解,而是直接自己 new 出来就行了,在配置过滤器链的时候,像下面这样配置即可:
1 | http.addFilterAfter(new JwtFilter(redisTemplate), SecurityContextHolderFilter.class); |
经过上面这样配置之后,JwtFilter 就不存在于原生过滤器链中了,只是单纯的存在于 SecurityFilter 中。
理解了 Spring Security 原理,那么日常开发中各种奇奇怪怪的情况,我们就都能轻车熟路的解决了。
如果小伙伴们想要彻底掌握 Spring Security+OAuth2,那么可以看看松哥最近录制的这套全新的视频教程:
基本上把 Spring Security 的方方面面以及 OAuth2 都讲到了。最重要的是,贴合了很多小伙伴们日常常见的开发场景,比如短信验证码登录、JWT+Redis 登录、微信 OAuth2 登录等等都有讲到。
这套视频是基于目前最新版的 Spring Security6 录制的,Spring Security6 在 API 层面的变化还是蛮大的,引入了大量的 Lambda 表达式去简化配置,很多旧版的写法在 Spring Security6 中被废弃,并在将来在 Spring Security7 中会移除相关的 API,所以说最近的 Spring Security 更新还是蛮激进的。
另一方面就是这套视频包含了全新的基于目前最新版 Spring Security 录制的 OAuth2 教程,松哥在 2020 年的时候出过图文版的 OAuth2 教程,但是,现在新版的 OAuth2 也有很多变化,不仅仅是 API 层面的变化,授权模式也发生了一些变化,传统的密码模式、简化模式现在都不再推荐,转而引入了 PKCE 模式,并且利用 OIDC 简化用户信息获取。同时,在 2020 年还处于萌芽状态的 Spring Security OAuth2 Server 这个项目,目前也趋于成熟,也可以直接使用了,这些松哥都在视频中和大家做了详细介绍。在 OAuth2 环节我也和大家分享了如何使用微信的 OAuth2 登录。
总之这一套教程,让大家彻底理解系统的安全管理。
之前有小伙伴说我直接自己写过滤器,既灵活还简单。我并不反对这种做法,但是有一个前提就是你很牛,你自己写的过滤器有考虑到计时攻击,有考虑到 XSS 攻击,有考虑到点击劫持,有考虑跨站请求伪造。。。等等太多了。当然,这些问题如果你都没有考虑到,那么 Spring Security 都有帮你考虑到并提供解决方案!
理解了 Spring Security,再去看市面上其他的安全管理框架,都会豁然开朗。
不同于松哥之前其他教程的交付方式,这套教程使用微信群答疑并且不再使用一机一码绑定设备播放,小伙伴们可以在任意设备上登录自己的账号就可以学习了,很便捷。
下面是两个试看视频:
这套视频是付费的,¥499,有需要的小伙伴加微信备注 499。
最后我再稍微介绍下自己,虽然很多小伙伴已经了解我了:
- 技术畅销书作者,出版过两本技术图书,《Spring Boot+Vue全栈开发实战》(清华大学出版社,2019)和《深入浅出 Spring Security》(清华大学出版社,2021)。
- 华为云最具价值专家、华为云杰出 MVP、华为开发者社区之星。
- 腾讯技术创作营特邀评委、腾讯云金牌讲师。
- GitHub 26k star 项目作者。
- 多年一线 coding 经验,有丰富的项目经验和授课经验。
一个写过 Spring Security 相关图书的人来教你 Spring Security,绝对够专业。