入门须知

这里用一些通俗的语言,描述OAuth 2.1中的一些基本概念。

①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖

[TOC]

1. OAuth 2.1OAuth 2.0的区别

OAuth 2.0 定义了四种授权方式

  • 密码模式(resource owner password credentials) --OAuth2.1 删除了该模式
  • 授权码模式(authorization code)
  • 简化模式(implicit)--OAuth2.1 删除了该模式
  • 客户端模式(client credentials)

OAuth2.1 中文文档

2. Authentication API

这里参考了auth0 的文档

3. Authorization API

3.1 申请授权 code

① Authorization Code Flow

  • GET /oauth2/authorize
ParameterRequiredDescription
response_type必须您要执行的 OAuth 2.1 流程,code=Authorization Code
client_id必须您的应用程序的 ID
redirect_uri必须回调的地址,如果不填写,会报错,并且填写的内容与设置的一致
state推荐应用程序添加的一个数值,回调的时候会原封不动的返回。可以判断是不是服务器返回的。
scope可选请求的范围

提示

  • 这里不需要客户端密码。
  • redirect_uri 在标准文档中是可选的,但是这里是必须填写的

② Authorization Code Flow with PKCE

  • GET /oauth2/authorize

在开始此流程之前,您需要随机生成一个 32 位长code_verifier,并存储在本地。然后运用hash算法s256加密得到一个code_challenge

第一步:先随机生成一个 32 位长的code_verifier,然后运用hash算法s256加密得到一个code_challenge。客户端向认证服务器申请认证码code, 要带着code_challenge

ParameterRequiredDescription
response_type必须您要执行的 OAuth 2.1 流程,code=Authorization Code ,这个可以使用PKCE
client_id必须您的应用程序的 ID
redirect_uri必须回调的地址,如果不填写,会报错,并且填写的内容与设置的一致
state推荐应用程序添加的一个数值,回调的时候会原封不动的返回。可以判断是不是服务器返回的。
code_challenge必须code_verifier运用hash算法s256加密得到一个code_challenge
code_challenge_method必须必须填写:S256 ,不然 Spring 提示错误
scope可选请求的范围

提示

  • 这里不需要客户端密码。
  • redirect_uri 在标准文档中是可选的,但是这里是必须填写的

3.2 设备授权

  • POST /oauth/device/code

这是输入受限设备用于访问 API 的流程。使用此端点获取设备代码。

ParameterRequiredDescription
client_id必须您的应用程序的 ID
scope可选请求的范围

Response Values

ValueDescription
device_codeThe unique code for the device. When the user visits the verification_uri in their browser-based device, this code will be bound to their session.
user_codeThe code that the user should input at the verification_uri to authorize the device.
verification_uriThe URL the user should visit to authorize the device.
verification_uri_completeThe complete URL the user should visit to authorize the device. Your app can use this value to embed the user_code in the URL, if you so choose.
expires_inThe lifetime (in seconds) of the device_code and user_code.
intervalThe interval (in seconds) at which the app should poll the token URL to request a token.

3.3 获取 Token

① Authorization Code Flow

POST /oauth2/token

ParameterRequiredDescription
grant_type必须表示您正在使用的流程。对于 Authorization Code,请使用 authorization_code
client_id必须您的应用程序的 ID
client_secret必须您应用的客户端密码。
redirect_uri必须必须与得到 code 时的一致
code必须从初始 /authorize 调用收到的授权 Code。

② Authorization Code Flow with PKCE

  • POST /oauth2/token
ParameterRequiredDescription
grant_type必须表示您正在使用的流程。对于 Authorization Code,请使用 authorization_code
client_id必须您的应用程序的 ID
client_secret必须您应用的客户端密码。
redirect_uri必须必须与得到 code 时的一致
code必须从初始 /authorize 调用收到的授权 Code。

备注:

  • 在这种模式下是不需要 client_secret 的

③ Client Credentials Flow

  • POST /oauth/token
ParameterRequiredDescription
grant_type必须表示您正在使用的流程。对于 Client Credentials,请使用 client_credentials
client_id必须您的应用程序的 ID
client_secret必须您应用的客户端密码。
scope可选请求的范围,如果选择 scope,名字就不能写错了

④ Device Authorization Flow

  • POST /oauth/token

