Spring Security Oauth2源码分析

avatar
作者
猴君
阅读量:3

Spring Security Oauth2源码分析

前言

1、用户发起了一个未经身份验证的请求。
2、Spring Security 的 DelegatingAuthenticationEntryPoint 组件检测到这个未经身份验证的请求。
3、通过一系列复杂的匹配器 DelegatingAuthenticationEntryPoint 确定这个请求需要进行身份验证。
4、DelegatingAuthenticationEntryPoint 执行了 LoginUrlAuthenticationEntryPoint来处理这个未经身份验证的请求。
5、LoginUrlAuthenticationEntryPoint将用户重定向到:
{baseUrl}/oauth2/authorization/{clientRegistrationId}

一:客户端OAuth2授权请求的入口

客户端进行第三方认证操作的起点,默认格式为
{baseUrl}/oauth2/authorization/{clientRegistrationId},其中 clientRegistrationId 代表着一个第三方标识。是在授权服务器中注册的应用 唯一标识id。

用户点击了这个请求后就开始了授权之旅。Spring Security 一定是拦截到了/oauth2/authorization后才启用了 OAuth2 相关的处理逻辑。那就去抓住这个源头!

1、DefaultOAuth2AuthorizationRequestResolver类

从名称上看着是一个默认 OAuth2 授权请求解析器。它实现了接口OAuth2AuthorizationRequestResolver

public interface OAuth2AuthorizationRequestResolver {     /**     * 从HttpServletRequest对象中解析封装 OAuth2AuthorizationRequest     */ 	OAuth2AuthorizationRequest resolve(HttpServletRequest request);      /**      * 从HttpServletRequest对象以及clientRegistrationId中解析封装 OAuth2AuthorizationRequest      */ 	OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId); } 

也就是说当我们请求 /oauth2/authorization 时,DefaultOAuth2AuthorizationRequestResolver 会从/oauth2/authorization对应的HttpServletRequest中提取数据封装到 OAuth2AuthorizationRequest 请求对象中做进一步使用。

其核心方法在 DefaultOAuth2AuthorizationRequestResolver 有两个重载,这里分析一个就够了

@Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {     // registrationId是通过uri路径参数/oauth2/authorization/{registrationId}获得的     String registrationId = resolveRegistrationId(request); 	if (registrationId == null) { 	    return null; 	} 	// 然后去请求对象request中提取key为action的参数,默认值是login 	String redirectUriAction = getAction(request, "login"); 	// 然后进入根本的解析方法 	return resolve(request, registrationId, redirectUriAction); } 

OAuth2AuthorizationRequest类

resolve(request,registrationId,redirectUriAction)方法才是最终从 /oauth2/authorization 提取 OAuth2AuthorizationRequest 的根本方法。resolve方法会根据不同的授权方式 (AuthorizationGrantType) 来组装不同的OAuth2AuthorizationRequest对象。

AuthorizationGrantType ,来自配置:

spring.security.client.registration.{registrationId}.authorization-grant-type= ×××××× 

OAuth2AuthorizationRequest 的几个参数的规则是固定的:

1、clientId 来自于配置,是第三方平台给予我们的唯一标识。 2、authorizationUri来自于配置,用来构造向第三方发起的请求 URL。 3、scopes 来自于配置,是第三方平台给我们授权划定的作用域,可以理解为角色。 4、state 自动生成的,为了防止 csrf 攻击。 5、authorizationRequestUri 向第三方平台发起授权请求的,可以直接通过OAuth2AuthorizationRequest的构建类来设置或者通过上面的authorizationUri等参数来生成。 6、redirectUri 当OAuth2AuthorizationRequest被第三方平台收到后,第三方平台会回调这个 URI 来对授权请求进行响应。 

authorizationRequestUri 的构建机制

如果不显式提供authorizationRequestUri就会通过OAuth2AuthorizationRequest中的

①responseType ②clientId ③scopes ④state ⑤redirectUri ⑥additionalParameters 

按照下面的规则进行拼接成authorizationUri的参数串,参数串的key和value都要进行 URI 编码。

https://{认证服务器地址}/oauth2/authorize?response_type=code&client_id=test123&state=VWr9pnTYODclkYgmDiCs2w6gE2CVEBH2WZVYCb_4nsc%3D&redirect_uri=http://localhost:8090/projectname/login/oauth2/code/test123 

然后OAuth2AuthorizationRequestRedirectFilter负责重定向到authorizationRequestUri向第三方请求授权。

redirectUri

认证服务器收到响应会调用 redirectUri ,回调也是有一定规则的,遵循{baseUrl}/{action}/oauth2/code/{registrationId}的路径参数规则。

1、baseUrl 是从我们/oauth2/authorization请求中提取的基础请求路径。 2、action,有两种默认值login、authorize ,当/oauth2/authorization请求中包含了action参数时会根据action的值进行填充。 3、registrationId 这个就不用多说了。  

3、OAuth2AuthorizationRequestRedirectFilter类

一看到它继承了 OncePerRequestFilter 就知道肯定是他了。甚至它的成员变量包含了用来解析 OAuth2 请求的 OAuth2AuthorizationRequestResolver 。到这里我们的路子就走对了,开始分析这个过滤器,下面是其核心过滤逻辑,这就是我们想要知道的 OAuth2 授权请求是如何被拦截处理的逻辑。

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 			throws ServletException, IOException { 		try { 			OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request); 			if (authorizationRequest != null) { 				this.sendRedirectForAuthorization(request, response, authorizationRequest); 				return; 			} 		} 		catch (Exception ex) { 			this.unsuccessfulRedirectForAuthorization(request, response, ex); 			return; 		} 		try { 			filterChain.doFilter(request, response); 		} 		catch (IOException ex) { 			throw ex; 		} 		catch (Exception ex) { 			// Check to see if we need to handle ClientAuthorizationRequiredException 			Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex); 			ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer 					.getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain); 			if (authzEx != null) { 				try { 					OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request, 							authzEx.getClientRegistrationId()); 					if (authorizationRequest == null) { 						throw authzEx; 					} 					this.sendRedirectForAuthorization(request, response, authorizationRequest); 					this.requestCache.saveRequest(request, response); 				} 				catch (Exception failed) { 					this.unsuccessfulRedirectForAuthorization(request, response, failed); 				} 				return; 			} 			if (ex instanceof ServletException) { 				throw (ServletException) ex; 			} 			if (ex instanceof RuntimeException) { 				throw (RuntimeException) ex; 			} 			throw new RuntimeException(ex); 		} 	} 

OAuth2AuthorizationRequestRedirectFilter 执行流程:
OAuth2AuthorizationRequestRedirectFilter执行流程
根据这个流程,如果要搞清楚 Spring Security OAuth2 是如何重定向到第三方认证平台的话就要深入研究sendRedirectForAuthorization方法。

二:OAuth2授权请求是如何构建并执行的

1、sendRedirectForAuthorization方法

sendRedirectForAuthorization方法的主要作用就是向授权服务器进行授权重定向访问。它所有的逻辑都和 OAuth2AuthorizationRequest 有关。

往上查找 OAuth2AuthorizationRequest 的详细介绍,然后再往下看。

当授权服务器收到 OAuth2 授权请求后,会将授权的回执通过我方提供的回调请求redirect_uri传递给我们。由于默认情况下回调的路径满足 /login/oauth2/code/* ,所以我们只要找到拦截回调的过滤器就可以知道 Spring Security 是如何处理回调了。通过搜索确认了 OAuth2LoginAuthenticationFilter 就是处理回调的过滤器。

三:OAuth2授权回调的处理机制

1、OAuth2LoginAuthenticationFilter类

第三方认证服务器在调用 redirect_uri 时附加 code 和 state 参数,在被这个Filter拦截后,创建一个待认证凭据 OAuth2LoginAuthenticationToken ,并委托给了AuthenticationManager 进行身份验证。

一旦成功验证,则生成认证凭据 OAuth2AuthenticationToken 和认证客户端对象OAuth2AuthorizedClient。最后, OAuth2AuthenticationToken 返回,并最终存储在SecurityContextRepository 完成认证处理;而 OAuth2AuthorizedClient 被保存到OAuth2AuthorizedClientRepository。流程图如下:

OAuth2LoginAuthenticationFilter执行流程
在这里插入图片描述

2、AuthenticationManager 的认证过程

AuthenticationManager 的实现类 ProviderManager 管理了众多的AuthenticationProvider。每一个 AuthenticationProvider 都只支持特定类型的Authentication,如果不支持将会跳过。另一个作用就是对适配的Authentication进行认证,只要有一个认证成功,那么就认为认证成功,所有的都没有通过才认为是认证失败。认证成功后的Authentication就变成授信凭据,并触发认证成功的事件。认证失败的就抛出异常触发认证失败的事件。

ProviderManager认证Token的流程:
在这里插入图片描述
从这里我们可以看出认证管理器 AuthenticationManager 针对特定的Authentication提供了特定的认证功能,我们可以借此来实现多种认证并存。

3、OAuth2 对应的 AuthenticationProvider

那么 OAuth2 登录有异曲同工之妙,我们需要找到OAuth2LoginAuthenticationToken对应的AuthenticationProvider。这里找到了两个:

1、OAuth2LoginAuthenticationProvider  2、OidcAuthorizationCodeAuthenticationProvider 

这两个各自对应的场景是什么呢,OAuth2LoginAuthenticationProvider 中有以下片段:

if (loginAuthenticationToken.getAuthorizationExchange().getAuthorizationRequest().getScopes() 				.contains("openid")) { 	// This is an OpenID Connect Authentication Request so return null 	// and let OidcAuthorizationCodeAuthenticationProvider handle it instead 	return null; } 

意思是说scopes中如果包含了openid就直接返回null,不会被OAuth2LoginAuthenticationProvider处理,而OidcAuthorizationCodeAuthenticationProvider 中正好相反。根据以往文章的脉络OAuth2LoginAuthenticationProvider就是我们需要的。

有兴趣可了解基于OIDC的 OAuth2 认证。

4、OAuth2LoginAuthenticationProvider

OAuth2LoginAuthenticationProvider 实现了授权回调的认证过程:
在这里插入图片描述
从上图中我们可以看出具体认证由OAuth2AuthorizationCodeAuthenticationProvider来负责,认证通过后会去获取用户的信息并封装为 OAuth2User ,最终生成授权成功的 OAuth2LoginAuthenticationToken 。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!