oauth2 spring-authorization-server 分析

1. 选型

选用 spring security 5.2.x+作为框架,在 spring security 5.2.x+ 框架中进行扩展。

Spring 曾经有旧版支持 OAuth2 的方案:Spring Security OAuth 项目,该项目已经被逐步淘汰。但网上有不少仍然是这个方案,需要充分注意他们的区别。

1.1. Spring Security 5.2.x+

1.1.1. 说明

spring security 实现 OAuth 的框架分为 spring security OAuth 项目和 spring security 5.2.x 自带的 OAuth 功能,目前 spring security OAuth 已作废,官方推荐使用 spring security 5.2.x 自带的 OAuth

1.1.2. Spring Security 5.2.x+自带 OAuth

spring security 5.2.x+ 只有资源服务器和客户端,并不包含授权服务器,官方推荐使用 spring-authorization-server,目前版本 0.2.3。

1.spring security 5.2.x+

项目地址:Spring Security

GitHub:https://github.com/spring-projects/spring-security

2.授权服务器

GitHub:https://github.com/spring-projects/spring-authorization-server

1.1.3. Spring Security OAuth 项目

spring security OAuth 项目已作废

oauth2 spring-authorization-server 分析_第1张图片

项目地址:Spring Security OAuth

GitHub:https://github.com/spring-projects/spring-security-oauth

1.1.4. 两个项目对比分析

https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix

2. 核心过滤链分析

 OAuth2 方案,支持在单体应用下启用。在单体应用中使用时,该应用的角色将会同时为“授权服务器”和“资源服务器”。

单体应用下,应用会同时成为“授权服务器”和“资源服务器”。分别对应两条由 Spring Security 创建的过滤器链,用于处理授权请求和资源服务的鉴权安全。

2.1. 授权服务器 SecurityFilterChain 过滤链

配置类 AuthorizationServerSecurityAutoConfiguration

oauth2 spring-authorization-server 分析_第2张图片

该过滤器链,会匹配下图的 URL 和 Method 的请求。

oauth2 spring-authorization-server 分析_第3张图片

 在授权服务器的配置类生效后,将会对上图中的符合的 URL 和 Method 的请求进行拦截,也就是说,只要请求符合,都会进入这个过滤器链中被处理。

符合的请求,会经过上图中的过滤器链被处理。

过滤器链中包含比较多的过滤器,这里,比较重要的过滤器有如下介绍的。

2.1.1. OAuth2AuthorizationEndpointFilter

这个过滤器用来处理授权码认证过程中获取 code 的请求。

http://{{AuthorizationServer}}/oauth2/authorize?client_id=client&response_type=code&scope=testScope

若请求成功,返回的 302 跳转路径是:

http://localhost:8080/callback?code=EuO9WT96cMPoTB777pTuEk9TW5kZEuX-WKwbtZBJfLoEUlfGLBBmceP_qsEuOiypnkDL_wO0FyNlc9qtAxupFSx0hRBntifGjipA2zJNk897VoXd5wlib26W3PaRBkIw

这样,就能获取到授权码,进行下一步的认证了。

2.1.2. OAuth2ClientAuthenticationFilter

客户端认证过滤器,主要是对请求的客户端进行认证。无论 grant type 是授权码还是客户端认证,请求中都会包含 client id 和 client secret。此过滤器就是确认客户端的 client id 和 client secret 是否正确。

与其他过滤器不同,如果客户端认证成功,会把客户端信息转为 Authentication 对象,保存在 SecurityContext 中,然后流转到下一个过滤器。

oauth2 spring-authorization-server 分析_第4张图片

2.1.3. OAuth2TokenEndpointFilter

处理不同 Grant Type,并真正颁发 Token(AccessToken 和 RefreshToken)的过滤器。这个过滤器是颁发的核心,并且处理的事情比较复杂,在后面从流程介绍时再进一步说明。

2.1.4. OAuth2TokenIntrospectionEndpointFilter

授权服务器会颁发 Token,同时,也负责要验证颁发出的 Token 的有效性。此过滤器被调用用于确认 Token 的有效性,Token 有效则返回属于这个 Token 的一些认证授权信息。

