源码分析

这一生的挚爱 提交于 2020-07-27 22:30:05

说明

  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` 是对请求参数 `parameters` 和 `ClientDetails` 属性的封装

第三步:

调用 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
}

在这里插入图片描述

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!