资源服务器的核心是OAuth2AuthenticationProcessingFilter过滤器。它被插到配置为资源端口的过滤器链中,主要功能是获取请求中携带的access_token中,通过access_token提取OAuth2Authentication并存入Spring Security上下文。
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 { //提取请求携带的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)); } //获取token携带的认证信息,OAuth2AuthenticationManager主要做了3件事 //1.通过token获取用户的OAuth2Authentication对象 //2.验证访问资源resourceId是否符合范围 //3.验证客户端访问的scope Authentication authResult = authenticationManager.authenticate(authentication); if (debug) { logger.debug("Authentication success: " + authResult); } eventPublisher.publishAuthenticationSuccess(authResult); //将当前的Authentication放入到Context中,访问后面的资源 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); }
接口的功能是提取请求中包含的access_token,目前只有一个实现类:BearerTokenExtractor,它只用于提取Bearer类型的access_token。请求中携带的access_token参数即可以放在HTTP请求头中,也可以在HTTP请求参数中。核心源码如下:
@Override public Authentication extract(HttpServletRequest request) { String tokenValue = extractToken(request); if (tokenValue != null) { PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(tokenValue, ""); return authentication; } return null; } protected String extractToken(HttpServletRequest request) { // 首先从header中解析access_token String token = extractHeaderToken(request); // 然后从request parameter中access_token if (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; }
public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication == null) { throw new InvalidTokenException("Invalid token (token not found)"); } else { String token = (String)authentication.getPrincipal(); //借助tokenServices,根据token加载身份信息 OAuth2Authentication auth = this.tokenServices.loadAuthentication(token); if (auth == null) { throw new InvalidTokenException("Invalid token: " + token); } else { Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds(); if (this.resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(this.resourceId)) { throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + this.resourceId + ")"); } else { this.checkClientDetails(auth); if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) { OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails(); if (!details.equals(auth.getDetails())) { details.setDecodedDetails(auth.getDetails()); } } auth.setDetails(authentication.getDetails()); auth.setAuthenticated(true); return auth; } } } }
其中,最关键的tokenServices是ResourceServerTokenServices的实例。ResourceServerTokenServices接口的最主要的2个实现类是RemoteTokenServices和DefaultTokenServices。下面的断点中,我用的是远程调用,RemoteTokenServices
下面是我部分断点的图,比较简单,就不说明了,其中pig的官网也有说明,连接:pig 校验令牌详解 【原理】 · 语雀 (yuque.com)