(四)Spring Security Oauth2.0 源码分析--客户端端鉴权(token校验)
创始人
2024-03-22 23:48:19
0

一 引言

在上篇文章我们分析了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 {...@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;}}

三 认证服务器根据token鉴权

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

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...