2. Oauth2认证流程源码分析
2.1客户端认证
从浏览器中输入网址之后,首先会通过Spring的相关过滤器进行拦截,filterchains中的过滤器一层一层执行doFilter,
到ClientCredentialsTokenEndPointFilter,首先会调用其父类AbstractAuthenticationProcessingFilter的doFilter方法。
然后就会进入子类ClientCredentialsTokenEndPointFilter的attemptAuthentication方法。
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) { throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" }); }
String clientId = request.getParameter("client_id"); String clientSecret = request.getParameter("client_secret");
// If the request is already authenticated we can assume that this // filter is not needed Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { return authentication; }
if (clientId == null) { throw new BadCredentialsException("No client credentials presented"); }
if (clientSecret == null) { clientSecret = ""; }
clientId = clientId.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId, clientSecret);
return this.getAuthenticationManager().authenticate(authRequest);
}
|
解析:从request中获取clientId和clientsecret,Authentication authentication = SecurityContextHolder.getContext().getAuthentication();检查当前的request,实质应该为client,是否已经被认证过,如果认证过,直接返回authentication,否则进行下面的认证。将clientId和clientsecret封装成UsernamePasswordAuthenticationToken;使用AuthenticationManager的authenticate方法去对UsernamePasswordAuthenticationToken进行客户端的认证。
AuthenticationManager:
使用容器中的顶级身份管理器AuthenticationManager去进行身份认证(AuthenticationManager的实现类一般是ProviderManager。而ProviderManager内部维护了一个List,真正的身份认证是由一系列AuthenticationProvider去完成。而AuthenticationProvider的常用实现类则是DaoAuthenticationProvider。
DaoAuthenticationProvider内部又聚合了一个UserDetailsService接口,UserDetailsService才是获取用户详细信息的最终接口,而我们上面的文章中在内存中配置用户,就是使用了UserDetailsService的一个实现类InMemoryUserDetailsManager)。
client模式是没有用户的概念的,运行中会将UserDetailsService适配成ClientDetailsUserDetailsService
UserDetailsService的继承关系如下
AuthenticationManager的认证流程
AuthenticationManager是接口类,因此分析它的实现类 ProviderManager,ProviderManager 的 authenticate() 方法代码如下:
public Authentication authenticate(Authentication authentication) throws AuthenticationException { 。。。 。。。
for (AuthenticationProvider provider : getProviders()) { 。。。
try { result = provider.authenticate(authentication);
if (result != null) { copyDetails(authentication, result); break; } } 。。。
if (result == null && parent != null) { // Allow the parent to try. try { result = parentResult = parent.authenticate(authentication); } 。。。 }
|
主要是result = provider.authenticate(authentication);
AuthenticationProvider是个接口,主要分析他的实现类AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider,它调用父类AbstractUserDetailsAuthenticationProvider中的authenticate方法。
public Authentication authenticate(Authentication authentication) throws AuthenticationException { 。。。
// Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) { cacheWasUsed = false;
try { user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } 。。。
try { preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } }
postAuthenticationChecks.check(user);
if (!cacheWasUsed) { this.userCache.putUserInCache(user); }
Object principalToReturn = user;
if (forcePrincipalAsString) { principalToReturn = user.getUsername(); }
return createSuccessAuthentication(principalToReturn, authentication, user); }
|
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
调用的就是DaoAuthenticationProvider的方法,具体如下
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); 。。。 return loadedUser; 。。。
|
从数据库中获取user,像mybatis的调用格式
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
这两个方法是对获得的user进行验证
其中addtionalAuthenticationChecks代码如下:
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 。。。
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { 。。。 } |
使用passwordEncoder的校验方法。这样authenticationManager的认证就结束了。
2.2 token的生成
通过过滤器和认证管理器之后,客户端的前置认证基本完成了,现在分析token的产生
涉及到TokenEndpoint
public class TokenEndpoint extends AbstractEndpoint {
。。。
@RequestMapping(value = "/oauth/token", method=RequestMethod.GET) public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { 。。。 return postAccessToken(principal, parameters); }
@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);----③ 。。。
return getResponse(token);
} 。。。 }
|
①加载客户端信息
②结合请求信息clientId,scope,grantType,创建TokenRequest
③将TokenRequest传递给TokenGranter颁发token
getTokenGranter().grant(tokenRequest.getGrantType());
TokenGranter来生成AccessToken,具体继承关系如下
TokenGranter的设计思路是使用CompositeTokenGranter管理一个List列表,每一种grantType对应一个具体的真正授权者,在debug过程中可以发现CompositeTokenGranter 内部就是在循环调用五种TokenGranter实现类的grant方法,而granter内部则是通过grantType来区分是否是各自的授权类型。
public class CompositeTokenGranter implements TokenGranter {
。。。
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { for (TokenGranter granter : tokenGranters) { OAuth2AccessToken grant = granter.grant(grantType, tokenRequest); if (grant!=null) { return grant; } } return null; }
。。。
} |
五种类型分别是
以客户端模式为例,分析如何产生token,从他们的父类AbstractTokenGranter入手。
public abstract class AbstractTokenGranter implements TokenGranter { 。。
private final AuthorizationServerTokenServices tokenServices;
private final ClientDetailsService clientDetailsService;
private final OAuth2RequestFactory requestFactory;
private final String grantType;
。。。
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);
logger.debug("Getting access token for: " + clientId);
return getAccessToken(client, tokenRequest);
} grant方法通过tokenRequest获取client和用户的相关信息,通过getAcessToken方法获取token,其中getOAuth2Authentication根据client和tokenRequest获得OAuth2Request,其中包含用户的相关信息。生成token
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 void validateGrantType(String grantType, ClientDetails clientDetails) { 。。。 }
。。。
}
|
重点是tokenServices的createAccessToken方法。
public interface AuthorizationServerTokenServices { //创建token OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException; //刷新token OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest) throws AuthenticationException; //获取token OAuth2AccessToken getAccessToken(OAuth2Authentication authentication); } |
它的实现类为
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean {
。。。 。。。
@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 { // Re-store the access token in case the authentication has changed 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作为existingAccessToken,如果当前token不为空过期了,就将该token对应的refrestToken一起删除;
如果没有过期,就继续使用当前的token;如果当前的existingAccessToken是null说明没有token,如果过期token没有一个关联的refreshToken,就只能创建一个refreshToken。
使用新的refreshToken和authentication创建新的accessToken
使用tokenStore将token和authentication一起存储起来。
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和scope。 |
上面就是token 生成的大致流程。