• (四)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);
    	}
    
    ...
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    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;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    调用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;
    
    	}
    ...
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    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;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    三 认证服务器根据token鉴权

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

  • 相关阅读:
    CGMH: Constrained Sentence Generation by Metropolis-Hastings Sampling
    ZEMAX | 室内照明案例分享2 —— 室内场景模拟
    迅镭激光万瓦切割设备中标全球轨交装备龙头中国中车
    聚类 监督聚类 k-means聚类
    docker自定义镜像
    华为OD机试 - 数字反转打印(Java 2023 B卷 100分)
    Elasticsearch(一)-基本概念与环境搭建
    Java Spring Cloud XXIII 之 配置中心
    Java开发者的Python快速进修指南:掌握T检验
    PMP考试可以延缓考吗?解答来了!
  • 原文地址:https://blog.csdn.net/Instanceztt/article/details/128197103