在上篇文章我们分析了token的获取过程,那么拿到token后,将token放在请求头中进行资源的访问,客户端是如如何对token进行解析的呢,本文带你走进token校验的源码解析,基本流程如下所示
OAuth2AuthenticationProcessingFilter
过滤器TokenExtractor
将请求头中的token转换为Authentication
RemoteTokenServices
的loadAuthentication方法去向认证服务器发起token的校验BasicAuthenticationFilter
,对clientId等进行校验CheckTokenEndpoint
,先从tokenStore中获取token,然后通过token在tokenStore中获取Authentication信息请求被FilterChainProxy拦截到(ps:通过前面的文章查看其底层原理),通过OAuth2AuthenticationProcessingFilter
作为权限校验的入口进行token校验
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {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 {// 1 通过tokenExtractor解析出请求头中的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));}// 2 通过authenticationManager对当前authentication 进行校验Authentication authResult = authenticationManager.authenticate(authentication);if (debug) {logger.debug("Authentication success: " + authResult);}eventPublisher.publishAuthenticationSuccess(authResult);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);}...}
TokenExtractor解析token
public class BearerTokenExtractor implements TokenExtractor {...@Overridepublic Authentication extract(HttpServletRequest request) {// 获得token的值String tokenValue = extractToken(request);if (tokenValue != null) {// 将token封装到PreAuthenticatedAuthenticationToken 中PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(tokenValue, "");return authentication;}return null;}protected String extractToken(HttpServletRequest request) {// first check the header...// 解析请求头中的token 就是请求头中的Authorization解析出来并将相应的前缀去掉String token = extractHeaderToken(request);// bearer type allows a request parameter as well// 请求为空的话 则通过获取请求参数中的access_tokenif (token == null) {logger.debug("Token not found in headers. Trying request parameters.");token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);if (token == null) {logger.debug("Token not found in request parameters. Not an OAuth2 request.");}else {request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE);}}return token;}/*** Extract the OAuth bearer token from a header.* * @param request The request.* @return The token, or null if no OAuth authorization header was supplied.*/protected String extractHeaderToken(HttpServletRequest request) {Enumeration headers = request.getHeaders("Authorization");while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that)String value = headers.nextElement();if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();// Add this here for the auth details later. Would be better to change the signature of this method.request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE,value.substring(0, OAuth2AccessToken.BEARER_TYPE.length()).trim());int commaIndex = authHeaderValue.indexOf(',');if (commaIndex > 0) {authHeaderValue = authHeaderValue.substring(0, commaIndex);}return authHeaderValue;}}return null;}}
调用authenticationManager.authenticate(authentication);方法,这里的authenticationManager返回的是
OAuth2AuthenticationManager
实例
public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {// ....public Authentication authenticate(Authentication authentication) throws AuthenticationException {if (authentication == null) {throw new InvalidTokenException("Invalid token (token not found)");}// 从authentication拿到上一步封token的值String token = (String) authentication.getPrincipal();// 调用RemoteTokenServices的loadAuthentication对token进行校验OAuth2Authentication auth = tokenServices.loadAuthentication(token);if (auth == null) {throw new InvalidTokenException("Invalid token: " + token);}Collection 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 + ")");}checkClientDetails(auth);if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();// Guard against a cached copy of the same detailsif (!details.equals(auth.getDetails())) {// Preserve the authentication details from the one loaded by token servicesdetails.setDecodedDetails(auth.getDetails());}}auth.setDetails(authentication.getDetails());auth.setAuthenticated(true);return auth;}
...}
RemoteTokenServices发起远程校验
public class RemoteTokenServices implements ResourceServerTokenServices {
// ...@Overridepublic OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {// 封装请求参数MultiValueMap formData = new LinkedMultiValueMap();formData.add(tokenName, accessToken);HttpHeaders headers = new HttpHeaders();headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));// 远程调用token校验 就是通过restTemplate发起http请求 请求的认证服务器接口http://认证服务器地址/oauth/check_tokenMap 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-838if (!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 postForMap(String path, MultiValueMap formData, HttpHeaders headers) {if (headers.getContentType() == null) {headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);}@SuppressWarnings("rawtypes")Map map = restTemplate.exchange(path, HttpMethod.POST,new HttpEntity>(formData, headers), Map.class).getBody();@SuppressWarnings("unchecked")Map result = map;return result;}}
1 首先进入BasicAuthenticationFilter,对clientId进行校验(这里不做分析 参考上篇文章)
2、然后进入CheckTokenEndpoint,先从tokenStore中获取token
3. 调用resourceServerTokenServices.loadAuthentication方法,通过token在tokenStore中获取Authentication信息
最终返回授权结果