Spring Security OAuth 认证流程浅析:授权码模式

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 【1月更文挑战第16天】上一篇[Spring Security OAuth 认证流程浅析:密码模式],简单分析了密码模式授权流程的源码,这篇来试着分析 OAuth 中最具代表性的授权码模式。

上一篇(Spring Security OAuth 认证流程浅析:密码模式 ),简单分析了 Spring Security OAuth 密码模式授权流程的源码,这篇来试着分析 OAuth 中最具代表性的授权码模式。

由于内容的连贯性,建议读之前,先阅读上一篇分析密码模式的文章(Spring Security OAuth 认证流程浅析:密码模式 )。同时,阅读以下内容需要你了解 OAuth 的原理,可以阅读这篇文章回顾一下。

授权码模式的流程

先简单回顾一下授权码模式的流程。看下图:

image.png

  • A 步骤:用户访问客户端,客户端会将前者重定向到认证服务器。
  • B 步骤:认证服务器返回认证和权限确认的页面,用户选择是否给予客户端授权。
  • C 步骤:当用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码(authorization_code)。
  • D 步骤:客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
  • E 步骤:认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

请求授权码(authorization code)

在 Spring Security OAuth 中,获取授权码的方式如下:

GET HOST:PORT/oauth/authorize?response_type=code&client_id={
  
  {client_id}}&redirect_uri={
  
  {redirect_uri}}&scope={
  
  {scope}}&state={
  
  {state}}

我们可以在 AuthorizationEndpoint 类中,找到处理这个请求的代码。

image.png

这个方法的代码比较长,你可以自行查看。

第一次访问这个请求的时候,会做这么几件事儿:

  • 首先,校验 response_type 参数的值,只能是 code 或者 token。使用授权码模式的时候,给的参数值是 code,token 是在简易模式时使用的,这里不考虑。
  • 然后,验证 client_id 参数是不是提供了。
  • 之后,会判断用户是不是已经认证,因为我们是第一次请求,因此会跑出异常。

这里抛出的异常是 InsufficientAuthenticationException ,它的父类是 AuthenticationException ,异常被 ExceptionTranslationFilter 捕获后,会将此请求跳转到用户登录和授权的页面。

用户登录授权

当用户登录并且确认授权后,根据 Spring Security 的流程,会跳转到登录前请求的地址,因此会再次请求获取授权吗的地址。

这次请求后,用户已经被认证过,会进入之后的逻辑,进入请求时 client_id 对应的客户端的重定向地址。可参考下面的代码片段:

if ( authorizationRequest.isApproved ()) {
   
   
 if ( responseTypes.contains ( "token" )) {
   
   
 return getImplicitGrantResponse ( authorizationRequest ) ;
   }
 if ( responseTypes.contains ( "code" )) {
   
   
 return new ModelAndView ( getAuthorizationCodeResponse ( authorizationRequest,
            ( Authentication ) principal )) ;
   }
 }

在重定向到这个地址的时候,会包含授权服务器生成的授权码,这个请求会发送到客户端程序的后端服务,这样,客户端会接收到这个授权码。

至此,客户端在不接触用户名和密码的情况下,获取到了一个合法的授权码。

请求访问令牌(access token)

客户端在得到授权码以后,下一步就是请求访问令牌,请求方式如下:

POST HOST:PORT/oauth/token
Authorization: Basic <Base64("clientId:clientSecret")>
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code={
  
  {authorization_code}}&redirect_uri={
  
  {redirect_uri}}&client_id={
  
  {client_id}}&scope={
  
  {scope}}&state={
  
  {state}}

请求的地址是 /oauth/token 与密码模式请求的地址相同,具体的处理逻辑可以参考上一篇分析密码模式的文章(Spring Security OAuth 认证流程浅析:密码模式 ),这里关键的一行代码是:

 OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

因此,之后的逻辑是由 AuthorizationCodeTokenGranter 类型来处理的。这里掠过与上一篇文章中重复的内容,直接看 getOAuth2Authentication 方法的内容。我把代码贴出来:

@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
   
   

   Map<String, String> parameters = tokenRequest.getRequestParameters();
   String authorizationCode = parameters.get("code");
   String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI);

   if (authorizationCode == null) {
   
   
      throw new InvalidRequestException("An authorization code must be supplied.");
   }

   OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
   if (storedAuth == null) {
   
   
      throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
   }

   OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request();
   // https://jira.springsource.org/browse/SECOAUTH-333
   // This might be null, if the authorization was done without the redirect_uri parameter
   String redirectUriApprovalParameter = pendingOAuth2Request.getRequestParameters().get(
         OAuth2Utils.REDIRECT_URI);

   if ((redirectUri != null || redirectUriApprovalParameter != null)
         && !pendingOAuth2Request.getRedirectUri().equals(redirectUri)) {
   
   
      throw new RedirectMismatchException("Redirect URI mismatch.");
   }

   String pendingClientId = pendingOAuth2Request.getClientId();
   String clientId = tokenRequest.getClientId();
   if (clientId != null && !clientId.equals(pendingClientId)) {
   
   
      // just a sanity check.
      throw new InvalidClientException("Client ID mismatch");
   }

   // Secret is not required in the authorization request, so it won't be available
   // in the pendingAuthorizationRequest. We do want to check that a secret is provided
   // in the token request, but that happens elsewhere.

   Map<String, String> combinedParameters = new HashMap<String, String>(pendingOAuth2Request
         .getRequestParameters());
   // Combine the parameters adding the new ones last so they override if there are any clashes
   combinedParameters.putAll(parameters);

   // Make a new stored request with the combined parameters
   OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters);

   Authentication userAuth = storedAuth.getUserAuthentication();

   return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);

}

这段代码主要做了这么几件事:

  1. 从请求当中获取授权码(authorizationCode)和重定向地址(redirectUri)。
  2. 通过授权码获取 OAuth2Authentication 对象。
  3. OAuth2Authentication 中获取 OAuth2Request,并校验授权码和重定向地址的合法性。
  4. 创建新的 OAuth2Request 并创建新的 OAuth2Authentication 对象。

这个方法由父类中的方法调用,最终的结果,会返回到 tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest)) 这行代码,生成最终的 Token 并返回给客户端。

目录
相关文章
|
20天前
|
安全 Java 数据安全/隐私保护
|
4天前
|
Java Spring 容器
深入理解Spring Boot启动流程及其实战应用
【5月更文挑战第9天】本文详细解析了Spring Boot启动流程的概念和关键步骤,并结合实战示例,展示了如何在实际开发中运用这些知识。
13 2
|
6天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
47 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
23天前
|
存储 安全 Java
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)(下)
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)
25 2
|
23天前
|
安全 Cloud Native Java
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)(上)
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)
31 2
|
23天前
|
存储 安全 Java
第9章 Spring Security 的测试与维护 (2024 最新版)(下)
第9章 Spring Security 的测试与维护 (2024 最新版)
21 1
|
23天前
|
安全 Java 测试技术
第9章 Spring Security 的测试与维护 (2024 最新版)(上)
第9章 Spring Security 的测试与维护 (2024 最新版)
24 0
|
23天前
|
缓存 Java 数据库
第8章 Spring Security 的常见问题与解决方案(2024 最新版)(下)
第8章 Spring Security 的常见问题与解决方案(2024 最新版)
30 0
|
23天前
|
安全 Java 数据安全/隐私保护
第8章 Spring Security 的常见问题与解决方案(2024 最新版)(上)
第8章 Spring Security 的常见问题与解决方案(2024 最新版)
34 0
|
23天前
|
安全 Java API
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)(下)
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)
26 0