spring cloud oauth2 check_token源码解析
check_token核心类流程图
下图就是check_toknen的核心类
整个流程是从OAuth2AuthenticationProcessingFilter过滤器开始
-
当前端发起请求的时候,OAuth2AuthenticationProcessingFilter会拦截请求,然后通过tokenExtractor去提取请求中的token,构建出一个Authentication对象
-
然后调用OAuth2AuthenticationManager中authenticate()方法,authenticate()方法中主要流程就是调用RemoteTokenServices去验证token并且返回OAuth2Authentication对象,
-
RemoteTokenServices主要就是调用认证服务器,通过调用认证服务器CheckTokenEndpoint端点中的/oauth/check_token端点,验证token并取得token的信息,然后调用DefaultAccessTokenConverter中的extractAuthentication()方法获取用户信息,并且通过token信息构建出OAuth2Request对象,在根据用户信息和OAuth2Request对象构建出OAuth2Authentication并返回
-
extractAuthentication()方法中又是怎么获取用户信息嘞,其实它是通过调DefaultUserAuthenticationConverter中的extractAuthentication()方法,extractAuthentication()方法会判断token信息中是否包含用户名,不包含直接返回null,包含的情况下,则去获取token信息中的用户权限信息,然后判断userDetailsService是否为空,为空则返回用户名,不为空则调用userDetailsService中loadUserByUsername()方法获取用户信息,最后封装成Authentication信息返回
源码解析
下面就让我们根据源码来走一遍流程,彻底搞清楚check_token流程
- OAuth2AuthenticationProcessingFilter会拦截请求,然后通过tokenExtractor去提取请求中的token,构建出一个Authentication对象
- 调用authenticationManager.authenticate(authentication)方法验证token,获取用户信息以及客户端信息封封装成Authentication对象存入SecurityContextHolder中
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
final boolean debug = logger.isDebugEnabled();
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
try {
//通过 tokenExtractor.extract(request)方法获取到请求中token,并返回Authentication对象
Authentication authentication = tokenExtractor.extract(request);
if (authentication == null) {
if (stateless && isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}
SecurityContextHolder.clearContext();
}
if (debug) {
logger.debug("No token in request, will continue chain.");
}
}
else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
//调用authenticationManager.authenticate(authentication)验证token,返回Authentication对象
Authentication authResult = authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
eventPublisher.publishAuthenticationSuccess(authResult);
//check_token流程已经走完,此时已经获取到用户信息以及客户端信息,将信息存入到SecurityContextHolder.getContext()中
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}
catch (OAuth2Exception failed) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + failed);
}
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException(failed.getMessage(), failed));
return;
}
chain.doFilter(request, response);
}
接下来让我们来一起解析authenticationManager.authenticate(authentication)中的源码,authenticationManager有7个实现类,而check_token用到的是OAuth2AuthenticationManager这个类
OAuth2AuthenticationManager.authenticate()又做了一些什么事情
-
首先它获取到token,通过调用tokenServices.loadAuthentication(token)得到了一个OAuth2Authentication对象,OAuth2Authentication对象中包含了用户信息以及客户端信息
-
然后对获取到OAuth2Authentication中的客户端信息进行验证
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
//获取token
String token = (String) authentication.getPrincipal();
//通过tokenServices调用认证资源服务器CheckTokenEndpoint端点上/oauth/check_token端点,验证token拿到token信息以及客户端信息
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token);
}
//验证客户端资源id
Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
}
//验证客户端scope信息
checkClientDetails(auth);
if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
// Guard against a cached copy of the same details
if (!details.equals(auth.getDetails())) {
// Preserve the authentication details from the one loaded by token services
details.setDecodedDetails(auth.getDetails());
}
}
auth.setDetails(authentication.getDetails());
auth.setAuthenticated(true);
return auth;
}
private void checkClientDetails(OAuth2Authentication auth) {
if (clientDetailsService != null) {
ClientDetails client;
try {
client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId());
}
catch (ClientRegistrationException e) {
throw new OAuth2AccessDeniedException("Invalid token contains invalid client id");
}
Set<String> allowed = client.getScope();
for (String scope : auth.getOAuth2Request().getScope()) {
if (!allowed.contains(scope)) {
throw new OAuth2AccessDeniedException(
"Invalid token contains disallowed scope (" + scope + ") for this client");
}
}
}
}
接下我们一起揭秘 tokenServices.loadAuthentication(token)是如何验证token,并且得到token的信息
-
本来是基于ResourceServerTokenServices的RemoteTokenServices实现类来讲解,通过下面的方法我们可以看出RemoteTokenServices是通过http访问远程接口验证token并且得到token信息,那么访问的哪里嘞? 其实是去访问认证服务器checkTokenEndpoint中/oauth/check_token端点,这个路径可以自己去设置,
-
然后调用tokenConverter.extractAuthentication(map)对token进行转换以及获取用户信息
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
//封装Token参数
MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add(tokenName, accessToken);
//封装请求头信息
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
//远程访问验证token并获取到token信息
Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);
if (map.containsKey("error")) {
if (logger.isDebugEnabled()) {
logger.debug("check_token returned error: " + map.get("error"));
}
throw new InvalidTokenException(accessToken);
}
// gh-838
if (!Boolean.TRUE.equals(map.get("active"))) {
logger.debug("check_token returned active attribute: " + map.get("active"));
throw new InvalidTokenException(accessToken);
}
return tokenConverter.extractAuthentication(map);
}
private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
if (headers.getContentType() == null) {
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
}
@SuppressWarnings("rawtypes")
Map map = restTemplate.exchange(path, HttpMethod.POST,
new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
@SuppressWarnings("unchecked")
Map<String, Object> result = map;
return result;
}
tokenConverter.extractAuthentication(map)是如何转换token信息的
-
调用 DefaultUserAuthenticationConverter.extractAuthentication(map)方法获取用户信息返回Authentication对象
-
获取token中客户端信息,根据客户端信息构建OAuth2Request对象
-
根据OAuth2Request以及用户信息构建OAuth2Authentication对象返回,其实OAuth2Authentication对象就是一个用户信息,请求参数,客户端参数的汇总对象
//tokenConverter.extractAuthentication(map)方法
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
Map<String, String> parameters = new HashMap<String, String>();
//获取token信息中客户端scope信息
Set<String> scope = extractScope(map);
//调用 DefaultUserAuthenticationConverter.extractAuthentication(map)方法获取用户信息返回Authentication对象
Authentication user = userTokenConverter.extractAuthentication(map);
//获取token信息中
String clientId = (String) map.get(clientIdAttribute);
parameters.put(clientIdAttribute, clientId);
if (includeGrantType && map.containsKey(GRANT_TYPE)) {
parameters.put(GRANT_TYPE, (String) map.get(GRANT_TYPE));
}
Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? getAudience(map)
: Collections.<String>emptySet());
Collection<? extends GrantedAuthority> authorities = null;
if (user==null && map.containsKey(AUTHORITIES)) {
@SuppressWarnings("unchecked")
String[] roles = ((Collection<String>)map.get(AUTHORITIES)).toArray(new String[0]);
authorities = AuthorityUtils.createAuthorityList(roles);
}
OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, null, null,
null);
return new OAuth2Authentication(request, user);
}
private Collection<String> getAudience(Map<String, ?> map) {
Object auds = map.get(AUD);
if (auds instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<String> result = (Collection<String>) auds;
return result;
}
return Collections.singleton((String)auds);
}
private Set<String> extractScope(Map<String, ?> map) {
Set<String> scope = Collections.emptySet();
if (map.containsKey(scopeAttribute)) {
Object scopeObj = map.get(scopeAttribute);
if (String.class.isInstance(scopeObj)) {
scope = new LinkedHashSet<String>(Arrays.asList(String.class.cast(scopeObj).split(" ")));
} else if (Collection.class.isAssignableFrom(scopeObj.getClass())) {
@SuppressWarnings("unchecked")
Collection<String> scopeColl = (Collection<String>) scopeObj;
scope = new LinkedHashSet<String>(scopeColl); // Preserve ordering
}
}
return scope;
}
下面是userTokenConverter.extractAuthentication(map)
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
//获取token信息中用户名
Object principal = map.get(USERNAME);
//获取token信息中用户资源权限集合
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
//userDetailsService == null 直接将用户信息设置成用户名
//userDetailsService != null 调 userDetailsService.loadUserByUsername根据用户名查询到用户信息
if (userDetailsService != null) {
UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey(AUTHORITIES)) {
return defaultAuthorities;
}
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString((Collection<?>) authorities));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}