今天的任务是追踪spring.security.oauth2的/oauth/token接口
一、springsecurity和cloud组合使用时,通过gateway转发路由到/oauth/token来实现登录,接下来是源码追踪
oauth2的内置接口都在org.springframework.security.oauth2.provider.endpoint包中,这些接口包含登录,授权等,其中有个TokenEndPoint类就是我们的登录入口类了
二、@FrameworkEndpoint
进入到这个类中,我们可以发现注解@FrameworkEndpoint,这个注解的作用类似于@Controller, 专门用于内部端点。
三、Principal参数的注入
java.security.Principal是security的一个基础类,他代表的是一个实体,常见的实现类有UsernamePasswordAuthenticationToken
This interface represents the abstract notion of a principal, which can be used to represent any entity, such as an individual, a corporation, and a login id.
我们可以很容易猜想到这里肯定是通过反射的方式注入的这个Principal,其注入过程肯定和HttpServletRequest相似,通过断点追踪我们可以验证
从servlet请求的入口方法org.springframework.web.servlet.FrameworkServlet#doPost- doService - doDispatch - handle - handleInternal - invokeHandlerMethod - invokeAndHandle - invokeForRequest - getMethodArgumentValues - resolveArgumenti一直到org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver#resolveArgument(java.lang.Class<?>, javax.servlet.http.HttpServletRequest),验证了我们的猜想
四、入口方法的主要步骤
@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.");
}
// 根据发起请求的客户端ID获取客户端信息
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
// 构建Token请求对象
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
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");
}
}
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)) {
// 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());
}
}
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)));
}
// 重点方法:根据grantType校验后保存并返回token
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
五、tokenServices接口,在每次创建了accessToken或者refreshToken之后都会保存到tokenStore中,TokenStore是用来保存token的,每次创建都会先从其中获取看是否还存在,主要有三种实现方式,InMemoryTokenStore(内存),RedisTokenStore(redis),JdbcTokenStore(数据库)
/oauth/login就到这里了