老的项目基于spring-boot-1.5.x、spring-security-oauth2-2.x实现的sso,在近期的安全漏洞扫描中发现如下2个漏洞:
漏洞
涉及jar
修复方案
Spring Framework JDK >= 9 远程代码执行漏洞(CVE-2022-22965)
spring-core-4.3.12.RELEASE.jar
升级官方安全版本 >= 5.3.18/5.2.20
Spring Security RegexRequestMatcher 认证绕过漏洞(CVE-2022-22978)
spring-security-core-4.2.3.RELEASE.jar
5.5.x 版本使用者建议升级至5.5.7及其以上;
5.6.x 版本使用者建议升级至5.6.4及其以上
然后笔者的同事将spring-boot升级到了2.5.14;将spring-security-oauth2升级到了2.1.0、RELEASE,这样一来,spring-core的版本被间接的升级到了5.3.20,spring-security-core版本变成了5.5.8(主要是spring-boot决定的)。
因为spring-boot-1.x和2.x之间的差距,同步的对项目依赖和代码做了微调。这里不再列出。不是今天的重点。
项目终于能成功启动了,但是一个致命的问题出来了,登录成功后,不能正确的回跳到redirect_uri指定的地址。这里如果你对spring-security-oauth2实现登录认证不熟悉的,可以参考使用Spring Security搭建一套OAuth 2.0架构
先说一下升级前是正常的。
1、浏览器输入http://localhost:8091/sso/oauth/authorize?redirect_uri=http://www.baidu.com&client_id=auth&response_type=code&scope=all
然后回车
2、系统检测到当前浏览器没有登录,跳转到登录界面
3、输入用户名和密码,后点击登录,跳转到第一步redirect_uri指定的地址,并且地址栏带回code
F12看到刚才点击登录的时候,请求了http://localhost:8091/sso/api/v1/login
这一步是没有问题的,但是为啥UsernamePasswordAuthenticationFilter拦截器没有拦截,然后进行用户名和密码的校验呢?而是当成了一次普通没认证的请求,再次跳转http://localhost:8091/sso/index.html。
1、当第1步请求gethttp://localhost:8091/sso/oauth/authorize
的时候是需要认证的,所以跳转到loginPage对应urlhttp://localhost:8091/sso/api/v1/login
。这里sso/api/v1/login是笔者自定义的接口,接着又redirect到sso/index.html对应的登录界面。截止到这里都是没有问题的。
2、点用户点击登录按钮的时候,请求了posthttp://localhost:8091/sso/api/v1/login
进行密码校验。此时应该被UsernamePasswordAuthenticationFilter拦截到,然后执行下面的密码校验才对:
为什么没有拦截到呢?
我们从FilterChainProxy类开始看,因为该类回给每一个请求组装一个List
,然后执行依次执行这个FilterChain中的每个xxxFilter。
debug看一下filterChains究竟有哪些匹配规则:
首先,0、1、2、3的pattern都不能匹配到posthttp://localhost:8091/sso/api/v1/login
排除。
剩下4、5是我们重点关注对象,到底是谁匹配了/sso/api/v1/login这个前缀,然后里面filter还不包括UsernamePasswordAuthenticationFilter ?
继续跟进4的NotOauthRequestMatcher:
这里是个逆向思维,如果请求的前缀不在下面范围内,认为匹配改fliterChain
0 = “/oauth/confirm_access”
1 = “/oauth/check_token”
2 = “/oauth/error”
3 = “/oauth/token”
4 = “/oauth/authorize”
那问题明了了,我们的/sso/api/v1/login刚好不在这个范围内,所以就使用了下面这些filter
刚好没有真正需要的UsernamePasswordAuthenticationFilter。一顿操作发现还没认证,就重新跳转到登录界面了。
因为升级之后,spring-security-web中匹配规则发生了变化,导致没有点击登录按钮时请求的posthttp://localhost:8091/sso/api/v1/login
没有被UsernamePasswordAuthenticationFilter拦截到,登录失败了。
说明是升级导致的问题,那我们回退代码,跟进老版本FilterChainProxy中的的逻辑看看:
的确如此,老版本中,自定义的匹配4规则在NotOauthRequestMatcher 6之前,所以不存在截胡的现象。
既然问题是fliterChain的顺序导致的,那有没有可能调整FilterChainProxy#filterChains来解决呢?经常源码跟踪发现这里似乎没有给开发者留“口子”。
换个思路,既然是xxxx#NotOAuthRequestMatcher这个fliterChain截胡了,那能否想办法让这个RequestMatchar#match匹配失败呢?
修改paths的元素,将我们自定义的请求/sso/api/v1/login加入进去。
通过测试,问题得到解决。
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