http://{{AuthorizationServer}}/oauth2/introspect?token={{AccessTokenUUID}}

2.1.5. OAuth2TokenRevocationEndpointFilter

负责 Token 的注销

http://{{AuthorizationServer}}/oauth2/revoke?token={{AccessTokenUUID}}

2.2. 资源服务器 SecurityFilterChain 过滤链

除授权服务器拦截的 url 外,其他任意请求都进资源服务器配置类的过滤器链。注意,任意请求的意思,也就是所有请求都要经过这里进行安全的鉴权和控制。这就是在 OAuth2 方案下进行鉴权的关键过滤器链了。

oauth2 spring-authorization-server 分析_第5张图片

这个过滤器链,与传统(单体)应用下的过滤器链中的过滤器类(数量顺序)基本上是一致的。除了在资源服务器下新增的 BearerTokenAuthenticationFilter 过滤器。

2.2.1. BearerTokenAuthenticationFilter

在资源服务器的角度下,任何请求都需要验证该请求的有效性。请求中必须要附上 Token,那 Token 会经过此过滤器,调用认证管理器 AuthenticationManager 来对此 Token 进行校验(一般校验流程与上述授权服务器的 OAuth2TokenIntrospectionEndpointFilter 一致)。

成功后,则把校验成功的信息存储在 SecurityContext 中,然后转到下一步的过滤器进行鉴权。

3. 授权模式

对不同授权许可类型进行分析与流程的梳理、记录,并整理对此进行扩展补充的地方。

3.1. 客户端凭据许可

客户端凭据许可:grant_type=client_credentials

下面会介绍一下客户端凭据许可的使用场景。在解释的案例中,小兔软件代指一个软件应用

客户端凭据许可主要使用在访问的资源,没有明确的资源拥有者。换句话说就是,如果小兔软件访问了一个不需要用户小明授权的数据,比如获取京东 LOGO 的图片地址,这个 LOGO 信息不属于任何一个第三方用户,再比如其它类型的第三方软件来访问平台提供的省份信息,省份信息也不属于任何一个第三方用户。

此时,在授权流程中,就不再需要资源拥有者这个角色了。当然了,你也可以形象地理解为 “资源拥有者被塞进了第三方软件中” 或者 “第三方软件就是资源拥有者”。这种场景下的授权,便是客户端凭据许可,第三方软件可以直接使用注册时的 app_id 和 app_secret 来换回访问令牌 token 的值。

还是以小明使用小兔软件为例,来看下客户端凭据许可的整个授权流程,如下图所示:

oauth2 spring-authorization-server 分析_第6张图片

这里,图示的app_idapp_secret等同于client_idclient_secret

3.1.1. 获取访问 token

3.1.1.1. 请求响应说明

请求格式

名称 说明
url /oauth2/token
Method POST
Content-Type application/x-www-form-urlencoded
Authorization 格式为:Basic 。 为 base64 编码的 {client_id}:{client_secret}。

请求参数

名称 说明
grant_type 授权许可类型。 固定为 client_credentials(客户端凭据许可)
scope 可选。申请的权限范围

响应

名称 说明
access_token 访问 token,格式为 uuid
scope 申请并获得授权的 scope
token_type token 的类型。固定值 Bearer
expires_in 访问 token 有效期,单位为秒

客户端凭据许可中,根据 OAuth2 规范,不会返回 refresh_token 。因此,如果 token 过期,则重新发起申请获取一次就可以了。

3.1.1.2. postman 测试结果

请求

POST /oauth2/token?grant_type=client_credentials&scope=test1 test2 HTTP/1.1
Host: localhost:8080
Authorization: Basic Y2xpZW50OjEyMzQ1Ng==

oauth2 spring-authorization-server 分析_第7张图片

 oauth2 spring-authorization-server 分析_第8张图片

其中,client 客户端信息要以 Header Authorization: Basic Y2xpZW50OjEyMzQ1Ng==发送。

Y2xpZW50OjEyMzQ1Ng==是把client:123456按 Base64 编码算出来的值。

响应