按照系统返回的时间间隔,来轮询这个端点,一致最后得到 Access Token

ParameterRequiredDescription
grant_type必须表示您正在使用的流程。对于 Device Authorization,请使用 urn:ietf:params:oauth:grant-type:device_code
client_id必须您的应用程序的 ID
device_code必须先前从 /oauth2/device/code 端点返回的 device code。

⑤ Refresh Token

  • POST /oauth/token
ParameterRequiredDescription
grant_type必须表示您正在使用的流程。对于刷新令牌,请使用 refresh_token
client_id必须您的应用程序的 ID
client_secret必须您应用的客户端密码。当您的应用程序设置中的令牌端点身份验证方法字段为 Post 或 Basic 时需要。
refresh_token必须要使用的刷新令牌。
scope可选以空格分隔的请求范围权限列表。如果未发送,将使用原始范围;否则,您可以请求减少一组范围。

⑥ Token Exchange for Native Social

  • POST /oauth/token

这个有待研究

3.4 撤销 Token

  • POST /oauth/revoke

如果刷新令牌已被泄露,请使用此端点使刷新令牌无效。

ParameterRequiredDescription
client_id必须您的应用程序的 ID
client_secret可选您应用的客户端密码。
token必须您要撤销的刷新令牌。

这个流程需要研究一下

4. 应该使用哪个 OAuth 2.1 流程?

决定使用那个流程,跟你的应用程序类型有主要的关系。同时有些参数要考虑,例如 client 端的信任程度,或您希望用户拥有的体验。这里参考了一个网址

这里假设你对OAuth 2.1中的属于已经很了解:Resource Owner ; Client ; Resource Server ; Authorization Server;User Agent; ,并且知道OAuth 2.0中的一些流程在OAuth 2.1中已经取消,并且OAuth 2.1添加了一些新的安全措施。

4.1 客户端是资源所有者吗?

Is the Client the Resource Owner?

客户端应用程序是资源所有者吗?可以通过下面几个条件才判断:

  • 访问资源服务器的是否是一台机器?
    • 在机器对机器授权的情况下,客户端也是资源所有者,因此不需要最终用户授权。
    • 例如一个定时程序,需要通过API将输入写入数据库。定时程序是ClientResource Owner ,所以它可以通过Client IDClient Secret从授权服务器获取Access Token

在这种模式下,可以使用:client_credentials 流程。

4.2 客户端是在服务器上执行的 Web 应用程序吗?

Is the Client a web app executing on the server?

如果客户端是在服务器上执行的常规 Web 应用程序,那么authorization_code流程就是您应该使用的流程。

使用authorization_code,客户端可以得到Access Token,同时可选择获取一个Refresh Token。它被认为是最安全的选择,因为Access Token直接传递到托管客户端的 Web 服务器,而无需通过用户的 Web 浏览器和暴露风险。

在这种模式下,可以使用:authorization_code 流程。

4.3 客户端是单页 Web 程序吗?

Is the Client a Single-Page App?

如果客户端是单页应用程序 (SPA),即使用 JavaScript 等脚本语言在浏览器中运行的应用程序,则有两个授权选项:

  • 使用Proof Key for Code Exchange (PKCE) 安全限制的authorization_code 流程。
  • 在表单中使用Implicit流程。但是OAuth2.1 这种流程被删除了。

对于大多数情况,我们建议使用带有 PKCEauthorization_code流程,因为Access Token不会在客户端公开(对这句话存疑,可以参考 PKCE 流程),并且此流程可以返回 Refresh Tokens

要了解有关此流程如何工作以及如何实施的更多信息,请参阅Authorization Code Flow with Proof Key for Code Exchange (PKCE).

有些程序提供了基于前端的 SDK,例如 Auth0 Single-Page App SDK ,为在 SPA 中使用 PKCE 实现授权代码流提供高级 API。

在这种模式下,可以使用:authorization_code 流程+PKCE

4.4 客户端是原生的本地/移动应用程序吗?

Is the Client a Native/Mobile App?

在这种模式下,可以使用:authorization_code 流程+PKCE

4.5 我的应用程序需要访问多个 resource servers

I have an application that needs to talk to different resource servers

