前言
上一篇我们是用了password模式来进行授权认证,获取token,接下来我们来看看其中关键的类是怎么获取token,分发token的。
ClientCredentialsTokenEndpointFilter
首先外部通过请求/oauth/token来获取token,当请求进来之前就会通过一个ClientCredentialsTokenEndpointFilter的过滤器,关键方如下:
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
//省略......
// 从请求中获取客户端id和客户端凭证
String clientId = request.getParameter("client_id");
String clientSecret = request.getParameter("client_secret");
//省略......
clientId = clientId.trim();
// 通过客户端Id和客户端凭证构建一个令牌
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
clientSecret);
// 把构建好的令牌对象交给AuthenticationManager去进行身份认证
return this.getAuthenticationManager().authenticate(authRequest);
}
从代码上可以很清晰的看到,首先从请求获取到client_id,client_secret,组装成UsernamePasswordAuthenticationToken作为身份标识,然后去获取到getAuthenticationManager交给它的实现类ProviderManager去进行身份认证。
顶级身份管理者AuthenticationManager
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//省略......
result = provider.authenticate(authentication);
//省略......
}
从debug可以发现关键代码就是这一行,身份认证交给provider去做,至于这个provider是什么呢,我们接着往下看。
AbstractUserDetailsAuthenticationProvider
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//省略......
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
//省略......
return createSuccessAuthentication(principalToReturn, authentication, user);
}
从字面意思可以猜测检索用户行为,因为上面和下面都是一些验证的方法,这里就直接省略了,我们继续往里面走,
DaoAuthenticationProvider
进入上诉的方法,就会由DaoAuthenticationProvider实现类去做
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
这里从上面可以看到去加载了我们重写的的实现类
加载完这些信息后,身份信息就已经得到了AuthenticationManager,接下来就要到TokenEndpoint。
TokenEndpoint
我们先来看看官方的解释,上面两个可以理解为前置校验和封装,这个类但从名字来看,就是和我们的授权有关,
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
//省略......
//
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
//省略......
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
//省略......
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type");
}
return getResponse(token);
}
-
首先是去加载了我们重写的客户端实现类,具体的可以参照我上篇文章提供的示例代码
-
然后进入到createTokenRequest可以发现核心代码是做了
TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);
简单来说就是通过请求参数,范围,授权模式来封装了一个tokenRequest对象
- 将token对象交给TokenGranter来进行颁发token
我们来看一下token的组成:
public interface OAuth2AccessToken {
public static String BEARER_TYPE = "Bearer";
public static String OAUTH2_TYPE = "OAuth2";
/**
* The access token issued by the authorization server. This value is REQUIRED.
*/
public static String ACCESS_TOKEN = "access_token";
/**
* The type of the token issued as described in <a
* href="https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-7.1">Section 7.1</a>. Value is case insensitive.
* This value is REQUIRED.
*/
public static String TOKEN_TYPE = "token_type";
/**
* The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will
* expire in one hour from the time the response was generated. This value is OPTIONAL.
*/
public static String EXPIRES_IN = "expires_in";
/**
* The refresh token which can be used to obtain new access tokens using the same authorization grant as described
* in <a href="https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-6">Section 6</a>. This value is OPTIONAL.
*/
public static String REFRESH_TOKEN = "refresh_token";
/**
* The scope of the access token as described by <a
* href="https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3">Section 3.3</a>
*/
public static String SCOPE = "scope";
//省略......
}
public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken {
private static final long serialVersionUID = 914967629530462926L;
private String value;
private Date expiration;
private String tokenType = BEARER_TYPE.toLowerCase();
private OAuth2RefreshToken refreshToken;
private Set<String> scope;
private Map<String, Object> additionalInformation = Collections.emptyMap();
//省略......
}
上面两个原石类就是我们请求/oauth/token的请求成功之后的对象,至于怎么颁发的呢,别急,继续往下走。
TokenGranter(掌握)
TokenGranter的设计思路是使用CompositeTokenGranter管理一个List列表,每一种grantType对应一个具体的真正授权者,在debug过程中可以发现CompositeTokenGranter 内部就是在循环调用五种TokenGranter实现类的grant方法,而granter内部则是通过grantType来区分是否是各自的授权类型。
public class CompositeTokenGranter implements TokenGranter {
private final List<TokenGranter> tokenGranters;
public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
}
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);
}
}
五种类型分别是:
ResourceOwnerPasswordTokenGranter ==> password密码模式
AuthorizationCodeTokenGranter ==> authorization_code授权码模式
ClientCredentialsTokenGranter ==> client_credentials客户端模式
ImplicitTokenGranter ==> implicit简化模式
RefreshTokenGranter ==>refresh_token 刷新token专用
我们回到AbstractTokenGranter的方法,
public abstract class AbstractTokenGranter implements TokenGranter {
//省略......
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
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);
}
return getAccessToken(client, tokenRequest);
}
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
//省略......
}
这上诉颁发的方法来看,我们可以看到,在颁发的时候首先加载了我们重写的客户端详情类clientDetailsService.loadClientByClientId(clientId);之后再通过getAccessToken里面的tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));来创建
我们debug往里面走可以发现,来到了DefaultService来进行创建,代码如下
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
// 检索根据提供的身份验证密钥存储的访问令牌(如果它存在的话)。
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
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);
}
else {
// 重新存储访问令牌,以防身份验证发生更改
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
//如果没有与过期访问令牌关联的现有令牌,则只创建新的刷新令牌。客户端可能持有现有的刷新令牌,所以我们在旧访问令牌过期的情况下重用它。
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
// 但是,如果刷新令牌已经过期,则可能需要重新发布它。
else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}
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,没有在进行createAccessToken(authentication, refreshToken);进行token的创建
进到createAccessToken()方法发现
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;
}
原来这个token是UUID生成的,天下大白!原来如此,refreshToken也是uuid生成的,这里不再过多逼逼
官方解释:
Base implementation for token services using random UUID values for the access token and refresh token values. The main extension point for customizations is the {@link TokenEnhancer} which will be called after the access and refresh tokens have been generated but before they are stored.
令牌服务的基本实现使用访问令牌和刷新令牌值的随机UUID值。自定义的主要扩展点是{@link TokenEnhancer},它将在生成访问和刷新令牌之后、但在它们被存储之前被调用。
TokenService
public interface AuthorizationServerTokenServices {
/**
* Create an access token associated with the specified credentials.
* 创建与指定凭证关联的访问令牌。
* @param authentication The credentials associated with the access token.
* @return The access token.
* @throws AuthenticationException If the credentials are inadequate.
*/
OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
/**
* Refresh an access token. The authorization request should be used for 2 things (at least): to validate that the
* client id of the original access token is the same as the one requesting the refresh, and to narrow the scopes
* (if provided).
*
* @param refreshToken The details about the refresh token.
* @param tokenRequest The incoming token request.
* @return The (new) access token.
* @throws AuthenticationException If the refresh token is invalid or expired.
*/
OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
throws AuthenticationException;
/**
* Retrieve an access token stored against the provided authentication key, if it exists.
*
* @param authentication the authentication key for the access token
*
* @return the access token or null if there was none
*/
OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
}
我们可以看到AuthorizationServerTokenServices,它集中了token的实现方法,比如创建token,刷新token,获取token等。
Finally
我们了解到了创建从请求/oauth/token到创建token的密码模式的整个流程,至于其他模式尽不相同,但功能都是一样的,只是会有不同的实现类去实现。
最后我是ikcross,附上一句座右铭,你所过的每一个平凡,可能都是一个个连续的奇迹