{
  "access_token": "d9787169-3b82-4477-9c5f-8e8b640582ed",
  "scope": "test2 test3",
  "token_type": "Bearer",
  "expires_in": "7200"
}

3.1.1.3. 请求序列图

oauth2 spring-authorization-server 分析_第9张图片

请求序列图详细分析如下:

客户端发起请求/oauth2/token

<1>对客户端信息进行认证

/oauth2/token接口需要客户端认证通过才能访问,OAuth2ClientAuthenticationFilter(客户端认证过滤器)拦截请求,调用OAuth2ClientAuthenticationProvider(客户端认证管理器的提供者)对传入的client_idclient_secret进行判断。登录认证成功后,设置认证成功的结果(OAuth2ClientAuthenticationToken,内含客户端信息的RegisteredClient结果)到SecurityContext中。然后跳到下一步的过滤器中。

<2>对客户端信息进行二次认证

过滤器OAuth2TokenEndpointFilter继续拦截此请求,然后在颁发 Token 之前,请求OAuth2ClientCredentialsAuthenticationProvider(客户端凭据许可认证管理器的提供者)的authenticate()方法进行第二次认证。因为第一步的OAuth2ClientAuthenticationFilter已经校验过client_secret,这里主要对 client 的授权模式是否吻合,以及 scope 的授权范围进行校验就通过了。

OAuth2TokenEndpointFilter中,调用 OAuth2XxxxAuthenticationProvider 的 authenticate() 方法进行认证是固定流程。实际会根据不同的grant_type去选择调用不同的 OAuth2XxxxAuthenticationProvider 进行认证。因此,对客户端信息进行了二次认证,第一次和第二次的认证的职责是不通的。

<3>创建访问 Token

<3.1>开始颁发 Token:增强 Token 中包含的信息

首先是要确定 Token 中包含的信息。在OAuth2ClientCredentialsAuthenticationProvider中通过 OAuth2TokenCustomizer 定制 token 相关信息(本质是定制 JwtEncodingContext 对象的 Claims 属性,相当于在一个 Map 中放进自定义的 Key 和 Value 值)。

<3.2>开始颁发 Token:修改 tokenValue 值为 uuid 格式

然后 JwtEncoder 根据定制之后的JwtEncodingContext,生成 jwtAccessToken,这里的 jwtAccessToken 可以认为只是一个以 jwt 为容器存储了 token 的不同属性信息的候选 AccessToken

<3.3>开始颁发 Token:正式颁发 OAuth2AccessToken

之后,真正颁发 AccessToken。通过 jwtAccessToken 构建 OAuth2AccessToken 对象,使用了 jwtAccessToken 的 tokenValue 值(该值是 UUID)。OAuth2AccessToken 才是真正要颁发的 AccessToken 对象。

<4>持久化 Token 及认证过程中的所有信息

为了在颁发 Token 后,能对 Token 进行验证其合法性,以及返回 Token 包含的信息,因此需要对 Token 及其相关的信息进行持久化。

结合OAuth2AccessTokenjwtAccessToken这两个对象,把它们中相关的属性抽出构建为OAuth2Authorization对象(最终的这个对象包含了客户端信息、token 信息、GrantType 信息、authorizedScopes 信息,token 中的所有 Claims 信息的集合),然后通过接口OAuth2AuthorizationService保存OAuth2Authorization对象,因为这个对象包含的信息最完整,这里保存下来后便于后续对 Token 进行校验。

<5>返回响应信息

最终,OAuth2ClientCredentialsAuthenticationProvider返回新 New 的OAuth2AccessTokenAuthenticationToken对象到OAuth2TokenEndpointFilter,这个 Filter 过滤器处理转换为返回给前端的响应OAuth2AccessTokenResponse

OAuth2AccessTokenResponse 对象

3.1.2. 校验 token,访问需要 API 权限的接口

第三方客户端,请求访问资源时,需要携带 token 来进行访问。

这里以访问接口 /api/users/{{user_id}} 为例说明。

3.1.2.1. postman 测试

访问需要权限的接口

将获取的 access_token 复制到请求头 Authorization 然后值为 Bearer accessToken 值。如下图所示。

