Spring Security OAuth2 源码分析2 - TokenGranter

TokenEndPoint 获取令牌过程中, 有个这样的步骤:

OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

TokenGranter, 字面上的理解: 令牌授予者。 以下是各授权模式对应的 TokenGranter:

实现类 对应的授权模式
AuthorizationCodeTokenGranter 授权码模式
ClientCredentialsTokenGranter 客户端模式
ImplicitTokenGranter implicit 模式
RefreshTokenGranter 刷新 token 模式
ResourceOwnerPasswordTokenGranter 密码模式

本文暂时只对 ResourceOwnerPasswordTokenGranter、RefreshTokenGranter 的源码作分析。

1. AbstractTokenGranter

以上实现类直接继承了抽象类 AbstractTokenGranter, 让我们通过源码来学习基本的 TokenGranter 授予令牌过程。

/**
 * @author Dave Syer
 */
public abstract class AbstractTokenGranter implements TokenGranter {
    // 核心部分代码...
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        // 判断传入的 grantType 是否符合当前的 TokenGranter。
        if (!this.grantType.equals(grantType)) {
            return null;
        }
        // 根据 TokenRequest 中的 clientId 加载 Client 信息
        String clientId = tokenRequest.getClientId();
        ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        // 判断 Client 信息中的 authorizedGrantTypes (已授权的 grantType) 是否包含着传入的 grantType
        validateGrantType(grantType, client);
        
        logger.debug("Getting access token for: " + clientId);
        // 通过 tokenService 创建 OAuth2AccessToken 并返回
        // note: TokenRequest 里有调接口时传进的 parameters (包含授权根据, 例如 username, password 等), 
        //       根据 client 信息 + tokenRequest 最终可得到 OAuth2AccessToken
        return getAccessToken(client, tokenRequest);
    }
    // ...
}

getAccessToken(client, tokenRequest) 这个过程可以理解为:

  1. 根据 client、tokenRequest 从 OAuth2RequestFactory 中创建一个 OAuth2Request, 进而可得到 OAuth2Authentication (存放着用户的认证信息)。
  2. 通过 tokenService 去创建 OAuth2AccessToken (存放着用户的 token信息、过期时间)。
    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
        return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
    }

    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, null);
    }

2. ResourceOwnerPasswordTokenGranter

以密码模式为例, 它是这样重写 getOAuth2Authentication 的:

/**
 * @author Dave Syer
 */
public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
    // ... 核心部分代码
    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        String username = parameters.get("username");
        String password = parameters.get("password");
        // 不将密码放入 details 中, 防止密码泄露
        parameters.remove("password");
        // 组装用户密码模式的认证信息
        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            // 通过 authenticationManager 认证已组装好的信息
            userAuth = authenticationManager.authenticate(userAuth);
        }
        catch (AccountStatusException ase) {
            // 过期、被锁定、不能用的时候抛出
            throw new InvalidGrantException(ase.getMessage());
        }
        catch (BadCredentialsException e) {
            // username/password 错误时抛出
            throw new InvalidGrantException(e.getMessage());
        }
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate user: " + username);
        }
        // 从工厂创建 OAuth2Request
        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);      
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }
    // ...
}

3. RefreshTokenGranter

我们拿到的 token 终会过期的, 对应于刷新 token模式的 RefreshTokenGranter 则负责获取新的 OAuth2AccessToken。

/**
 * @author Dave Syer
 */
public class RefreshTokenGranter extends AbstractTokenGranter {
    // 核心部分代码 ... 
    @Override
    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
        // 传入的参数需要有 refresh_token (DefaultOAuth2AccessToken 中有 refreshToken 字段)
        String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");

        // 调用 tokenService 的刷新方法得到新的 OAuth2AccessToken
        return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
    }
    // ...
}

4. CompositeTokenGranter

Spring security oauth2 还提供了一个 TokenGranter, 它可以是很多个 TokenGranter 的集合。

/**
 * @author Dave Syer
 */
public class CompositeTokenGranter implements TokenGranter {

    private final List<TokenGranter> tokenGranters;

    public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
        this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
    }
    
    /**
     * 从 TokenGranter 列表中挨个提出来获取准许证 (也就是我们要的 Token), 只要有个能获取到立即返回结果。
     */
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        for (TokenGranter granter : tokenGranters) {
            OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
            if (grant!=null) {
                return grant;
            }
        }
        return null;
    }
    
    /**
     * 添加 TokenGranter
     */
    public void addTokenGranter(TokenGranter tokenGranter) {
        if (tokenGranter == null) {
            throw new IllegalArgumentException("Token granter is null");
        }
        tokenGranters.add(tokenGranter);
    }
}

该系列文章:

Spring Security OAuth2 源码分析1 - TokenEndpoint
Spring Security OAuth2 源码分析2 - TokenGranter
Spring Security OAuth2 源码分析3 - TokenServices

持续更新中...