介绍:
跟踪调试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跟踪。
跟踪断点
debug模式发起请求后,自定义的/oauth/token断点拦住了请求。查看debug帧信息,可以看到请求参数
Principal principal存储信息就是Auth头中的客户端信息封装后的UsernamePasswordAuthenticationToken对象, 从源码关系可以看到该类也是Authentication接口定义的一个多态实现。
@RequestParam Map<String, String> parameters参数中封装的是密码模式的请求参数。
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;
}
遍历所有的实现类,找到匹配的granter,本次请求的grant_type为password,对应的granter为ResourceOwnerPasswordTokenGranter,当匹配到正确的granter时就是生成一个OAuth2AccessToken返回。