oauth2 spring-authorization-server 分析_第10张图片

3.1.2.2. token校验方式

3.1.2.3. 通过HTTP校验Token请求序列图

这里的请求序列图,主要描述在过滤器链中的过滤器如何对 token 的有效性进行判断,不会涉及对 /api/users/{{user_id}} 这个请求具体的业务处理逻辑进行描述。

oauth2 spring-authorization-server 分析_第11张图片

请求权限保护的接口 /api/users/{{user_id}} ,接口被部署在角色为资源服务器的应用上。

资源服务器上,

<1>BearerTokenAuthenticationFilter 认证过滤器,处理 token 请求。首先,由BearerTokenResolver 类负责从 request 中读取解析出 token 值。这里。成功读取 Token 后,封装到 BearerTokenAuthenticationToken 对象中,进行下一步对 Token 的验证。

<2>OpaqueTokenAuthenticationProvider 是 Token 的认证者。

校验 Token 成功后,此处可以扩展,比如根据授权服务器校验 token 返回的信息,组装权限信息,详细后述,这里先按流程继续分析。

<3>NimbusOpaqueTokenIntrospector 通过配置好的 url 地址/oauth2/introspect,请求授权服务器。这个请求授权服务器的过程,有 Spring 内部处理,为了方便后续研发的理解,这里列出这个远程服务调用的 HTTP 调用的详细参数说明。

请求响应说明

请求

名称 说明
url /oauth2/introspect
Method POST
Content-Type application/x-www-form-urlencoded
Authorization Basic 值 值为 base64 [客户端 ID]:[客户端密码]

请求参数

名称 说明
token 访问 token

响应

名称 说明
active true 表示 token 有效 false 表示无效
client_id 客户端 ID
iat token 的签发时间
exp token 的过期时间,这个过期时间必须要大于签发时间
scope 申请的 scope
token_type token 的类型
nbf 定义在什么时间之前,该 jwt 都是不可用的
sub token 所面向的用户
aud 接收 tokent 的一方
jti token 的唯一身份标识,该值与 token 值应该一致,主要用来作为一次性 token,从而回避重放攻击。

当发起该请求后,后续步骤转到角色为授权服务器的应用上。

授权服务器上:

<4>OAuth2TokenIntrospectionEndpointFilter 拦截 /oauth2/introspect 请求,调用OAuth2TokenIntrospectionAuthenticationProvider校验 token。

<5>OAuth2TokenIntrospectionAuthenticationProvider 处理 token 校验请求,调用 OAuth2AuthorizationService 类获取 OAuth2Authorization 对象。

之后,从 Provider 中返回 token 对应的tokenClaims认证信息。OAuth2TokenIntrospection 对象实际上就是包含tokenClaims认证信息的对象。

再次转到资源服务器:

<6>NimbusOpaqueTokenIntrospector 接收授权服务器的校验 token 响应,组装为 OAuth2AuthenticatedPrincipal 对象。

<7>OpaqueTokenAuthenticationProvider 根据 OAuth2AuthenticatedPrincipal 信息,返回 BearerTokenAuthentication 对象

<8>BearerTokenAuthenticationFilter 设置认证对象 BearerTokenAuthentication 到 SecurityContext 中。

<9>最后,认证信息存储好,会经过 API 接口鉴权的 Filter 进行权限的鉴定,通过后,则进入业务处理的 Controller 。

3.1.3. 注销 token

3.1.3.1. 请求响应说明

请求

名称 说明
url /oauth2/revoke
Method POST
Content-Type application/x-www-form-urlencoded
Authorization Basic 值 值为 base64 [客户端 ID]:[客户端密码]

请求参数

名称 说明
token 访问 token,必填

响应

Http 状态码为 200 即成功。

3.1.3.2. postman 测试

oauth2 spring-authorization-server 分析_第12张图片

3.1.3.3. 请求时序图

oauth2 spring-authorization-server 分析_第13张图片

请求/oauth2/revoke

