(四)Spring Security Oauth2.0 源码分析--客户端端鉴权(token校验)

一 引言

在上篇文章我们分析了token的获取过程,那么拿到token后,将token放在请求头中进行资源的访问,客户端是如如何对token进行解析的呢,本文带你走进token校验的源码解析,基本流程如下所示
在这里插入图片描述

  • 客户端向资源服务器发起请求时,在请求头Authorization携带申请的token
  • 请求被FilterChainProxy过滤器链拦截到,交由OAuth2AuthenticationProcessingFilter过滤器
  • 在OAuth2AuthenticationProcessingFilter中通过TokenExtractor将请求头中的token转换为Authentication
  • 然后调用OAuth2AuthenticationManager.authenticate(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 {

	...
	@Override
	public 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_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;
	}

	/**
	 * 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<String> 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<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 + ")");
		}

		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;

	}
...

}

RemoteTokenServices发起远程校验

public class RemoteTokenServices implements ResourceServerTokenServices {
// ...
    @Override
	public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
	    // 封装请求参数
		MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
		formData.add(tokenName, accessToken);
		HttpHeaders headers = new HttpHeaders();
		headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
		// 远程调用token校验 就是通过restTemplate发起http请求 请求的认证服务器接口http://认证服务器地址/oauth/check_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;
	}

}

三 认证服务器根据token鉴权

1 首先进入BasicAuthenticationFilter,对clientId进行校验(这里不做分析 参考上篇文章)
2、然后进入CheckTokenEndpoint,先从tokenStore中获取token
在这里插入图片描述
3. 调用resourceServerTokenServices.loadAuthentication方法,通过token在tokenStore中获取Authentication信息
在这里插入图片描述
最终返回授权结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值