您当前的位置: 首页 >  spring

暂无认证

  • 10浏览

    0关注

    94683博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Spring Security+OAuth2资源服务之令牌校验源码分析

发布时间:2022-02-24 00:05:45 ,浏览量:10

我们知道 Spring Security 采用 IoC 和 AOP思想,基于 Servlet 过滤器实现的安全框架。

传送门:Spring Security过滤器链加载执行流程分析:https://blog.csdn.net/qq_42402854/article/details/122205790

OAuth2AuthenticationProcessingFilter是OAuth2受保护资源的身份验证过滤器。重点查看它。

一、OAuth2AuthenticationProcessingFilter
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean { private final static Log logger = LogFactory.getLog(OAuth2AuthenticationProcessingFilter.class); private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); private AuthenticationManager authenticationManager; private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource(); private TokenExtractor tokenExtractor = new BearerTokenExtractor(); private AuthenticationEventPublisher eventPublisher = new NullEventPublisher(); private boolean stateless = true; ... } 
1、查看 doFilter方法

查看 OAuth2AuthenticationProcessingFilter类的 doFilter方法。

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. 从标头中提取OAuth令牌 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设置属性 request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal()); // 转为AbstractAuthenticationToken并设置Details if (authentication instanceof AbstractAuthenticationToken) { AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication; needsDetails.setDetails(authenticationDetailsSource.buildDetails(request)); } // 2. 认证管理器进行认证 Authentication authResult = authenticationManager.authenticate(authentication); if (debug) { logger.debug("Authentication success: " + authResult); } // 发布认证成功 eventPublisher.publishAuthenticationSuccess(authResult); // 4. 设置SecurityContext SecurityContextHolder.getContext().setAuthentication(authResult); } }// 5. 异常处理 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); } 

下面基于 JdbcTokenStore查看分析源码。

2、从标头中提取OAuth令牌

调用 BearerTokenExtractor的 extract方法,从标头中提取OAuth令牌。

最后封装返回PreAuthenticatedAuthenticationToken对象,它是一个预认证对象的Authentication,它的 principal属性包含了当前令牌。

@Override public Authentication extract(HttpServletRequest request) { String tokenValue = extractToken(request); if (tokenValue != null) { // 创建PreAuthenticatedAuthenticationToken PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(tokenValue, ""); return authentication; } return null; } 
2.1 extractToken方法如下:
protected String extractToken(HttpServletRequest request) { // 1. 检查消息头中的令牌 String token = extractHeaderToken(request); // 2. 消息头没有检查请求参数中是否有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); } } // 3.返回token return token; } 

注意:先从请求头中提取令牌,如果没有,再从请求参数中提取令牌。所以这也是通过令牌访问资源服务器的两种方式。

2.2 extractHeaderToken方法如下:
protected String extractHeaderToken(HttpServletRequest request) { // 1.获取Authorization消息头内容 Bearer xxxtoken Enumeration<String> headers = request.getHeaders("Authorization"); // 2. 循环消息头内容 while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that) String value = headers.nextElement(); if // 3. 如果已Bearer 开头((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) { // 4. 截取令牌  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. // 5. request对象添加属性 request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, value.substring(0, OAuth2AccessToken.BEARER_TYPE.length()).trim()); int commaIndex = authHeaderValue.indexOf(','); if (commaIndex > 0) { // 6.如果存在逗号,则重新截取 authHeaderValue = authHeaderValue.substring(0, commaIndex); } // 7. 返回token return authHeaderValue; } } return null; } 
3、调用认证管理器进行认证

查看认证管理器 OAuth2AuthenticationManager的 authenticate方法。

public Authentication authenticate(Authentication authentication) throws AuthenticationException { // Authentication预认证对象为null,抛出Invalid token (token not found)异常。 if (authentication == null) { throw new InvalidTokenException("Invalid token (token not found)"); } // 获取token  String token = (String) authentication.getPrincipal(); // 1. JdbcTokenStore这里调用DefaultTokenServices 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 + ")"); } // 2. 检查ClientDetails checkClientDetails(auth); // 填充认证信息并返回Authention 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; } 
3.1 调用DefaultTokenServices

查看 DefaultTokenServices类的 loadAuthentication方法。

public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, InvalidTokenException { //1. 查库,通过令牌获取token_id OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue); if (accessToken == null) { throw new InvalidTokenException("Invalid access token: " + accessTokenValue); } else if (accessToken.isExpired()) { tokenStore.removeAccessToken(accessToken); throw new InvalidTokenException("Access token expired: " + accessTokenValue); } //2. 查库,通过token_id获取授权信息 OAuth2Authentication result = tokenStore.readAuthentication(accessToken); if (result == null) { // in case of race condition throw new InvalidTokenException("Invalid access token: " + accessTokenValue); } if (clientDetailsService != null) { String clientId = result.getOAuth2Request().getClientId(); try { clientDetailsService.loadClientByClientId(clientId); } catch (ClientRegistrationException e) { throw new InvalidTokenException("Client not valid: " + clientId, e); } } return result; } 

在这里插入图片描述 在这里插入图片描述

3.2 检查ClientDetails

查看 checkClientDetails方法

private void checkClientDetails(OAuth2Authentication auth) { // 1. Oauth客户端如果为null ,不检查,配置了ClientDetailsService的实现类,会进行检查 if (clientDetailsService != null) { ClientDetails client; try { // 2. 获取客户端 client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId()); } catch (ClientRegistrationException e) { throw new OAuth2AccessDeniedException("Invalid token contains invalid client id"); } // 3. 获取配置的客户端授权范围 Set<String> allowed = client.getScope(); for (String scope : auth.getOAuth2Request().getScope()) { // 4. 如果没有当前授权,则抛出异常OAuth2AccessDeniedException if (!allowed.contains(scope)) { throw new OAuth2AccessDeniedException( "Invalid token contains disallowed scope (" + scope + ") for this client"); } } } } 
4、设置SecurityContext

认证管理器进行认证通过,将用户授权信息放到 SecurityContextHolder上下文中。

eventPublisher.publishAuthenticationSuccess(authResult); SecurityContextHolder.getContext().setAuthentication(authResult); 
5、异常处理
catch (OAuth2Exception failed) { // 清理SecurityContext SecurityContextHolder.clearContext(); if (debug) { logger.debug("Authentication request failed: " + failed); } // 发布认证失败 eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), new PreAuthenticatedAuthenticationToken("access-token", "N/A")); // 调用OAuth2AuthenticationEntryPoint返回ResponseEntity authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(failed.getMessage(), failed)); return; } chain.doFilter(request, response); } 

在这里插入图片描述

通过查看源码,对OAuth2令牌的身份验证有了一定认识。

– 求知若饥,虚心若愚。

关注
打赏
1655516835
查看更多评论
立即登录/注册

微信扫码登录

1.3261s