<1>/oauth2/revoke 接口需要登录才能访问,OAuth2ClientAuthenticationFilter(客户端认证过滤器)拦截请求,调用 OAuth2ClientAuthenticationProvider(客户端认证提供者)对传入的 clientId 和 clientSeceret 进行判断。登录认证成功后,设置认证对象到 SecurityContext 中

<2>登录成功后,OAuth2TokenRevocationEndpointFilter 拦截地址/oauth2/revoke

<3>OAuth2TokenRevocationAuthenticationProvider 处理 token 注销,调用 OAuth2AuthorizationService 类获取 OAuth2Authorization,处理注销。处理注销的方式是对这个 token 增加一个 meta 标签"metadata.token.invalidated"=true,并重新保存 此OAuth2Authorization 对象到 InMemory 内存或者 Redis 中。这里,通过增加 metadata 而不是直接删除存储中的 token 信息,是因为需要非常清晰明确该 token 已经被注销,如果提早删去或清理掉,则授权服务器端就无法判断该 token 最正确的状态(区分不了 token 错误 还是 token 已注销)。

<4>OAuth2TokenRevocationEndpointFilter 设置响应状态为 OK。

3.2. 授权码许可类型

oauth2 spring-authorization-server 分析_第14张图片

3.2.1. 获取授权码

获取授权码时,会引导用户到授权页面,当未登录时,需要用户登录。登录成功后,如果客户端未配置自动授权,则会显示授权页面,如果配置自动授权,则直接跳过授权页面,302 跳转返回授权码。

这里,再分不同小节,说明 用户登录 - 授权页面 - 获取授权码 的整套流程。

3.2.1.1. 用户登录

1.请求说明

请求格式

名称 说明
url api/login
Method POST
Content-Type application/x-www-form-urlencoded

请求参数

名称 说明 注意
username 用户名
password 用户密码

2.postman 测试

执行 用户登录 操作,返回两个有用的信息给浏览器和前端:

  • 登录成功后的 Cookie(含 JSession),后续读取授权码需要从 Session 中读取当前用户的信息;
  • LoginUserDto 的信息(JSON 对象),前端会缓存该用户信息到前端,用于判断当前是否有用户登录,用于给前端根据用户登录状态显示不同的界面。

3.2.1.2. 授权页面

授权页面,是用于呈现给用户,第三方客户端请求获取当前用户的什么权限。作为权限的拥有者(用户)必须要确认是否允许第三方客户端访问自己的信息。

因此,这个授权页面(Code 页面)是需要用户确认并做出选择的。每一个客户端的配置选项中,通过字段 requiry_user_consent 来设置是否展现该页面。

强烈建议对于第三方的客户端,都要求开启授权页面的展示。如果是受信任的第一方客户端,则可以自行决定是否展示。

如果客户端配置表中的 require_user_consent=1 ,用户登录成功后的下一步,前端应当展现授权页面。

请求

名称 说明
url oauth2/authorize
Method GET

请求参数

名称 说明 注意
client_id 客户端 ID
response_type 授权码凭证许可固定值为 code
scope 授权的 scope 编码 分配多个则用空格分隔

访问上述请求后,会返回授权页面 JSON 数据的请求地址,如下示例。

http://{{AuthorizationServer}}/oauth2/consent?client_id={{client}}&scope={{scope}}&state={{consentState}}

继续转去请求此地址,则会返回 JSON 格式的响应。

JSON 格式:

{
  "clientId": "客户端ID",
  "clientName": "客户端名称",
  "principalName": "登录用户名",
  "state": "state值",
  "scopes": [
    {
      "scope": "scope编码",
      "scopeName": "scope名称",
      "scopeProfileInfo": "scope的简介"
    }
    //...
  ]
}

前端就可以根据上述 JSON 显示授权页面了。

前端根据授权的 JSON 响应,展示页面,在页面中会有一个“允许”的授权控制按钮,如果用户点击“允许”,则会继续发送确认授权的请求。

当用户选择授权的 scope,点击确认授权授权的请求格式如下:

请求

名称 说明
url oauth2/authorize
Method POST
Content-Type application/x-www-form-urlencoded

请求参数

