说明

  1. 本文内容全部基于Spring Security OAuth2(2.3.5.RELEASE).

  2. OAuth2.0 有四种授权模式, 本文会以密码模式 来举例讲解源码.

  3. 阅读前, 需要对OAuth2.0 的相关概念有所了解.

  4. 最好有Spring Security OAuth 框架的使用经验

下面是前面写的OAuth2.0 相关文章

结合第三方登录案例理解 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 }
  1. 获取 token 的默认请求路径是/oauth/token
  2. 获取 token 的入口类是TokenEndpoint
  3. 获取 token 的接口需要接收两个参数
    • Principal principal
    • Map<String, String> parameters

执行流程

第一步:

调用ClientDetailsService 类的loadClientByClientId 方法, 获取客户端信息装载到ClientDetails 对象中

  • ClientDetailsService 用来管理客户端信息
    • 实现1:InMemoryClientDetailsService (把客户端信息放在内存中)

    • 实现2:JdbcClientDetialsService (把客户端信息放在数据库中)

第二步:

调用OAuth2RequestFactory 类生成TokenRequest 对象

  • DefaultOAuth2RequestFactoryOAuth2RequestFactory 的唯一实现

  • TokenRequest 是对请求参数parametersClientDetails 属性的封装

第三步:

调用TokenGranter, 利用TokenRequest 产生两个对象OAuth2RequestAuthentication

TokenGranter 是对 4 种授权模式的一个封装。它会根据grant_type 参数去挑一个具体的实现来生成令牌

部分实现类如下:

* ResourceOwnerPasswordTokenGranter  * AuthorizationCodeTokenGranter * ImplicitTokenGranter * ClientCredentialsTokenGranter

第四步:

OAuth2RequestAuthorization 两个对象组合起来形成一个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()

在密码模式中的策略:

  1. 根据请求中携带的用户名和密码来获取当前用户的授权信息Authentication
  2. OAuth2RequestAuthentication 组合一个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 }

源码分析 Spring Security OAuth2 生成 token 的执行流程