Spring cloud oauth token生成源码解析

817 阅读6分钟

Spring cloud oauth token生成源码解析

spring cloud oauth 核心类解析

什么是oauth2.0协议是什么可以阅读 阮一峰老师的oauth2.0讲解

下面这样图就是生成token源码的核心类(Password模式未命名文件.png

整个流程的入口是从TokenEndpoint开始直到最后返回access_token

  • 首先前端请求通过/oauth/token路径访问直接进入到TokenEndpoint中对应的端点上
    TokenEndpoint获取到请求参数client_id,
    通过ClientDetailsService接口根据client_id得到ClientDetail对象

  • 然后通过前端请求参数(scope,grant_type,client_id等)以及ClientDetail对象构建出TokenRequest对象

  • 然后通过TokenGranter令牌得授权者执行生成令牌的操作,TokenGranter会根据不同的grant_type执行不同的令牌生成逻辑

  • 不管是通过哪种方式去生成令牌它都会生成一个Authentication对象以及OAuth2Request对象, Authentication中包含了用户信息
    OAuth2Request包含了TokenRequestClientDetail信息
    然后会将AuthenticationOAuth2Request中的信息汇总构建出一个OAuth2Authentication对象

  • 最后将OAuth2Authentication对象传递给AuthorizationServerTokenServices的实现类(默认是: DefaultTokenServices)去生成令牌返回给前端

spring cloud oauth token生成流程

  • 获取前端请求参数clientId,通过ClientDetailsService接口查询到clientId对应的ClientDetails对象,
  • 通过ClientDetails以及前端请求参数构建TokenRequest对象
  • 验证ClientDetails信息
  • 根据grant_type调用不同的令牌授权者生成令牌信息
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

   if (!(principal instanceof Authentication)) {
      throw new InsufficientAuthenticationException(
            "There is no client authentication. Try adding an appropriate authentication filter.");
   }

   //获取请求头clientId参数
   String clientId = getClientId(principal);
   // 通过clientId查询客户端信息,返回ClientDetails对象
   ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

   //根据前端请求参数parameters以及ClientDetails构建TokenRequest对象
   TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

   //验证clientId
   if (clientId != null && !clientId.equals("")) {
      // Only validate the client details if a client authenticated during this
      // request.
      if (!clientId.equals(tokenRequest.getClientId())) {
         // double check to make sure that the client ID in the token request is the same as that in the
         // authenticated client
         throw new InvalidClientException("Given client ID does not match authenticated client");
      }
   }
   //验证Scope
   if (authenticatedClient != null) {
      oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
   }
   //验证grant_type
   if (!StringUtils.hasText(tokenRequest.getGrantType())) {
      throw new InvalidRequestException("Missing grant type");
   }
    //简单模式会直接生成token返回,根本不会走到这一步,所以当grant_type=implicit直接抛异常
   if (tokenRequest.getGrantType().equals("implicit")) {
      throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
   }
    //授权码模式在获取授权码的时候scope就定义好了,所以需要清洗scope的值
   if (isAuthCodeRequest(parameters)) {
      // The scope was requested or determined during the authorization step
      if (!tokenRequest.getScope().isEmpty()) {
         logger.debug("Clearing scope of incoming token request");
         tokenRequest.setScope(Collections.<String> emptySet());
      }
   }
   //刷新token模式下,scope需要重定义为scope
   if (isRefreshTokenRequest(parameters)) {
      // A refresh token has its own default scopes, so we should ignore any added by the factory here.
      tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
   }

   //调用令牌授权者生成令牌
   OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
   if (token == null) {
      throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
   }

   //以application/json方式返回令牌给前端
   return getResponse(token);

}

TokenGranter流程解析

  • 通过getTokenGranter().grant(tokenRequest.getGrantType()进入到CompositeTokenGranter
  • CompositeTokenGranter 有一个tokenGranters集合,集合中包含了四种授权模式以及一种刷新token的操作
  • 遍历tokenGranters集合,调用AbstractTokenGranter.grant()判断当前模式与grant_type是否匹配,不匹配返回null,匹配则再次验证客户端信息,最后调用getAccessToken()生成token
public class CompositeTokenGranter implements TokenGranter {

  //集合中包含4中授权模式一种刷新令牌操作
   private final List<TokenGranter> tokenGranters;

   public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
      this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
   }
   
   //遍历tokenGranters集合,调用AbstractTokenGranter.grant()
   // 判断当前模式与grant_type是否匹配,不匹配返回null,匹配则再次验证客户端信息,
   public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
      for (TokenGranter granter : tokenGranters) {
         OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
         if (grant!=null) {
            return grant;
         }
      }
      return null;
   }
   
   public void addTokenGranter(TokenGranter tokenGranter) {
      if (tokenGranter == null) {
         throw new IllegalArgumentException("Token granter is null");
      }
      tokenGranters.add(tokenGranter);
   }

}
//这是AbstractTokenGranter的grant()方法
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {

    //判断当前模式与grant_type是否匹配,不匹配返回null
   if (!this.grantType.equals(grantType)) {
      return null;
   }
   //匹配则再次验证客户端信息
   String clientId = tokenRequest.getClientId();
   ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
   validateGrantType(grantType, client);

   if (logger.isDebugEnabled()) {
      logger.debug("Getting access token for: " + clientId);
   }
   //生成token
   return getAccessToken(client, tokenRequest);

}