名称 说明 注意
client_id 客户端 ID
state 授权页面响应的 json 中的 state 值
scope 授权的 scope 编码 分配多个则参数名有多个同名的 scope 键

这里需要注意,首先 method 从 GET 请求,改为 POST 请求,POST 请求必须注明是 form-urlencoded 格式的表单提交。

第二,这里的 scope 不再是用空格分割,而是会在 form 表单中用相同的键(键值就是 scope)来填写多个不同的 scope。

postman 模拟用户授权提交

oauth2 spring-authorization-server 分析_第15张图片

可以从截图中看出,响应信息为 302 状态码,并从 Header 的 Location 中读取出授权码

3.2.1.3. 获取授权码

1.请求说明

请求

名称 说明
url oauth2/authorize
Method GET/POST
Content-Type application/x-www-form-urlencoded

请求参数

名称 说明 注意
client_id 客户端 ID
response_type 响应类型 固定为 code
scope 申请的 scope 多个以空格分割 可选
redirect_uri 跳转的 uri 可选 draft-ietf-oauth-v2-1-01 地址中本机不能是 localhost,可使用 127.0.0.1,且和客户端中的 redirect_uri 一直

2.响应说明

<1>当客户端配置不需要用户授权同意时,则直接跳过授权页面,直接 302 跳转

http://localhost:8080/callback?code=vNAXkiS70FzBEsZ1wyXYhDC6eRQ8W7T7kQ8ieSR97CsJzNSQxCZmcJzm6wIEAQ_toe7nIqiJ7qHzPLX4HJ-hro647F5j3ow0TX5P-k-OhcbSfzNcj9qR8S03BoOvjxjw

postman 测试获取授权码

oauth2 spring-authorization-server 分析_第16张图片

使用 Postman 做测试的话,因为它不是一个浏览器,所以需要在 Settings 里面关闭自动跟踪重定向的新 URL 地址,确保在 Header 头的 Location 中能截取出授权码 code 的值。

<2>当客户端配置需要用户授权同意时,则会跳转到授权页面

postman 测试跳转到授权页面

oauth2 spring-authorization-server 分析_第17张图片

使用 Postman 做测试的话,因为它不是一个浏览器,所以需要在 Settings 里面打开自动跟踪重定向的新 URL 地址以 ,JSON 格式响应。

3.请求序列图

oauth2 spring-authorization-server 分析_第18张图片

<1>OAuth2AuthorizationEndpointFilter 处理获取授权码的相关逻辑,创建 OAuth2Authorization,调用 OAuth2AuthorizationService 保存。

3.2.2. 用授权码获取 token

1.请求响应格式说明

请求

名称 说明
url /oauth2/token
Method POST
Content-Type application/x-www-form-urlencoded
Authorization Basic 值 值为 base64 [客户端 ID]:[客户端密码]

请求参数

名称 说明
grant_type 授权类型 固定为 authorization_code
code 授权码的值

响应

名称 说明
access_token 访问 token
refresh_token 刷新 token
scope 申请的 scope
token_type token 的类型
expires_in 访问 token 有效期,单位为秒

2.postman 测试

oauth2 spring-authorization-server 分析_第19张图片

3.请求序列图 

oauth2 spring-authorization-server 分析_第20张图片

客户端发起请求/oauth2/token

<1>对客户端信息进行认证

/oauth2/token接口需要客户端认证通过才能访问,OAuth2ClientAuthenticationFilter(客户端认证过滤器)拦截请求,调用OAuth2ClientAuthenticationProvider(客户端认证管理器的提供者)对传入的client_idclient_secret进行判断。登录认证成功后,设置认证成功的结果(OAuth2ClientAuthenticationToken,内含客户端信息的RegisteredClient结果)到SecurityContext中。然后跳到下一步的过滤器中。

<2>对客户端信息进行二次认证

过滤器OAuth2TokenEndpointFilter继续拦截此请求,然后在颁发 Token 之前,请求OAuth2AuthorizationCodeAuthenticationProvider(授权码凭据许可认证管理器的提供者)的authenticate()方法进行第二次认证。因为第一步的OAuth2ClientAuthenticationFilter已经校验过client_secret,这里主要对 client 的授权模式是否吻合,以及 scope 的授权范围进行校验就通过了。

