说明
-
本文内容全部基于
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 }
热门文章
- 1月12日|Shadowrocket/V2ray/SSR/Clash每天更新22.4M/S免费节点订阅链接,付费节点订阅推荐
- 3月10日|Shadowrocket/V2ray/Clash/SSR每天更新21.5M/S免费节点订阅链接,付费节点订阅推荐
- Spring Boot 整合 Apache Dubbo
- PHP如何在同一文件中定义多个命名空间
- PowerBI开发:用自然语言来探索数据–Q&A
- 1月18日|Shadowrocket/V2ray/SSR/Clash每天更新22.6M/S免费节点订阅链接,付费节点订阅推荐
- 3月9日|Shadowrocket/V2ray/Clash/SSR每天更新22.2M/S免费节点订阅链接,付费节点订阅推荐
- 狗粮猫粮加工设备有哪些牌子(猫粮狗粮加工机器)
- 1月24日|V2ray/Shadowrocket/SSR/Clash每天更新20.1M/S免费节点订阅链接,付费节点订阅推荐
- 2月25日|Clash/Shadowrocket/SSR/V2ray每天更新21.8M/S免费节点订阅链接,付费节点订阅推荐