这篇文章需要结合这篇文章来看​​SpringBoot整合SpringSecurityOAuth2后产生的登录、授权、鉴权一系列问题​​来看,当完成前面的铺垫问题后,先确保httpBasic登录认证没问题!


配置摘要

至于UserDetailsService、TokenConfig、ResourceServerConfig 令牌配置、AuthorityServerConfig、Controller请自行到本文开头提到的那篇文章中查看

SpringSecurity

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_java


ResourceServerConfig

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_02


其中上面WebSecurityConfig这里重写configure方法可以不重写,因为这里是为了好做权限控制才写的,默认配置和这个差不多,只是没有关闭csrf,但是一定要@EnableWebSecurity和继承WebSecurityConfigurerAdapter,因为老版本的SpringSecurity自动开启,而高版本的需要手动开启

问题演示

1.启动服务器

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_03


2.请求授权码

​http://192.168.0.99:6100/oauth/authorize?response_type=code&client_id=c1&redirect_uri=http://live.lingdangji.com/live/pay/getCode.html&scope=ROLE_ADMIN​

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_java_04


跳转登录页正常!3.登录

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_java_05


这是登录成功后跳转/路径,这里我声明一下这里登陆是成功的,测试这个的代码我注释了,编写一个登录成功处理器打印一下日志即可!

那么问题已经演示出来了,本文也就是针对这个问题作出分析

问题解析

当产生这个问题的时候,没看源码之前其实已经找到解决方案了,但是并不知道个理所然!所以有点不爽,那就进行源码分析!

问题定位

首先确定的是登录授权这个流程是没问题的,就是登录成功后无法跳转获取授权码地址,而是直接跳转到/路径上,这里我就想起了SpringSecurity未授权请求登录访问时会将未授权的请求进行缓存,然后引导用户到登录页完成登录后,在从缓存中获取刚开始访问的为授权请求进行访问,那么就从这里开始下手,这里摘要一张流程图!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_缓存_06


这种图就是请求缓存的流程!注意其中的ExceptionTranslationFilter这个过滤器、RequestCacheAwareFilter过滤器,还有HttpSessionRequestCache(实际上这里应该是RequestCache)对象对应的码位我这里也提供一下,方便调试查出原因!

ExceptionTranslationFilter

这里就是对应的2到3这个流程,也就是将未授权请求缓存的步骤

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_缓存_07


SavedRequestAwareAuthenticationSuccessHandler

这里也就是对应6这个流程,也就是登录成功后从缓存中获取未登录前被拦截缓存的请求

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_java_08


两个流程也是我们懂这个问的的第一个砍,我们先启动调试一下,查看存储缓存数据的RequestCache对象情况!

查看RequestCache对象内存地址

debug模式重启服务器访问
​ http://192.168.0.99:6100/oauth/authorize?response_type=code&client_id=c1&redirect_uri=http://live.lingdangji.com/live/pay/getCode.html&scope=ROLE_ADMIN​​

断点停在ExceptionTranslationFilter的this.requestCache.saveRequest(request, response);这一行代码

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_09


记住他的地址,因为获取授权码的地址就是缓存在9328这个RequestCache中的!然后我们放行一下代码!让程序跳转到登录页!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_10


登录成功后断点停在SavedRequestAwareAuthenticationSuccessHandler类中的SavedRequest savedRequest = this.requestCache.getRequest(request, response);一行

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_11


这里取缓存的这是在9446的RequestCache,取出来的还是null,而且这里使用saveRequest的值做了判断,决定流程的走向

刚才这两个断点验证了存储未授权的缓存路径和取未授权的缓存路径的对象不是同一个,那么肯定是取不出来的,这里可能有人会有疑惑了,下面我来解开这个点的疑惑!我们修改一下资源服ResourceServerConfig的configure配置,让他不全部接管所有的资源,只是只接管部分路径!

