spring cloud oauth2 check token源码解析

2,119 阅读5分钟

spring cloud oauth2 check_token源码解析

check_token核心类流程图

下图就是check_toknen的核心类

未命名文件 (1).png

整个流程是从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这个类

image.png

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");
}