前言
周末闲来无事,谢谢自己的项目,然后想把老的授权模式改造一下,老的是基于SpringSecurity的实现,想升级为SpringSecurity OAuth2模式,于是看了下之前搭建的SpringSecurity OAuth2的框架,代码嫁接进来基本上是没问题的,嫁接进来后满足SpringSecurity OAuth2默认提供的5种授权模式,然后外加一套自定义账号密码登录模式(原有的一套账号密码模式用作Admin管理后台系统登录,这一套自定义的用于客户端的账号密码登录)、验证码登录模式,看了之前写的文章SpringSecurityOAuth2授权流程源码分析,发现文章末尾有提到补充其他授权模式的文章,那么借此机会补充下!前提是按照SpringSecurityOauth2自定义授权模式这篇文章把自定义验证码模式搞定!
主流程框架
流程分析
这是整个请求,请求包括两部分,一部分是请求头中的Authorization和请求体中的参数,参数中grant_type尤为重要,关于grant_type参数可以看看往期文章SpringSecurityOauth2自定义授权模式
1.请求开始
当客户端发起请求后,请求会经过一系列过滤器,WebAsyncManagerIntegrationFilter、SecurityContextPersistenceFilter、HeaderWriterFilter、CsrfFilter(关闭后不经过)、LogoutFilter、BasicAuthenticationFilter、OAuth2ClientAuthenticationProcessingFilter、UsernamePasswordAuthenticationFilter、DefaultLoginPageGeneratingFilter。。。。等其他,其中BasicAuthenticationFilter
会过滤会执行一些逻辑,当然其他过滤器也会执行,直到最后ExceptionTranslationFilter中的决定器会根据投票器的结果判断请求能否到达Controller
2.BasicAuthenticationFilter在干嘛?
我们来到BasicAuthenticationFilter过滤器的doFilterInternal方法,这个方法中下面代码
Authentication authResult = this.authenticationManager.authenticate(authRequest);
这行代码会做一件有趣的事情,这是我在这次权限升级成SpringSecurity Oauth2的时候发现的
我将之前搭建的SpringSecurity OAuth2的代码嫁接过来后,在没有对代码更改的情况下,又将之前配套的表导入到数据库,但是其中有一张表出了问题
oauth_client_details
,就是这张表,之前的表名是oauth_client_details
,我在前面加了项目名作为前缀,发现授权流程请求失败了,提示没有权限,于是调试了下,发现既然真的是这个表名的问题,原因是请求在没到Controller之前被BasicAuthenticationFilter拦截下来了,然后执行上面那段代码后通过请求头中Authorization的Username和Password到oauth_client_details
这张表中查询端点信息去了,但是我这里表名发生变化了,所以抛出异常被捕获,导致请求没被放行。下面代码就是到数据库查询端点信息的
当把表名恢复过来后,整个流程就可以了!顺着这个有趣问题好好回味一下整个授权流程!
3.请求放行
当把表名恢复过来后,请求就被放行了,这时请求到达了/oauth/token这个Controller上面,看过上下文的朋友会发现这个/oauth/token请求和SpringSecurity OAuth2默认提供的账号密码授权模式是一样的,对就是一样的,不但这个自定义的短信验证码授权流程是一样的其他的包括SpringSecurity OAuth2的其他模式还有我另外一个客户端的自定义账号密码授权模式也都是同一个只是/oauth/token中调用的Provider和Granter不同,SpringSecurity OAuth2默认的账号密码模式的Provider是DaoAuthenticationProvider,Granter是ResourceOwnerPasswordTokenGranter,而自定义的短信验证码这是调用我们自己编写的SmsVerificationCodeAuthenticationProvider和SmsVerificationCodeTokenGranter
4./oauth/token
对应图中的TokenEndpoint
核心流程就是这上面几个框框!
if (!(principal instanceof Authentication))
校验下入参信息!
对应图中的ClientDetails和TokenRequest
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
这两行实际上就是构建端点信息,也就是根据请求头中Authorization的Username和Password去数据库中查询的,这里我觉得挺奇怪的,在上面的BasicAuthenticationFilter过滤器中查询过一次,这里还TM查询两次,有点不明白这种设计!
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
检验一下端点信息中的重点来了
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
其实关于这行代码已经在SpringSecurityOAuth2授权流程源码分析中讲过了,但是感觉并不是很详细,下面进行补充!
重中之重
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
getTokenGranter()
对应图中TokenGrande
getTokenGranter()得到okenGranter
这里得到了我们系统中配置的授权模式,总共7个,前面5个是SpringSecurity OAuth2默认提供的,下面两个是自定义的!
tokenRequest.getGrantType()
得到请求参数中的grant_type
.grant
最后在执行这个方法,如下,因为getTokenGranter()得到的TokenGranter,然后调用grant方法
上面有个重要的入口
return delegate.grant(grantType, tokenRequest);
delegate为CompositeTokenGranter中的方法
这里就是通过grantType循环在tokenGranters(配置好的授权模式)中找到匹配类型的Granter,核心代码如下
OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
此时匹配到的granter为SmsVerificationCodeTokenGranter,调用.grant(grantType, tokenRequest);方法,grant方法并不在SmsVerificationCodeTokenGranter中,而是在SmsVerificationCodeTokenGranter继承的AbstractTokenGranter中如下
进入grant方法后通过
return getAccessToken(client, tokenRequest);
调用
进入这个getAccessToken后调用了getOAuth2Authentication(client, tokenRequest)这个方法,然而getOAuth2Authentication方法在SmsVerificationCodeTokenGranter中重写了,所以执行逻辑来到了SmsVerificationCodeTokenGranter中的getOAuth2Authentication方法
其中这行代码则将SpringSecurity中的部分知识联系起来了。
userAuth = authenticationManager.authenticate(userAuth);
上面代码执行进入下面这部分代码
if (!provider.supports(toTest)) {
这里在循环系统中配置的Providers,然后通过每个Provider的supports进行匹配
那么这里SmsVerificationCodeAuthenticationProvider被选中!
得到provider为SmsVerificationCodeAuthenticationProvider!
result = provider.authenticate(authentication);
调用SmsVerificationCodeAuthenticationProvider中的authenticate方法
UserDetails user = clientUserDetailsService.loadUserByPhone(principal.get(SecurityConstants.PHONE_PARAMETER));
关于这行代码,我是真的不想说了,从SpringSecurity的第一篇文章到此,讲过无数次了,不知道的看往期文章!
SmsVerificationCodeAuthenticationToken smsVerificationCodeAuthenticationToken = new SmsVerificationCodeAuthenticationToken(user, user.getAuthorities());
smsVerificationCodeAuthenticationToken.setDetails(authenticationToken.getDetails());
return smsVerificationCodeAuthenticationToken;
封装用户信息,得到Authentication!
对应图中Authentication
一路返回到调用处SmsVerificationCodeTokenGranter中
Authentication中的用户信息信息已经得到了,然后在将端点信息和请求信息封装到OAuth2Request中
对应图中OAuth2Request
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
然后将OAuth2Request和Authentication封装到OAuth2Authentication中
new OAuth2Authentication(storedOAuth2Request, userAuth);
对应图中OAuth2Authentication
这里返回的是OAuth2Authentication对象,别忘了,这里一些列的操作都要回到AbstractTokenGranter的getAccessToken方法
执行tokenServices.createAccessToken得到OAuth2AccessToken,进入createAccessToken方法
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
这行代码时在Redis的缓存中检查Token,如果存在且没过期,那么直接返回原有的token重置一下过期时间。如果不存在那么往下
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
在进入createAccessToken方法,我们得到OAuth2AccessToken
对应图中OAuth2AccessToken
通过accessTokenEnhancer.enhance(token, authentication) : token;将授权信息编码,也就是转换成JWT
那么经过编码后得到token
tokenStore.storeAccessToken(accessToken, authentication);
执行上面代码将Token刷新到Redis中去!并将Token原路返回到TokenEndpoint中
然后我们代码就回到了TokenEndpoint中的/oauth/token请求
最后写回到Response中去,响应到客户端!
!
标签:请求,自定义,grant,SpringSecurity,token,源码,SpringSecurityOAuth2,tokenRequest From: https://blog.51cto.com/u_15899048/5903406