//调用AuthorizationServerTokenServices接口生成token
//getOAuth2Authentication(client, tokenRequest)根据匹配的模式生成OAuth2Authentication对象
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);
}

protected AuthorizationServerTokenServices getTokenServices() {
   return tokenServices;
}

protected OAuth2RequestFactory getRequestFactory() {
   return requestFactory;
}

这里以最常见的password模式解析一下,grant_type匹配后的操作

  • 拿到前端请求的账号密码构建UsernamePasswordAuthenticationToken
  • 通过 authenticationManager.authenticate获取用户信息,authenticationManager.authenticate是最终是调用UserDetailService中loadUserByUsername()方法查询到用户信息
  • 根据ClientDetails和tokenRequest信息构建OAuth2Request对象
  • 最后将Authentication和OAuth2Request信息汇总构建成OAuth2Authentication对象,,这个时候OAuth2Authentication就包含了请求参数,客户端信息,用户信息等所有信息
@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");
   // Protect from downstream leaks of password
   parameters.remove("password");

  //构建UsernamePasswordAuthenticationToken
   Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
   ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
   try {
   
      //获取用户信息,最终会调用到UserDetailService中loadUserByUsername()方法
      userAuth = authenticationManager.authenticate(userAuth);
   }
   catch (AccountStatusException ase) {
      //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
      throw new InvalidGrantException(ase.getMessage());
   }
   catch (BadCredentialsException e) {
      // If the username/password are wrong the spec says we should send 400/invalid grant
      throw new InvalidGrantException(e.getMessage());
   }
   if (userAuth == null || !userAuth.isAuthenticated()) {
      throw new InvalidGrantException("Could not authenticate user: " + username);
   }
   
   //根据ClientDetails和tokenRequest信息构建OAuth2Request对象
   OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);    
   //最后将Authentication和OAuth2Request信息汇总构建成OAuth2Authentication对象
   //OAuth2Authentication这个时候就包含了请求参数,客户端信息,用户信息
   return new OAuth2Authentication(storedOAuth2Request, userAuth);
}

最后解析一下tokenServices.createAccessToken方法流程

  • AuthorizationServerTokenServices接口,默认的实现类是DefaultTokenServices,
  • 根据authentication去tokenStore中查询token,判断token是否存在
    存在的情况下说明用户前面已经申请过token,这个时候需要去验证token是否未过期,过期则需要删除掉token,然后创建新的token并返回
    不存在的情况下说明用户是第一次申请token则直接创建新token返回
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

   //根据authentication去tokenStore中查询token
   OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
   OAuth2RefreshToken refreshToken = null;
   
   //判断token是否存在
   if (existingAccessToken != null) {
      //token是否过期,过期则删除
      if (existingAccessToken.isExpired()) {
       //刷新token是否过期,过期则删除
         if (existingAccessToken.getRefreshToken() != null) {
            refreshToken = existingAccessToken.getRefreshToken();
            // The token store could remove the refresh token when the
            // access token is removed, but we want to
            // be sure...
            tokenStore.removeRefreshToken(refreshToken);
         }
         tokenStore.removeAccessToken(existingAccessToken);
      }
      //不存在则创建token直接返回
      else {
         // Re-store the access token in case the authentication has changed
         tokenStore.storeAccessToken(existingAccessToken, authentication);
         return existingAccessToken;
      }
   }

   // Only create a new refresh token if there wasn't an existing one
   // associated with an expired access token.
   // Clients might be holding existing refresh tokens, so we re-use it in
   // the case that the old access token
   // expired.

   if (refreshToken == null) {
      refreshToken = createRefreshToken(authentication);
   }
   // But the refresh token itself might need to be re-issued if it has
   // expired.
   else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
      ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
      if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
         refreshToken = createRefreshToken(authentication);
      }
   }
   //token的存在的情况下,还是要重新创建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的时候,它最后会判断是否存在TokenEnhancer(token增强),存在的情况下,会去执行token增强
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
  DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
  int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
  if (validitySeconds > 0) {
     token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
  }
  token.setRefreshToken(refreshToken);
  token.setScope(authentication.getOAuth2Request().getScope());

  return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}