@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/r/**").and()//限制资源服只接管/r/**相匹配的资源
.formLogin().permitAll().and()
.authorizeRequests()//授权的请求
.antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/r3").permitAll()
.antMatchers("/r/r4").denyAll()
.anyRequest()//任何请求
.authenticated()//需要身份认证
.and().csrf().disable();
}

重启服务器再次debug运行,断点不变
访问获取授权码地址

​http://192.168.0.99:6100/oauth/authorize?response_type=code&client_id=c1&redirect_uri=http://live.lingdangji.com/live/pay/getCode.html&scope=ROLE_ADMIN​

断点依然停留在同样的地方,查看一下RequestCache对象地址

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_缓存_12


放行让程序引导到登录页!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_13


登录成功后查看SavedRequestAwareAuthenticationSuccessHandler中的断点,检查RequestCache对象地址

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_14


这里存储的RequestCache对象地址和取的RequestCache地址是一样的,我们将程序放行,查看情况

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_15


成功跳转!那么这里就修改了一下资源服保护的地址,怎么就可以了呢!不要慌,接着看下面的分析!

产生两个RequestCache的原因

在将这个问题之前先来做个SpringSecurity的相关知识铺垫!

WebSecurityConfigurerAdapter与ResourceServerConfigurerAdapter
这两个很熟悉吧,一个是Springsecurity的Adapter一个是SpringSecurityOAuth2资源服的Adapter,但是真的了解二者的使用么,不一定,他们其实本质就是Adapter,只是二者所属的功能模块不同而已。而且二者的加载顺序是不一样的,ResourceServerConfigurerAdapter是优先于WebSecurityConfigurerAdapter(这个特点要记住后面会用到,当然了这个顺序也是可以修改的!这个默认加载顺序情况下先加载的Adapter在处理相同路径情况下会覆盖后续加载的路径,造成后加载的失效!),那么我们这里WebSecurityConfigurerAdapter和ResourceServerConfigurerAdapter都存在那么我们系统中就会有两个Adapter,我们每声明一个*Adapter类,都会产生一个filterChain。一个request(匹配url)只能被一个filterChain处理,这就解释了为什么二者Adapter同时在的时候处理相同请求路径时,后者默认为什么会失效的原因。我们可以在FilterChainProxy中的getFilters(HttpServletRequest request)方法中可以看到有哪些filter chain,并处理哪些url

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_16

验证WebSecurityConfigurerAdapterResourceServerConfigurerAdapter加载顺序问题

验证每个Adapter都会产生filterChain问题

这个验证起来比较简单,将WebSecurityConfig代码修改如下

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_17


也就是将SpringSecurity关闭!

debug重启服务器添加如下断点

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_18


访问获取授权码地址!

​http://192.168.0.99:6100/oauth/authorize?response_type=code&client_id=c1&redirect_uri=http://live.lingdangji.com/live/pay/getCode.html&scope=ROLE_ADMIN​

发送请求

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_19

这里发现存在两个filterChains,一个是SpringSecurityOAuth2默认自带处理token的Adapter默认处理三个路径,另一个则是我们自己配置的资源服Adapter处理我们配置的/r/++请求,我们在开启SpringSecurity的配置!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_java_20


重启debug

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_21


这里第一个是SpringSecurityOAuth2默认的,第二个则是我们自己配置的资源服,第三个就是我们的SpringSecurity的,那么这里也就可以验证每个Adapter都会产生filterChain问题,那么这里验证每个Adapter都会产生filterChain问题有什么用呢,当然有用,这里就要注意第二个资源服拦截的请求!

限制资源服只接管/r/++相匹配的资源和不限制/r/++产生的问题

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_22


SpringSecurity和SpringSecurityOAuth2资源服都开启不配置/r/++的情况分析

根据上图源码分析,这里由于SpringSecurity和SpringSecurityOAuth2都配置了,那么这里存在3个filterChain,当我们发送获取授权码请求时,第一个Adapter匹配的三个路径也就是/oauth/token、/oauth//token_key、/oauth/check_token是不匹配的,那么进入while判断时返回的为false,那么do循环继续执行,那么这里就会开始匹配资源服管控的资源路径,但是资源服有没有指定管控的资源路径,根据检验规则返回也是false

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_23


那么都循环继续!进入SpringSecurity的匹配

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_缓存_24


这里返回的是true,那么这里返回的chain也就是SpringSecurity那么返回的filter也就是SpringSecurity的filter,继续执行,断点放开,跳转登录界面!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_重启_25


开始登陆,断点进入下面

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_开发语言_26


这次的请求路径是/login,那么显然第一个Adapter不满足,返回false,那么继续进入第二个Adapter,也就是资源服的

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_缓存_27


这里既然匹配到了,原因就是资源服自身开启了表单登录,存在UsernamePasswordAuthenticationFilter过滤器,这个过滤器就会匹配到当前登录的路径!那么这里返回的是true下面的do循环将停止,直接返回资源服的过滤器!

这里就发现了未授权请求(获取授权码地址)缓存是在SpringSecurity中完成的,而登录成功后取的时候而是在资源服总取的,所以这也造成了本文开头的问题RequestCache对象不一致的问题,也就导致了登录成功后无法跳转到获取授权码地址的原因!那么这里也就能解释清楚了!为了更好的验证本文的推论,我们完成资源配置保护资源限制

SpringSecurity和SpringSecurityOAuth2资源服都开启并配置/r/++的情况分析

修改资源服配置!如下

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_28


重启debug运行!

这种情况下获取授权码请求引导用户到登录页这部分流程和上面的流程是一样的,主要就是跳转登录页后完成登录,登录成功的这个流程上有区别!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_29


开始登陆!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_缓存_30


第一个Adapter依然不满足!返回false,循环继续!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_31


第二个依然还是资源服的Adapter,但是这次资源服做了资源限制,只管控/r/++的匹配的请求,那么这里的/login是无法匹配的,那么返回false,这里就和上面资源服不加限制的区别参数了,循环继续!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_开发语言_32


这里就到了SpringSecurity的Adapter上,这里返回true,循环终止,那么这里将返回SpringSecurity的filter,然后我们将断点放开,查看跳转情况,再来补充结论!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_开发语言_33


成功回到登录前的请求(获取授权码)上!这里登录成功后使用的Adapter和拦截请求缓存起来的Adapter都是SpringSecurity所以RequestCache也就是同一个,那么登录成功后也能从RequestCache获取未登录前访问的获取授权码地址,然后完美跳转!

写在后文

文中有提到Adapter这里的顺序,是可以修改的,可以在配置类上添加Order(n)来调整加载顺序!

SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解_spring_34


这里关于顺序在文章开头提到的那篇文章中有提到过!