说明
-
本文内容全部基于
Spring Security OAuth2(2.3.5.RELEASE)
. -
OAuth2.0
有四种授权模式, 本文会以 密码模式 来举例讲解源码. -
阅读前, 需要对
OAuth2.0
的相关概念有所了解. -
最好有
Spring Security OAuth
框架的使用经验
下面是前面写的 OAuth2.0
相关文章
spring security oauth2 实战(仿微博第三方登录) - 工程搭建及登陆流程
正文
前置知识
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(
Principal principal,
@RequestParam Map<String, String> parameters)
throws HttpRequestMethodNotSupportedException {
// TODO
}
- 获取 token 的默认请求路径是
/oauth/token
- 获取 token 的入口类是
TokenEndpoint
- 获取 token 的接口需要接收两个参数
Principal principal
Map<String, String> parameters
执行流程
第一步:
调用 ClientDetailsService
类的 loadClientByClientId
方法, 获取客户端信息装载到 ClientDetails
对象中
ClientDetailsService
用来管理客户端信息-
实现1:
InMemoryClientDetailsService
(把客户端信息放在内存中) -
实现2:
JdbcClientDetialsService
(把客户端信息放在数据库中)
-
第二步:
调用 OAuth2RequestFactory
类生成 TokenRequest
对象
DefaultOAuth2RequestFactory
是OAuth2RequestFactory
的唯一实现
* `TokenRequest` 是对请求参数 `parameters` 和 `ClientDetails` 属性的封装
第三步:
调用 TokenGranter
, 利用 TokenRequest
产生两个对象 OAuth2Request
和 Authentication
TokenGranter
是对 4 种授权模式的一个封装。它会根据 grant_type
参数去挑一个具体的实现来生成令牌
部分实现类如下:
* ResourceOwnerPasswordTokenGranter
* AuthorizationCodeTokenGranter
* ImplicitTokenGranter
* ClientCredentialsTokenGranter
第四步:
将 OAuth2Request
和 Authorization
两个对象组合起来形成一个 OAuth2Authorization
对象,它的里面包含了:
- 哪个第三方应用在请求 token
- 哪个用户以哪种授权模式进行授权
第五步:
将第 4 步 的对象会传递到 AuthorizationServerTokenServices
的实现类DefaultTokenServices
中,最终会生成一个OAuth2AccessToken
。
源码分析
1. TokenEndpoint#postAccessToken()
// 从请求参数中解析出 clientId
String clientId = this.getClientId(principal);
// 第一步: 从 内存 or 数据库(根据 ClientDetailsService 的具体实现)中取出客户端的详细信息
ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
// 第二步: 调用 `OAuth2RequestFactory` 类生成 `TokenRequest` 对象
TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
// 省略一堆判断
// 第3-5步: 根据不同的授权方式, 生成 token
OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
} else {
return this.getResponse(token);
}
针对上述 第 3-5 步的源码接着分析:
OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
假设现在使用的是授权码模式-密码模式, 那么this.getTokenGranter()
返回的结果就是ResourceOwnerPasswordTokenGranter
.
对应的 grant()
方法调用的是 CompositeTokenGranter的 grant()方法
2. CompositeTokenGranter#grant()
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
for (TokenGranter granter : tokenGranters) {
OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
if (grant!=null) {
return grant;
}
}
return null;
}
CompositeTokenGranter
中有一个集合,这个集合里装的就是五个会产生令牌的操作。
在遍历过程中, 通过 grant_type
在五种情况中挑一种生成 accessToken
对象。
3. AbstractTokenGranter#grant
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
// 判断参数传来的的授权类型和该类所支持的授权类型是否一致
59到第63行是
if (!this.grantType.equals(grantType)) {
return null;
}
//获取客户端信息跟授权类型再做一个校验
String clientId = tokenRequest.getClientId();
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
validateGrantType(grantType, client);
return getAccessToken(client, tokenRequest);
}
4. AbstractTokenGranter #getAccessToken()
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
// 调用 ResourceOwnerPasswordTokenGrante的getOAuth2Authentication方法
return this.tokenServices.createAccessToken(this.getOAuth2Authentication(client, tokenRequest));
}
5. ResourceOwnerPasswordTokenGranter#getOAuth2Authentication()
在密码模式中的策略:
- 根据请求中携带的用户名和密码来获取当前用户的授权信息
Authentication
- 将
OAuth2Request
和Authentication
组合一个OAuth2Authentication
对象
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
// 从 TokenRequest 中获取请求参数
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
// 用户名和密码
String username = (String)parameters.get("username");
String password = (String)parameters.get("password");
parameters.remove("password");
// 构造一个 Authentication 对象
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken)userAuth).setDetails(parameters);
Authentication userAuth;
try {
// 把 userAuth 传递给authenticationManager做认证
// 其实就是调用 自定义的UserDetailService 的 loadUserByUsername 方法去校验用户
userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var9) {
throw new InvalidGrantException(var9.getMessage());
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
// 通过 OAuth2Request 构造一个 OAuth2Authentication 对象
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
}
6. DefaultTokenServices#createAccessToken
// 从 tokenStore 取出 token
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
// 如果 token 过期
if (existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
// 移除 refresh token
refreshToken = existingAccessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
// // 移除 token
tokenStore.removeAccessToken(existingAccessToken);
} else {
// 如果token 没过期, 就刷新有效期, 返回 token
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
} else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}
// 创建新的 token, 并返回
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
// In case it was modified
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
总结
密码模式获取 token
的流程就是把请求的参数 比如 clientId, secret, grant_type, username, password
等信息, 通过/oauth/token
接口传到后端, 经过下图中的一系列转换得到一个 OAuth2AccessToken
对象
最终获得如下 json
串
{
"scope": "[all, read, write]",
"code": 0,
"access_token": "71561b3d-73d5-4a91-bf0f-456c9dc84d7d",
"token_type": "bearer",
"refresh_token": "b888d6d7-5ec2-47f9-82fe-eca5a0350770",
"expires_in": 7199
}
来源:oschina
链接:https://my.oschina.net/nimo10050/blog/4329219