OAuth2TokenEndpointFilter中,调用 OAuth2XxxxAuthenticationProvider 的 authenticate() 方法进行认证是固定流程。实际会根据不同的grant_type去选择调用不同的 OAuth2XxxxAuthenticationProvider 进行认证。因此,对客户端信息进行了二次认证,第一次和第二次的认证的职责是不同的。

<3>创建访问 Token

<3.1>开始颁发 Token:增强 Token 中包含的信息

首先是要确定 Token 中包含的信息。在OAuth2AuthorizationCodeAuthenticationProvider中通过 OAuth2TokenCustomizer 定制 token 相关信息(定制 JwtEncodingContext 对象的 Claims 属性)

<3.2>开始颁发 Token:修改 tokenValue 值为 uuid 格式

然后JwtEncoder 根据定制之后的JwtEncodingContext,生成 jwtAccessToken,这里的 jwtAccessToken 可以认为只是一个以 jwt 为容器存储了 token 的不同属性信息的候选 AccessToken

<3.3>开始颁发 Token:正式颁发 OAuth2AccessToken

之后,真正颁发 AccessToken。通过 jwtAccessToken 构建 OAuth2AccessToken 对象,使用了 jwtAccessToken 的 tokenValue 值(该值是 UUID)。OAuth2AccessToken 才是真正要颁发的 AccessToken 对象。

<4>创建刷新 Token

如果客户端支持刷新 Token,则会创建OAuth2RefreshToken

<5>持久化 Token 及认证过程中的所有信息

为了在颁发 Token 后,能对 Token 进行验证其合法性,以及返回 Token 包含的信息,因此需要对 Token 及其相关的信息进行持久化。

结合OAuth2AccessTokenjwtAccessToken这两个对象,把它们中相关的属性抽出构建为OAuth2Authorization对象(最终的这个对象包含了客户端信息、token 信息、GrantType 信息、authorizedScopes 信息,token 中的所有 Claims 信息的集合),然后通过接口OAuth2AuthorizationService保存OAuth2Authorization对象,因为这个对象包含的信息最完整,这里保存下来后便于后续对 Token 进行校验。

<6>返回响应信息

最终,OAuth2AuthorizationCodeAuthenticationProvider返回新 New 的OAuth2AccessTokenAuthenticationToken对象到OAuth2TokenEndpointFilter,这个 Filter 过滤器处理转换为返回给前端的响应OAuth2AccessTokenResponse

OAuth2AccessTokenResponse 对象

3.2.3. 刷新 token

1.请求响应格式说明

请求

名称 说明
url /oauth2/token
Method POST
Content-Type application/x-www-form-urlencoded
Authorization Basic 值 值为 base64 [客户端 ID]:[客户端密码]

请求参数

名称 说明
grant_type 授权类型 固定为 refresh_token
refresh_token 刷新 refresh_token 的值

响应

名称 说明
access_token 访问 token
refresh_token 刷新 token
scope 申请的 scope
token_type token 的类型
expires_in 访问 token 有效期,单位为秒

2.postman 测试

oauth2 spring-authorization-server 分析_第21张图片

3.请求时序图

oauth2 spring-authorization-server 分析_第22张图片

请求/oauth2/token?grant_type=refresh_token

<1>/oauth2/token 接口需要登录才能访问,OAuth2ClientAuthenticationFilter(客户端认证过滤器)拦截请求,调用 OAuth2ClientAuthenticationProvider(客户端认证提供者)对传入的 clientId 和 clientSeceret 进行判断。登录认证成功后,设置认证对象到 SecurityContext 中

<2>登录成功后,OAuth2TokenEndpointFilter拦截地址/oauth2/token,请求 OAuth2RefreshTokenAuthenticationProvider(刷新 Token 认证提供者)

<3>OAuth2RefreshTokenAuthenticationProvider 根据 refresh_token,通过 OAuth2AuthorizationService 找到 OAuth2Authorization 对象