每个授权将使用不同的 audience,这将在流程结束时产生不同的访问令牌。有关详细信息,请参阅 OAuth 2.0: Audience Information Specification.

4.6 在开发客户端前,可以提前测试授权服务器的端点吗?

可以,但是我要提前了解一些 spring 提供了那些默认的端点。

下面有两个其他系统的文档,可以参考一下

  • For the Authorize endpoint, go to Authorize Application and read the "Test this endpoint" paragraph for the grant you want to test.
  • For the Token endpoint, go to Get Token and read the "Test this endpoint" section for the grant you want to test.

5. 授权与认证流程

参考了

5.1 Authorization Code+PKCE

Authorization Code Flow with Proof Key for Code Exchange (PKCE)

① 当前存在的风险

如果您的应用程序无法安全地存储客户端认证密码,那么这就是您的授权类型。

当公共客户端(例如,原生和单页 Web 应用程序)请求访问令牌时,如果单独使用Authorization Code会有一些安全问题,例如:

  • Native apps
    • 客户端密码无法安全保存。 如果反编译源代码会得到登陆密码,它绑定到应用程序,对所有用户和设备都是相同的。
    • 拦截授权服务器重定向redirects uri返回的code
  • Single-page apps
    • 无法安全地存储客户端密钥,因为在浏览器中可以看到所有的源代码。

鉴于这些情况,OAuth 2.0 提供了一个授权代码流版本,它使用Proof Key for Code Exchange (PKCE) (在 OAuth 2.0 RFC 7636 中定义)。

PKCE的原理

PKCE增强了Authorization Code流程,流引入了一个由调用应用程序创建的密钥,该密钥可由授权服务器验证;

  • Code Verifier:上述密钥被称为Code Verifier
  • Code Challenge:调用应用程通过对Code Verifier进行转换得到的一个值叫Code Challenge,这个数值会在请求 code 的时候,通过HTTPS一起发送给授权服务器。

这样一来,恶意攻击者只能截获授权码,而没有验证码他们无法将其换成令牌。

③ 如何工作?

因为 PKCE 增强的授权代码流建立在标准授权代码流之上,所以步骤非常相似。

用户点击登陆

创建一个随机加密的 code_verifier,并由code_verifier生成一个 code_challenge

重定向到授权服务器,同时将code_challenge也给传递过去。

授权服务器将用户重定向到登录和授权提示。

用户使用配置的登录选项之一进行身份验证,并且可能会看到一个同意页面,其中列出了将授予应用程序的权限。

授权服务器存储 code_challenge 并将授权 code通过重定向返回给用户的应用程序。

codecode_verifier(在步骤 2 中创建)发送到授权服务器

授权服务器验证 code_challengecode_verifier

授权服务器返回 ID TokenAccess Token(以及可选的Refresh Token)。

您的应用程序可以使用Access Token调用 API 以访问有关用户的信息。

④ 如何实施?

简单来说就是获取 Token 过程中,利用两个数值比较来验证请求者是否是最终用户,防止攻击者盗用 Token。 它提高了公有云环境下的单页面运用或 APP 获取 Token 的安全性,也能应用在私有云环境里。我们可利用它来实现 SSO。

具体步骤简单描述如下:

第一步:先随机生成一个 32 位长的code_verifier,然后运用hash算法s256加密得到一个code_challenge。js 算法实现可参考这里

第二步:客户端向 oauth2 认证服务器申请认证码 code, 并带着 code_challenge;

第三步:客户端利用返回的 code 和 code_verifier 来向 oauth2 服务器申请 token;

第四步:客户端通过在 header 里加 bearer token 就能访问受限资源了;

可以参考相关的案例

⑤ SpringBoot 中的 PKCE 算法

参考文档

  • 如何通过 PKCE 模式访问受 Oauth2 保护的资源

    • CodeChallengeMethod类用来codeVerifier和codeChallenge比较
      PkceAuthorizationCodeServices类用于调度
      PkceAuthorizationCodeTokenGranter类用于从request请求中获取token所需信息
      PkceProtectedAuthentication类定义PKCE认证对象信息
    • 全局跨域访问和 Token 调用设置

6. 设备授权流程

参考网址:

