Spring Security OAuth2.0密码模式请求达到/oauth/token后debug记录

394 阅读3分钟

介绍:

跟踪调试Spring Security OAuth2.0的密码模式获取token过程,了解记录获取token的大体流程,便于后续遇到问题时debug调试。这里调试使用的spring security版本5.7.6是较低的版本,截图中也会看到很多类被标记@Deprecated。另外本次调试的前提是搭建好了授权服务(这里不展开搭建过程),且本次调试请求没有经过网关,只有auth和admin服务。auth服务提供授权、admin服务提供查询数据库服务。本文跟踪调试的是请求到达/oauth/token方法后的过程

请求参数

接上一篇记录,继续调试密码模式获取token请求到达后端Controller的方法/oauth/token后,TokenEndpoint后续逻辑debug跟踪。

image.png

image.png

跟踪断点

debug模式发起请求后,自定义的/oauth/token断点拦住了请求。查看debug帧信息,可以看到请求参数 Principal principal存储信息就是Auth头中的客户端信息封装后的UsernamePasswordAuthenticationToken对象, 从源码关系可以看到该类也是Authentication接口定义的一个多态实现。

@RequestParam Map<String, String> parameters参数中封装的是密码模式的请求参数。

image.png

TokenEndpoint

F7跟进,可以发现TokenEndpoint就是一个mvc方法,也就是说即使不自定义可以请求/oauth/token获取token的,之所以自定义一个相同的接口是为了定制一些业务逻辑。大致的逻辑如下:

1、断言请求参数Principal必须为Authentication实例(如果不按规范要求,后续get参数都不知道get什么)

2、从Principal中获取client信息,并组合parameters参数一起构建TokenRequest对象。TokenRequest对象封装了客户端信息,请求授权参数信息,授权范围信息。

3、根据grant_type方式选择对应TokenGrantor生成OAuth2Access

4、将token封装成ResponseEntity对象后返回。

    @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(
      Principal principal, @RequestParam Map<String, String> parameters)
      throws HttpRequestMethodNotSupportedException {
    // 1、断言请求参数Principal必须为Authentication实例
   if (!(principal instanceof Authentication)) {
      throw new InsufficientAuthenticationException(
       "There is no client authentication. Try adding an appropriate authentication filter.");
   }

    // 2、从Principal中获取client信息,并组合parameters参数一起构建TokenRequest对象。
   String clientId = getClientId(principal);
   ClientDetails authenticatedClient =getClientDetailsService().loadClientByClientId(clientId);
   TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
    // 若params参数也写了client_id,则必须与Auth头里username保持一致,否则校验失败(无意义的校验) 
   if (StringUtils.hasText(clientId) && !clientId.equals(tokenRequest.getClientId())) {
      throw new InvalidClientException("Given client ID does not match authenticated client");
   }

   if (authenticatedClient != null) {
      oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
   }

   if (!StringUtils.hasText(tokenRequest.getGrantType())) {
      throw new InvalidRequestException("Missing grant type");
   }

   if (tokenRequest.getGrantType().equals("implicit")) {
      throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
   }

   if (isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
      logger.debug("Clearing scope of incoming token request");
      tokenRequest.setScope(Collections.<String>emptySet());
   } else if (isRefreshTokenRequest(parameters)) {
      if (StringUtils.isEmpty(parameters.get("refresh_token"))) {
         throw new InvalidRequestException("refresh_token parameter not provided");
      }

      tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
   }
   // 3、根据grant_type方式选择对应TokenGrantor生成token
   OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
   if (token == null) {
      throw new UnsupportedGrantTypeException("Unsupported grant type");
   }
   // 4、将token封装成OAuth2AccessToken对象后返回。
   return getResponse(token);
}
    

如下这行代码逻辑复杂,实现的逻辑是获取一个TokenGranter,返回实现类CompositeTokenGranter,CompositeTokenGranter又把请求委托给delegate去完成具体的grant授权。

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

// getTokenGranter返回的是一个接口对应的所有实现类
protected TokenGranter getTokenGranter() {
   return tokenGranter;
}
    

image.png

遍历所有的实现类,找到匹配的granter,本次请求的grant_type为password,对应的granter为ResourceOwnerPasswordTokenGranter,当匹配到正确的granter时就是生成一个OAuth2AccessToken返回。

image.png