OAuth 2.1 框架


OAuth 2.1 Draft

当前版本:v2-1-05
失效时间:2022/09/08

本文对部分原文翻译,同时加了一些笔记,以便理解。

单词 译意
identifiler 识别码
Resource Owner 资源拥有者
User-Agent 用户代理
Authorization Code 授权码
Access Token 访问令牌
refresh token 刷新令牌
scope 可选
endpoint 端点
AS 授权服务器

许可类型

要获取访问令牌,客户端需要从资源拥有者那里获得授权。本规约定义了以下几种授权许可类型。

  • 授权码(authorization code)
  • 客户端证书(client credentials)
  • 刷新令牌(refresh token)

本规约还提供了扩展机制,以便定义其他许可类型。

授权码许可

授权码许可类型用于获取访问令牌和刷新令牌。

许可类型使用额外的授权端点,实现授权服务器与资源拥有者交互,以便获取资源访问准许。

由于这是一个基于重定向的工作流,客户端必须能够通过资源拥有者(比如某个用户)的用户代理(一般指 web 浏览器)初始化工作流,并且能够从授权服务器重定向回来。

授权码流程图

OAuth 2.1 框架

注意:图中所示的步骤(1)、(2)、(3)在通过用户代理的时候会分为两个部分。

图中包括的步骤如下:

(1)客户端通过将资源拥有者的用户代理指向授权端点来发起授权流程。请求携带客户端自己的识别码、code challenge(来自生成的 code verifier)、请求范围(可选)、local state(可选,这里的意思是可以传递一些客户端的数据,回调的时候会把这些数据原样传回来)、回调 URI,当授权服务器许可(或拒绝)的时候会向该 URI 发送用户代理。