一个受限的设备连接到网络时,不是直接对用户进行身份验证,设备会要求用户转到其计算机或智能手机上的链接并授权设备。这避免了无法轻松输入文本的设备的糟糕用户体验。为此,设备应用程序使用设备授权流程 (ratified in OAuth 2.0),在其中传递 Client ID 用来启动授权过程并获取 token。

6.1 How it works

设备授权流程包含两条不同的路径:一个发生在请求授权的设备上,另一个发生在浏览器中。浏览器路径,其中 device code 绑定到浏览器中的 session,与设备路径的一部分平行发生。

Device Flow

用户在设备上启动应用程序。

设备应用程序使用其 Client ID 从授权服务器请求授权(/oauth2/device/code 端点)。

授权服务器响应device_code, user_code, verification_uri, verification_uri_complete , expires_indevice_codeuser_code 的生命周期以秒为单位),和轮询间隔。

设备应用程序要求用户使用他们的计算机或智能手机进行激活。该应用程序可以通过以下方式完成此操作:

  • 在屏幕上显示这些值后,要求用户访问 verify_uri 并输入 user_code
  • 要求用户使用从 verify_uri_complete 生成的内嵌user code与 二维码或短 URL 进行交互。
  • 如果设备中有浏览器,则使用 verify_uri_complete 直接导航到带有user code的验证页面。

设备应用程序根据指定的时间间隔 interval ,开始轮询您的授权服务器以获取访问令牌(/oauth2/token 端点)。设备应用程序会继续轮询,直到用户完成浏览器上的确认操作或 user code 过期。

判断用户是否完成操作。在等待的过程。。。。。。

用户完成浏览器上的确认操作后,授权服务器返回 Access Token(以及可选的 Refresh Token),设备应用程序现在应该忘记它的 device_code,因为它会过期。

您的设备应用可以使用 Access Token 调用 API 以访问有关用户的信息。

API 返回请求的数据。

Browser Flow

用户在其计算机上访问verify_uri,输入 user_code 并确认正在激活的设备正在显示 user_code。如果用户通过任何其他方法访问verification_uri_complete(例如通过扫描二维码),只需要设备确认。

如果需要,授权服务器会将用户重定向到登录和同意提示。

用户使用配置的登录选项之一进行身份验证,并且可能会看到要求授权设备应用程序的同意页面。

您的设备应用程序已获得访问 API 的授权。

6.2 模拟例子

参考了这里

Get user code

curl --request POST \
--url 'https://YOUR_DOMAIN/oauth/device/code' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'client_id=YOUR_CLIENT_ID' \
--data scope=SCOPE \
--data audience=AUDIENCE

Device code response

{
"device_code": "Ag_EE...ko1p",
"user_code": "QTZL-MCBW",
"verification_uri": "https://accounts.acmetest.org/activate",
"verification_uri_complete": "https://accounts.acmetest.org/activate?user_code=QTZL-MCBW",
"expires_in": 900,
"interval": 5
}

device_code 不直接用于用户,不应在交互过程中显示以避免混淆用户。

Once you received a device code and use code,可以在设备上显示这么一个提示

Request tokens

curl --request POST \
--url 'https://YOUR_DOMAIN/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=urn:ietf:params:oauth:grant-type:device_code \
--data device_code=YOUR_DEVICE_CODE \
--data 'client_id=YOUR_CLIENT_ID'

grant_type 将此设置为“urn:ietf:params:oauth:grant-type:device_code”。这是扩展授权类型(由 RFC6749 的第 4.5 节定义)请注意,这必须是 URL 编码的。

收到下面,就继续轮询

{
"error": "authorization_pending",
"error_description": "..."
}

放慢访问速度

{
"error": "slow_down",
"error_description": "..."
}

过期

{
"error": "expired_token",
"error_description": "..."
}

拒绝

{
"error": "access_denied",
"error_description": "..."
}

Authorize user

输入用户编号

确认设备

用户将通过登录完成交易。

Receive tokens

当用户一直在对设备进行身份验证和授权时,设备应用程序会继续轮询令牌 URL 以请求访问令牌。

{
"access_token": "eyJz93a...k4laUWw",
"refresh_token": "GEbRxBN...edjnXbL",
"id_token": "eyJ0XAi...4faeEoQ",
"token_type": "Bearer",
"expires_in": 86400
}