springSecurity及springOAuth2源码解析
目录
前言
在DEBUG分析springOAuth2的登录源码前,此处先列出springSecurity与OAuth2的定义。
springSecurity定义:
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
OAuth2.0定义:
OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。
以下是个人理解哈:
springSecurity是提供认证授权的安全框架,可能考虑的场景没有那么全。然后OAuth2.0对所有场景的认证流程做了个标准定义。之后spring对OAuth2.0协议进行了实现。
因为接触到的认证场景较少,所以很难理解OAuth2.0协议的精髓,先大概了解下吧。
此篇文章对springSecurity与springOAuth2框架实现的登录接口/oauth/token【POST】进行DEBUG源码分析。
一、/oauth/token接口代码路径
第一次接触还以为是公司自己写的登录接口呢0.0。后来才知道是spring-security-oauth2 jar包自带的接口。
类:
org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
接口方法:
@RequestMapping(value = {"/oauth/token"}, method = {RequestMethod.POST})
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException
二、源码分析
以下分析是在如下连接的demo基础上实现的,demo中实现了账号密码以及短信验证码登录方式,此处只分析短信验证码模式(其他模式原理一样)。
使用springSecurity及OAuth2实现多模式登录
1、接口调用
通过postman调用接口。
通过DEBUG查看接口入参:
2、加载配置信息
如下图,通过clientId获取clientId对应的配置信息,包括次clientId支持的认证方式、token有效期等信息。
3、创建TokenRequest
此处没啥说的,后面就new了一个对象。
4、认证并生成token
4.1、通过入参确定granter
继续回到/oauth/token接口方法,后面几行校验代码略过。
从DEBUG中可以看到this.getTokenGranter()为CompositeTokenGranter。
这块是demo中配置granter时初始化的选择,如下图:
下来咱们分析grant()方法。
代码很简单,通过循环去判断到底使用哪一个granter去处理。通过入参“grant_type=SMSVerification”,咱们直接就看看SMSVerificationTokenGranter的代码。会发现SMSVerificationTokenGranter 继承自AbstractTokenGranter,而且没有重写AbstractTokenGranter的grant(String grantType, TokenRequest tokenRequest)方法。
public class SMSVerificationTokenGranter extends AbstractTokenGranter
那么咱们就需要看看父类AbstractTokenGranter的grant(String grantType, TokenRequest tokenRequest)方法实现。如下图:
代码也很简单,判断属性grantType是否和入参一致,一致则准备认证并生成token。
当代码执行到方法的最后一行
return this.getAccessToken(client, tokenRequest);
时,这个时候要注意this指的是AbstractTokenGranter的子类SMSVerificationTokenGranter。
4.2、springSecurity认证
下图这段代码就是认证并生成token的真正开始。先看括号里面的代码执行,我们知道this指的是SMSVerificationTokenGranter。而且demo中的SMSVerificationTokenGranter类重写了方法
getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest)
下来就需要跳转到SMSVerificationTokenGranter类DEBUG了。
这里我们从入参中解析参数并创建SMSVerificationAuthenticationToken对象(这里咱们不讨论为什么要新建SMSVerificationAuthenticationToken这个类,文章最后会附上其他大佬对SpringSecurity框架的API分析文章地址)。
我们可以看到创建的SMSVerificationAuthenticationToken对象中的属性也就是附上了登录入参而已。
下来准备进行短信验证码的认证,从DEBUG中可以看到authenticationManager是WebSecurityConfigurerAdapter中的内部类AuthenticationManagerDelegator。
那么咱们就DEBUG跳过来看AuthenticationManagerDelegator的authenticate(Authentication authentication)方法。
this.delegate为null,于是通过this.delegateBuilder.getObject()给this.delegate重新赋值。
从DEBUG参数中可以看到为ProviderManager。
那么咱们又要跳到ProviderManager去看ProviderManager中的authenticate(Authentication authentication)方法。
这里ProviderManager管理一系列的provider实现。
这里的toTest就是我们的SMSVerificationAuthenticationToken的Class对象。
通过迭代器遍历所有的provider(有许多是springSecurity默认的)找到我们自己的实现SMSAuthenticationProvider,我们可以看看我们SMSAuthenticationProvider中的supports(Class<?> authentication)方法,如下图:
下来咱们就要看我们自己provider的认证方法了,毕竟咱们是自定义的认证方式。
这里可以看到入参为SMSVerificationAuthenticationToken对象,SMSVerificationAuthenticationToken对象中的属性principal为输入的用户名,属性credentials为输入的短信验证码。
这里我写死的短信验证码比较,实际业务中还是要从短信验证码的存储位置取出做比较的。
这行代码是获取用户信息的。
SMSAuthenticationProvider的初始化是在配置文件WhqSecurityConfig中处理的(demo中给短信验证码方式赋予了新的获取用户的方式),如下图:
咱们再来看SMSAuthenticationProvider认证方法authenticate(Authentication authentication)最后的代码。这里判断了用户的存在性(实际业务可能还需要做其他判断),最后又重新创建了SMSVerificationAuthenticationToken对象为后面生成token做准备,这里创建对象时构造函数中调用了父类的方法super.setAuthenticated(true),此参数是告诉框架认证通过的标记。如下图:
代码执行到这里说明我们的认证已经完成了。下来就是要准备生成token了。
4.3、Token生成
现在我们回到SMSVerificationTokenGranter类的getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest)方法。
当springSecurity认证完成后,返回Authentication对象,如下图:
认证通过后userAuth参数中principal属性为用户信息,authenticated属性表示认证通过。
方法最后创建OAuth2Authentication对象,属性storedRequest为OAuth2Request对象(后面生成token时从此参数获取相关配置信息,比如token的有效期),属性userAuthentication为springSecurity认证结果,其中包含用户详细信息。
接下来代码执行到AbstractTokenGranter,下图中的this.getOAuth2Authentication(client, tokenRequest)方法执行完成,准备生成Token执行this.tokenServices.createAccessToken。
DefaultTokenServices类的createAccessToken(OAuth2Authentication authentication)方法源码如下,53行会返回null,于是会执行到70行生成一个UUID的refreshToken。78行准备生成accessToken。
下来看方法createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken)的源码。
此方法前面只是创建DefaultOAuth2AccessToken对象并初始化部分参数信息。主要看return语句,从DEBUG信息中我们可以知道return的时候执行的是this.accessTokenEnhancer.enhance(token, authentication)方法。
从配置文件中我们可以知道此处需要执行token增强相关的方法。
最终会执行demo中JwtTokenEnhancer的enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication)方法。在此处我们可以将token信息存入redis缓存。这里就没啥可说的了。
到此处AbstractTokenGranter中的方法getAccessToken(ClientDetails client, TokenRequest tokenRequest)执行完成,token生成完成。
这个时候再回到我们的登录接口:/oauth/token
将生成的token信息返回给客户端即可。
5、登录完成
最后看下登录结果:
总结
以上只是通过DEBUG模式对springOAuth2提供的登录接口/oauth/token(POST)进行分析,大家看到最后可能有点乱。文中有不对的地方希望大家评论指正,感谢!!!
以下文章对springSecurity做了更全面且清晰的分析,包括主要API的介绍、相关的类图、认证流程等。大家看完之后再对自己实际开发的项目进行DEBUG分析应该会更有收获。
Spring Security系列教程11–Spring Security核心API讲解
Spring Security系列教程12–Spring Security认证授权流程
以上两篇文章的作者专门开了个专栏对springSecurity进行讲解。写的不错,值得一看。
SpringSecurity精讲系列