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

2022年8月1日10:14:39

说明

  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 的执行流程

  • 作者:nimo10050
  • 原文链接:https://blog.csdn.net/cnm10050/article/details/107027245
    更新时间:2022年8月1日10:14:39 ,共 6197 字。