关于 code verifier,可查看下一节 [[#授权请求]] 

(2)授权服务器认证资源拥有者的身份(通过用户代理),并确定资源拥有者是许可还是拒绝客户端的访问请求。

(3)假设资源拥有者许可访问,则授权服务器通过之前(在发起请求时或者客户端注册期间)提供的重定向 URI 将用户代理重定向回客户端。重定向 URI 里包括授权码和客户端之前提供的任何 local state。

(4)客户端从授权服务器的令牌端点请求访问令牌,请求中需要携带上个步骤中获取的授权码、以及客户端自己的 code verifier。当发起请求时,如果授权服务器可以认证身份,客户端将通过授权服务器认证身份。客户端为了验证,将携带重定向 URI 以便获取授权码。

(5)授权服务器尽可能的认证客户端的身份,验证授权码、code verifier,并且保证接收到的 URI 与步骤(3)中重定向到客户端的 URI 是匹配的。如果通过认证,授权服务器返回反问令牌,以及刷新令牌(可选)。

授权请求

要发起授权请求,客户端需要将参数添加到授权服务器的授权端点 URI 上,以此构建授权请求 URI。客户端最终会将用户代理重定向到此 URI 上来发起请求。

这里看上去不是很好理解,我提供一个示例 ``` java 	private final String authorizationRequestUri = UriComponentsBuilder   	  //授权服务端点 URI       .fromPath("/oauth2/authorize")  	  //参数       .queryParam("response_type", "code")        .queryParam("client_id", "messaging-client")         .queryParam("scope", "openid message.read message.write")         .queryParam("state", "state")         .queryParam("redirect_uri", this.redirectUri)         .toUriString(); 

客户端每次发起授权请求都使用唯一的密钥,以此避免授权码注入,CSRF 攻击。客户端先生成此密钥,它可以在使用授权码时使用它来证明使用授权码的客户端就是请求它的客户端。

客户端通过 application/x-www-form-urlencoded 格式,添加以下参数到授权端点 URI 的查询组件中,构造客户端的请求 URI。

参数 是否必填 说明
response_type 授权端点支持不同的请求集合和响应数据。客户端根据 response_type 的值来决定授权流程。本规约定义了值的代码,该代码必须用于指示客户端要使用授权码流程。

"response_type":必填。授权端点支持不同的请求集合和响应数据。客户端根据 response_type 的值来决定授权流程。本规约定义了值的代码,该代码必须用于指示客户端要使用授权码流程。

扩展的响应类型可能是包含空格分隔符(%x20)的列表,这些响应类型的值在列表中顺序不会产生影响(例如,响应类型 a b 等同于 b a)。这类组合响应类型的含义有它们各自的规范定义。

某些扩展响应类型由 OpenID 定义。

如果授权请求缺少 response_type 参数,或者如果响应类型无法理解,授权服务器必须返回错误响应。

参数 是否必填 说明
client_id 客户端识别码
code_challenge 是或推荐 Code challenge
code_challenge_method 可选 默认值 plain,Code verifier 转换方法为 S256 或 plain
redirect_uri 可选
scope 可选
state 可选 客户端用于维护请求与回调之间的状态。授权服务器在重定向用户代理回客户端时将此值加入请求中

code_verifier 是唯一的熵很高的加密随机字符串,每次授权请求生成一次,使用 unreserved 字符包括 [A-Z]、[a-z]、[0-9]、“-”、“.”、“ _ ”、“~”,最小字符串长度为 43,最大字符长度 128。

1948年,香农Claude E. Shannon引入信息(熵),将其定义为离散随机事件的出现概率。一个系统越是有序,信息熵就越低;反之,一个系统越是混乱,信息熵就越高。所以说,信息熵可以被认为是系统有序化程度的一个度量。 

客户端临时存储 code_verifier,计算用于授权请求的 code_challenge。

用于 code_verifier 的 ABNF(巴科斯范式)如下。

code-verifier = 43 * 128unreserved unreserved = ALPHA / DIGIT / "-" / "." / " _ " / "~" ALPHA = %x41-5A / %x61-7A DIGIT = %x30-39 

注意:code verifier 的熵应该足够高,以至于值不会被猜到。建议使用合适的随机数生成器来创建一个 32 octet 的序列。每个 octect 序列使用 base64url 编码后生成一个 43 octet 的 URL 安全的字符串作为 code verifier。

1 octet = 8 bits  为什么不使用 byte,因为 byte 的语义存在歧义,历史上的 byte 不是固定的 8 位。 

客户端然后在 code verifier 的基础上创建 code_challenge:

S256
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

plain
code_challenge = code_verifier

如果客户端能够使用 S256,则必须使用 S256,因为服务器上强制执行(Mandatory To Implement,MTI) S256。客户端只能在由于某些技术原因不支持 S256 的情况下,才能使用 pain,例如,受环境限制不能使用哈希函数,并且通过带外配置或者授权服务器元数据得知服务器支持 plain。

用于 code_challenge 的 ABNF(巴科斯范式)如下。

code-challenge = 43 * 128unreserved unreserved = ALPHA / DIGIT / "-" / "." / " _ " / "~" ALPHA = %x41-5A / %x61-7A DIGIT = %x30-39 

code_challenge 和 code_verifier 的属性吸取了 OAuth 2.0 的扩展”Proof-Key for Code Exchange“,也叫做 PKCE,也是这项技术的起源地。

授权服务器必须支持 code_challenge 和 code_verifier 参数。

客户度必须使用 code_challenge 和 code_verifier,除了一些在 7.6 节 中描述的条件外,服务器也必须强制客户端使用 code_challenge 和 code_verifier。在当前情况下,我们仍然推荐按照下面列出的方式强制使用 code_challenge 和 code_verifier。

state 和 scope 参数不应该在 plain 文本中包含和客户端、资源拥有者相关的敏感信息,因为它们能够通过不安全的通道传输,或者以不安全的方式存储。

客户端通过 HTTP 重定向或者用户代理提供的其他方式,指示资源拥有者构造 URI。

例如,客户端指示用户代理发起以下 HTTP 请求(额外的断行符只是为了显示的目的):

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb        &code_challenge=6fdkQaPm51l13DSukcAH3Mdx7_ntecHYd1vi3n0hMZY        &code_challenge_method=S256 HTTP/1.1    Host: server.example.com 

授权服务器验证请求,以确保所有的必须的参数都有效。

特别地,如果请求中存在 redirect_uri,授权服务器必须验证,确保其值与客户端注册的 URI 匹配。当比较两个 URI 时,授权服务器必须一个字符一个字符的比较。

如果请求有效,授权服务器认证资源拥有者的身份,并且获取授权决策(通过询问资源拥有者或者通过其他方式建立批准)。

这种获取授权决策,具体体现,比如:使用微信登录其他 App 时,会跳转到微信 App,询问用户是否允许。 

当完成决策后,授权服务器指示用户代理使用 HTTP 重定向响应或用户代理提供的其他方式,提供客户端重定向 URI。

授权响应

如果资源拥有者许可访问请求,授权服务器将颁发一个授权码并传送给客户端,使用 application/x-www-form-urlencoded 格式,在重定向 URI 查询组件中添加以下参数:

参数 是否必填 说明
code 由授权服务器生成的授权码,并且对客户端不透明。授权码在颁发后,必须在短期内失效,以防泄漏。推荐授权码的生命周期为 10 分钟。客户端只能使用一次授权码。如果使用授权码超过一次,授权服务器必须拒绝请求,并且应该撤销(如果可能的话)基于上次颁发的授权码生成的所有访问令牌和刷新令牌。授权码与客户端识别码、code challenge、重定向 URI绑定。
state 如果客户端授权请求中包含 state 参数。确切值来自客户端。

例如,授权服务器通过发送以下 HTTP 响应重定向用户代理:

HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA              &state=xyz 

客户端必须忽略不能识别的响应参数。本规约未定义授权码字符串的大小。客户端应该避免假定授权码的大小。授权服务器应该记录颁发的授权码的大小。

服务器关联颁发的授权码与 code_challenge 的确切方法,超出了本规约的范围。code challenge 应该存储在服务器上,并且在服务器上关联授权码。code_challenge 和 code_challenge_method 的值可以以加密的方式存储在代码自身中,但是服务器不能在响应参数重包括 code_challenge 的值,只能提取 AS 以外的实体。

客户端必须防止攻击者注入授权码到授权响应中。使用 code_challenge 和 code_verifier 可以阻止注入授权码,原因是如果 code_verifier 不匹配,授权服务器将拒绝令牌请求。

错误响应

如果请求由于重定向 URI 缺失、无效、或者不匹配失败,或者如果客户端识别码缺失、无效,授权服务器应该通知资源拥有者错误,并且不能自动将用户代理重定向到错误的 URI。

AS 必须拒绝不携带 code_challenge 来自公共客户端的请求,并且必须拒绝来自其他客户端的这类请求,除非能保证客户端客户端不会以其他方式注入授权码。

如果服务器不支持请求的 code_challenge_method 转换方法,授权端点必须返回错误响应,并将 error 的值设为 invalid_request。error_description 或者 error_uri 应该解释错误的本质,比如,不支持的转换算法。

如果资源拥有者拒绝访问请求,或者如果请求失败是因为除了重定向 URI 缺失或失效之外的原因,授权服务器应该使用 application/x-www-form-urlencoded 格式,向重定向 URI 查询组件中添加以下参数:

参数 是否必填 描述 错误代码 错误代码描述
error 必填 错误参数不能包含特殊代码 %x20-21、%x23-5B、%x5D-7E。 invalid_request 请求缺少必须的参数,存在无效的参数值,同一个参数出现多次,个税不正确
unauthorized_client 使用此方式请求授权码时,客户端未被授权
access_denied 资源拥有者或者授权服务器拒绝请求
unsupported_response_type 授权服务器不支持使用此方法获取授权码
invalid_scope 请求范围无效、未知、或者格式错误
server_error 授权服务器遇到未知的情况,进而不能完成请求。(此错误代码时必须的,因为不能通过 HTTP 重定向,返回 500 错误到客户端)
temporarily_unavailable 由于授权服务器当前不能处理超载或维护状态,导致不能处理请求。(此错误代码时必须的,因为不能通过 HTTP 重定向,返回 503 状态到客户端)
error_description 可选 具备可读性的文本,提供额外的信息,当发生错误时,协助开发者理解错误。来自 error_description 的参数不能包含特殊字符 %x20-21、%x23-5B、%x5D-7E。
error_uri 可选 指定一个包含错误信息的 web 页面 URI,为客户端开发者提供和错误相关的额外信息。error_uri 的参数符合 URI 引用的语法,因此不能包含特殊字符 %x20-21、%x23-5B、%x5D-7E。
state 必填 如果客户端授权请求中存在 state 参数,则该参数是必填。该参数的值来自客户端。

例如,授权服务器通过发送以下请求重定向用户代理:

HTTP/1.1 302 Found Location: https://client.example.com/cb?error=access_denied&state=xyz 

令牌端点扩展

授权准许类型在令牌端点通过 authorization_code 的 grant_type 的值来识别。

如果设置此值。则需要设置以下额外令牌请求参数:

参数 是否必填 描述
code 必填 来自授权服务器的授权码
redirect_uri 必填 如果授权请求中有 redirect_uri 参数,如[[#授权请求]]中所述,在此情形下,它们的值必须相同。如果授权请求中没有 redirect_uri,此参数是可选的。
code_verifier 必填 如果授权请求中有 code_challenge 参数。绝对不能在其他地方使用。原始的 code verifier 字符串。

例如,客户端发起如下 HTTP 请求(包含用于显示目的地换行符):

POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded  grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb &code_verifier=3641a2d12d66101249cdf7a79c000c1f8c05d2aafcf14bf146497bed 

除了[[请求令牌]]的处理规则外,授权服务器必须

  • 确保授权码颁发给经过认证的受信任的客户端或者有凭据的客户端,如果是公开客户端,确保授权码颁发给请求中的 client_id。
  • 验证授权码的有效性
  • 验证 code_verifier 参数,当且仅当授权请求中存在 code_challenge 参数时。
  • 如果存在 code_verifier,则从接收到的 code_verifier 中计算 code challenge,并与之前关联的 code_challenge 经过客户端指定的 code_challenge_method 转换后进行比较,以此验证 code_verifier。
  • 确保包含 redirect_uri 参数,如果 redirect_uri 如[[#授权请求]]中描述的那样包含在初始授权请求中,如果存在,则要确保它们的值是相同的。

客户端证书许可

当客户端在其控制范围内请求访问受保护的资源时,客户端能够只使用它的客户端证书请求访问令牌(或者其他支持的认证方式),或者另一个资源拥有者提前安排了授权服务器(处理方式超出了此规约的范围)。

客户端证书许可类型必须只能被受信任的客户端或者有凭据的客户端使用。

OAuth 2.1 框架

客户端证书许可包含以下步骤:
(1)通过授权服务器做客户端身份认证,从令牌端点请求访问令牌。
(2)授权服务器认证客户端身份,如果有效,颁发访问令牌。

令牌端点扩展

授权许可类型通过 grant_type 的值 client_credentials 识别令牌终端。

如果设置了此值,则不需要[[#令牌请求]]之外参数:

例如,客户端发起如下 HTTP 请求:

POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded  grant_type=client_credentials 

授权服务器必须认证客户端的身份。

刷新令牌许可

刷新令牌是授权服务器颁发给客户端的凭证,可以用它基于现有的许可方式获取新的(刷新)访问令牌。客户端使用此选项,要么因为上一个访问令牌过期,要么因为上次获取的访问令牌 scope 比各自通过的许可窄,并且在相同的许可下请求 scope 不同的访问令牌。

刷新令牌必须安全保存,只在授权服务器和颁发刷新令牌的客户端之间共享。授权服务器必须在刷新令牌和颁发给的客户端之间维护绑定关系。

当客户端身份被认证时,授权服务器必须验证刷新令牌和客户端身份的绑定关系。当客户端无法认证身份时,授权服务器应该颁发 sender-constrained 刷新令牌或者使用刷新令牌反转(参见 [[#刷新访问令牌]])。

授权服务器必须确保刷新令牌不能被未授权的第三方生成、修改、或者猜着生成有效的刷新令牌。

发表评论

相关文章