(四)Spring Security Oauth2.0 源码分析--客户端端鉴权(token校验)
创始人
2024-03-22 23:22:38
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信息
在这里插入图片描述
最终返回授权结果

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...