Spring cloud oauth token生成源码解析
spring cloud oauth 核心类解析
什么是oauth2.0协议是什么可以阅读 阮一峰老师的oauth2.0讲解
下面这样图就是生成token源码的核心类(Password模式)
整个流程的入口是从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包含了TokenRequest和ClientDetail信息
然后会将Authentication和 OAuth2Request中的信息汇总构建出一个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;
}