<4>OAuth2RefreshTokenAuthenticationProvider中生成新的访问 Token 设置到OAuth2Authorization对象中,生成访问 token 的处理过程和获取访问 token 时处理逻辑一致

<5>OAuth2TokenEndpointFilter 将返回的 OAuth2AccessTokenAuthenticationToken处理转换为返回给前端的响应OAuth2AccessTokenResponse

3.2.4. 校验 token,访问需要 API 权限的接口

同客户端模式,区别就是第 5 和第 6 步中的对象属性不一样

<5>OAuth2TokenIntrospection 对象

3.2.5. 注销 token

同客户端模式

3.3. 资源拥有者凭据许可

从“资源拥有者凭据许可”这个命名上,你可能就已经理解它的含义了。没错,资源拥有者的凭据,就是用户的凭据,就是用户名和密码。可见,这是最糟糕的一种方式。那为什么 OAuth 2.0 还支持这种许可类型,而且编入了 OAuth 2.0 的规范呢?

我们先来思考一下。正如上面我提到的,小兔此时就是京东官方出品的一款软件,小明也是京东的用户,那么小明其实是可以使用用户名和密码来直接使用小兔这款软件的。原因很简单,那就是这里不再有“第三方”的概念了。

但是呢,如果每次小兔都是拿着小明的用户名和密码来通过调用 Web API 的方式,来访问小明店铺的订单数据,甚至还有商品信息等,在调用这么多 API 的情况下,无疑增加了用户名和密码等敏感信息的攻击面。

如果是使用了 token 来代替这些“满天飞”的敏感信息,不就能很大程度上保护敏感信息数据了吗?这样,小兔软件只需要使用一次用户名和密码数据来换回一个 token,进而通过 token 来访问小明店铺的数据,以后就不会再使用用户名和密码了。

接下来,我们一起看下这种许可类型的流程,如下图所示:

下图的“第三方”实际上应为“第一方”

oauth2 spring-authorization-server 分析_第23张图片

3.3.1. 获取访问 token

1.请求响应说明

请求格式

名称 说明
url /oauth2/token
Method POST
Content-Type application/x-www-form-urlencoded
Authorization 格式为:Basic 。 为 base64 编码的 {client_id}:{client_secret}。

请求参数

名称 说明
grant_type 授权许可类型。 固定为 password
scope 可选。申请的权限范围
username 用户名 必须
password 用户密码 必须

响应

名称 说明
access_token 访问 token
refresh_token 刷新 token
scope 申请的 scope
token_type token 的类型
expires_in 访问 token 有效期,单位为秒

2.postman 测试

oauth2 spring-authorization-server 分析_第24张图片

请求

POST /oauth2/token?username=guest&password=guest&grant_type=password&scope=test1 test2 HTTP/1.1
Host: localhost:8080
Authorization: Basic Y2xpZW50OjEyMzQ1Ng==

响应

{
  "access_token": "747558ce-e130-4efe-b73e-7a0f5789a571",
  "refresh_token": "e6DEmAQt4-1t-BrlqHz1mCtyne2ujD5drpwk_ohSQqqnaEiEgJq",
  "scope": "test2 test1",
  "token_type": "Bearer",
  "expires_in": 7200
}

3.请求序列图

oauth2 spring-authorization-server 分析_第25张图片

核心流程和授权码基本一致,不同之处如下:

1.OAuth2ResourceOwnerPasswordAuthenticationProvider中会调用AuthenticationManager验证用户名密码是否正确。

2.在授权码的第<5>步中持久化 Token 及认证过程中的所有信息 OAuth2Authorization 对象属性值不一样

3.3.2. 刷新 token

同授权码处理模式基本一样

3.3.3. 校验 token,访问需要 API 权限的接口

同授权码处理模式基本一样

3.3.4. 注销 token

同授权码处理模式基本一样

6. 附录

Spring Security OAuth

https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide

Announcing the Spring Authorization Server

https://github.com/spring-projects-experimental/spring-authorization-server

oauth2.0的授权流程详解 - charlyFeng - 博客园

你可能感兴趣的:(SpringBoot,SpringSecurity,spring,boot,oauth2,spring,security)