1. 程式人生 > 其它 >Oauth2的資源伺服器核心原始碼分析

Oauth2的資源伺服器核心原始碼分析

資源伺服器核心原始碼分析

OAuth2AuthenticationProcessingFilter

資源伺服器的核心是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);
	}
  • TokenExtractor的預設實現類BearerTokenExtractor
  • AuthenticationManager的預設實現類是OAuth2AuthenticationManager

TokenExtractor

介面的功能是提取請求中包含的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;
	}

OAuth2AuthenticationManager

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)