源码分析 Spring Security OAuth2 生成 token 的执行流程
说明
-
本文内容全部基于
Spring Security OAuth2(2.3.5.RELEASE). -
OAuth2.0有四种授权模式, 本文会以密码模式 来举例讲解源码. -
阅读前, 需要对
OAuth2.0的相关概念有所了解. -
最好有
Spring Security OAuth框架的使用经验
下面是前面写的OAuth2.0 相关文章
结合第三方登录案例理解 OAuth2.0 授权码方式
spring security oauth2 实战(仿微博第三方登录) - 工程搭建及登陆流程
正文
前置知识
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST) public ResponseEntity postAccessToken( Principal principal, @RequestParam Map parameters) throws HttpRequestMethodNotSupportedException { // TODO } - 获取 token 的默认请求路径是
/oauth/token - 获取 token 的入口类是
TokenEndpoint - 获取 token 的接口需要接收两个参数
Principal principalMapparameters
执行流程
第一步:
调用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 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 } 