|
: t: q9 L5 ?" `; o1 k d! A8 s! @0 D8 p
本文对部分原文翻译,同时加了一些笔记,以便理解单词译意identifiler识别码Resource Owner资源拥有者User-Agent用户代理Authorization Code授权码Access Token。
& b* Z) Z( n4 h, \# T6 [ 访问令牌refresh token刷新令牌scope可选endpoint端点AS授权服务器许可类型要获取访问令牌,客户端需要从资源拥有者那里获得授权本规约定义了以下几种授权许可类型授权码(authorization code)。
/ [$ z {; G" X5 e7 F6 [+ C( B8 t) D. B 客户端证书(client credentials)刷新令牌(refresh token)本规约还提供了扩展机制,以便定义其他许可类型授权码许可授权码许可类型用于获取访问令牌和刷新令牌许可类型使用额外的授权端点,实现授权服务器与资源拥有者交互,以便获取资源访问准许。 5 u I% y$ K6 ~$ o
由于这是一个基于重定向的工作流,客户端必须能够通过资源拥有者(比如某个用户)的用户代理(一般指 web 浏览器)初始化工作流,并且能够从授权服务器重定向回来。授权码流程图
! l, T2 g3 C) \% V* ~ 注意:图中所示的步骤(1)、(2)、(3)在通过用户代理的时候会分为两个部分图中包括的步骤如下:(1)客户端通过将资源拥有者的用户代理指向授权端点来发起授权流程请求携带客户端自己的识别码、code challenge(来自生成的 code verifier)、请求范围(可选)、local state(可选,这里的意思是可以传递一些客户端的数据,回调的时候会把这些数据原样传回来)、回调 URI,当授权服务器许可(或拒绝)的时候会向该 URI 发送用户代理。 " e4 z; ^# k: }
关于 code verifier,可查看下一节 [[#授权请求]](2)授权服务器认证资源拥有者的身份(通过用户代理),并确定资源拥有者是许可还是拒绝客户端的访问请求(3)假设资源拥有者许可访问,则授权服务器通过之前(在发起请求时或者客户端注册期间)提供的重定向 URI 将用户代理重定向回客户端。 4 |8 [5 Z, f; B
重定向 URI 里包括授权码和客户端之前提供的任何 local state(4)客户端从授权服务器的令牌端点请求访问令牌,请求中需要携带上个步骤中获取的授权码、以及客户端自己的 code verifier。 - ]: N* I& [6 F; a+ H$ P- T" u
当发起请求时,如果授权服务器可以认证身份,客户端将通过授权服务器认证身份客户端为了验证,将携带重定向 URI 以便获取授权码(5)授权服务器尽可能的认证客户端的身份,验证授权码、code verifier,并且保证接收到的 URI 与步骤(3)中重定向到客户端的 URI 是匹配的。
: s( b& n4 @: i7 l$ e) q* L9 Q 如果通过认证,授权服务器返回反问令牌,以及刷新令牌(可选)授权请求要发起授权请求,客户端需要将参数添加到授权服务器的授权端点 URI 上,以此构建授权请求 URI客户端最终会将用户代理重定向到此 URI 上来发起请求。 - i: U+ H; S" W4 Q$ [6 P H% d
这里看上去不是很好理解,我提供一个示例
& E9 p( e5 z8 v8 g- E' C ``` java8 g% c6 d! q X& ?) D. s
privatefinal String authorizationRequestUri = UriComponentsBuilder+ Y& U) E! X' P2 Z6 K( Q
# Z. T4 n6 a6 z
//授权服务端点 URI
" w. T: s- M2 C& G .fromPath("/oauth2/authorize")
: M; B7 e) h& b: y( W //参数
' D. B& d2 m+ y .queryParam("response_type", "code")2 U* F; o9 i" m1 Y
.queryParam(
8 v" b* @ _( Z "client_id", "messaging-client")/ P" G) A; [5 f( n& N
.queryParam("scope", "openid message.read message.write")
( Y$ F% P. A: I) u2 Y4 K .queryParam(
% `) R% Z8 ~4 I3 ` "state", "state")
: r1 y+ H: \9 Y7 A$ A3 V .queryParam("redirect_uri", this.redirectUri)
- N& j3 l" g3 H- c# j) x' K5 B .toUriString();" y2 Z; G' ~7 w3 O+ [: v/ y4 h
客户端每次发起授权请求都使用唯一的密钥,以此避免授权码注入,CSRF 攻击。 4 z% A7 R. }" c+ b. T
客户端先生成此密钥,它可以在使用授权码时使用它来证明使用授权码的客户端就是请求它的客户端客户端通过 application/x-www-form-urlencoded 格式,添加以下参数到授权端点 URI 的查询组件中,构造客户端的请求 URI。
2 |. Z: b3 M& `9 ]4 e% a 参数是否必填说明response_type是授权端点支持不同的请求集合和响应数据客户端根据 response_type 的值来决定授权流程本规约定义了值的代码,该代码必须用于指示客户端要使用授权码流程"response_type":必填。 ) C Y2 H9 o# o1 W; b) z l1 b+ C. {9 E
授权端点支持不同的请求集合和响应数据客户端根据 response_type 的值来决定授权流程本规约定义了值的代码,该代码必须用于指示客户端要使用授权码流程扩展的响应类型可能是包含空格分隔符(%x20)的列表,这些响应类型的值在列表中顺序不会产生影响(例如,响应类型 a b 等同于 b a)。 " d4 Z7 o3 @8 m+ @7 x8 H
这类组合响应类型的含义有它们各自的规范定义某些扩展响应类型由 OpenID 定义如果授权请求缺少 response_type 参数,或者如果响应类型无法理解,授权服务器必须返回错误响应参数是否必填说明client_id 9 v5 k1 x0 t2 S' U. E! R
是客户端识别码code_challenge是或推荐Code challengecode_challenge_method可选默认值 plain,Code verifier 转换方法为 S256 或 plain
0 c/ ~7 A, R6 c6 j6 D |) Z3 K redirect_uri可选scope可选state可选客户端用于维护请求与回调之间的状态授权服务器在重定向用户代理回客户端时将此值加入请求中code_verifier 是唯一的熵很高的加密随机字符串,每次授权请求生成一次,使用 unreserved 字符包括 [A-Z]、[a-z]、[0-9]、“-”、“.”、“ _ ”、“~”,最小字符串长度为 43,最大字符长度 128。 2 N& Z5 O% _6 {" A$ J+ j
1948年,香农ClaudeE.Shannon引入信息(熵),将其定义为离散随机事件的出现概率一个系统越是有序,信息熵就越低;反之,一个系统越是混乱,信息熵就越高所以说,信息熵可以被认为是系统有序化程度的一个度量。
9 k" J% n; }" @ 客户端临时存储 code_verifier,计算用于授权请求的 code_challenge用于 code_verifier 的 ABNF(巴科斯范式)如下code-verifier = 43 * 128
' N; s; s3 Z* \" [" @( q$ Y unreserved s5 j3 H6 c/ F% L
unreserved = ALPHA / DIGIT / "-" / "." / " _ " / "~"ALPHA = %x41-5A / %x61-7A% m9 [+ [% L) o4 p' n2 E; O/ f/ E
DIGIT = %x30- : o$ l' o# `# u' I3 e4 ]: k
39注意:code verifier 的熵应该足够高,以至于值不会被猜到建议使用合适的随机数生成器来创建一个 32 octet 的序列每个 octect 序列使用 base64url 编码后生成一个 43 octet 的 URL 安全的字符串作为 code verifier。 # ?% ]9 Q# U( H: m2 Z8 U
1 octet = 8 bits
. \2 F2 G- m N% ~3 y+ P s8 J: p3 B% f+ p6 w
为什么不使用 byte,因为 byte 的语义存在歧义,历史上的 byte 不是固定的 8 位
) r3 ~2 M- f3 i6 C2 [ 客户端然后在 code verifier 的基础上创建 code_challenge:。
8 ~, q, ]/ t6 }7 A( {9 q$ D* R S256code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))plaincode_challenge = code_verifier 6 L, A# ~8 T. q1 i
如果客户端能够使用 S256,则必须使用 S256,因为服务器上强制执行(Mandatory To Implement,MTI) S256客户端只能在由于某些技术原因不支持 S256 的情况下,才能使用 pain,例如,受环境限制不能使用哈希函数,并且通过带外配置或者授权服务器元数据得知服务器支持 plain。
6 ]& T* W5 L3 C' d! H# w$ p( [; s 用于 code_challenge 的 ABNF(巴科斯范式)如下code-challenge = 43 * 128unreserved
1 `) J; j, E$ A! c unreserved = ALPHA / DIGIT / "-"。 : x+ f& F% \+ R0 X& {5 V( }
/ "." / " _ " / "~"ALPHA = %x41-5A / %x61-7A1 F. v: h* H# h
DIGIT = %x30-39code_challenge 和 code_verifier 的属性吸取了 OAuth 2.0 的扩展”Proof-Key for Code Exchange“,也叫做 PKCE,也是这项技术的起源地。 $ L/ `, Y/ d8 v+ n& G" G
授权服务器必须支持 code_challenge 和 code_verifier 参数客户度必须使用 code_challenge 和 code_verifier,除了一些在 7.6 节 中描述的条件外,服务器也必须强制客户端使用 code_challenge 和 code_verifier。
. r8 F: O# n; W/ ~ 在当前情况下,我们仍然推荐按照下面列出的方式强制使用 code_challenge 和 code_verifierstate 和 scope 参数不应该在 plain 文本中包含和客户端、资源拥有者相关的敏感信息,因为它们能够通过不安全的通道传输,或者以不安全的方式存储。
1 _- M: c9 M% U: I7 V# b 客户端通过 HTTP 重定向或者用户代理提供的其他方式,指示资源拥有者构造 URI例如,客户端指示用户代理发起以下 HTTP 请求(额外的断行符只是为了显示的目的):GET /authorize?response_type=code&client_id=s6BhdRkqt3&。
" Z1 b+ s$ `5 W; K) q- s1 q state=xyz
( N( a+ e, v: A. r &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb1 C4 s; H8 b, x+ x5 f; h$ I/ m# A
&code_challenge=6fdkQaPm51l13DSukcAH3Mdx7_ntecHYd1vi3n0hMZY
8 _9 [( W! T) k: ~% Z &code_challenge_method=S256 HTTP/
! l: W4 B- j7 a9 j" y6 b, X 1.1
; i: K9 Z( m5 M$ x$ A" Y- Y4 ? Host: server.example.com8 H$ B1 `7 s! H; s
授权服务器验证请求,以确保所有的必须的参数都有效特别地,如果请求中存在 redirect_uri,授权服务器必须验证,确保其值与客户端注册的 URI 匹配。 $ @2 j; u1 v W$ `0 K
当比较两个 URI 时,授权服务器必须一个字符一个字符的比较如果请求有效,授权服务器认证资源拥有者的身份,并且获取授权决策(通过询问资源拥有者或者通过其他方式建立批准)这种获取授权决策,具体体现,比如:使用微信登录其他 App 时,会跳转到微信 App,询问用户是否允许。
1 d4 E; I( P9 h
! k" @7 v! O8 l3 O 当完成决策后,授权服务器指示用户代理使用 HTTP 重定向响应或用户代理提供的其他方式,提供客户端重定向 URI授权响应如果资源拥有者许可访问请求,授权服务器将颁发一个授权码并传送给客户端,使用 application/x-www-form-urlencoded 格式,在重定向 URI 查询组件中添加以下参数:
+ K; t3 @5 F' K- P& M6 {3 W. G0 f9 j 参数是否必填说明code是由授权服务器生成的授权码,并且对客户端不透明授权码在颁发后,必须在短期内失效,以防泄漏推荐授权码的生命周期为 10 分钟客户端只能使用一次授权码如果使用授权码超过一次,授权服务器必须拒绝请求,并且应该撤销(如果可能的话)基于上次颁发的授权码生成的所有访问令牌和刷新令牌。 ( x- D5 O% H5 W8 K- h1 X a
授权码与客户端识别码、code challenge、重定向 URI绑定state是如果客户端授权请求中包含 state 参数确切值来自客户端例如,授权服务器通过发送以下 HTTP 响应重定向用户代理:HTTP/1.1 2 | ? j6 ~9 J* {# b
302FoundLocation:https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz客户端必须忽略不能识别的响应参数本规约未定义授权码字符串的大小。
1 q W! Q) f x4 @( r 客户端应该避免假定授权码的大小授权服务器应该记录颁发的授权码的大小服务器关联颁发的授权码与 code_challenge 的确切方法,超出了本规约的范围code challenge 应该存储在服务器上,并且在服务器上关联授权码。 * C2 k$ k- A5 X7 o
code_challenge 和 code_challenge_method 的值可以以加密的方式存储在代码自身中,但是服务器不能在响应参数重包括 code_challenge 的值,只能提取 AS 以外的实体。 1 ^% n0 y6 _% B
客户端必须防止攻击者注入授权码到授权响应中使用 code_challenge 和 code_verifier 可以阻止注入授权码,原因是如果 code_verifier 不匹配,授权服务器将拒绝令牌请求。
# H! @* h; ]6 A1 ~4 t1 r* O 错误响应如果请求由于重定向 URI 缺失、无效、或者不匹配失败,或者如果客户端识别码缺失、无效,授权服务器应该通知资源拥有者错误,并且不能自动将用户代理重定向到错误的 URIAS 必须拒绝不携带 code_challenge 来自公共客户端的请求,并且必须。 . o+ C3 g. W+ l, l2 o' ]9 ?* `
拒绝来自其他客户端的这类请求,除非能保证客户端客户端不会以其他方式注入授权码如果服务器不支持请求的 code_challenge_method 转换方法,授权端点必须返回错误响应,并将 error 的值设为 invalid_request。
$ ^, N0 P! m& z error_description 或者 error_uri 应该解释错误的本质,比如,不支持的转换算法如果资源拥有者拒绝访问请求,或者如果请求失败是因为除了重定向 URI 缺失或失效之外的原因,授权服务器应该使用 。 . O& u5 a4 S5 c# {. X
application/x-www-form-urlencoded 格式,向重定向 URI 查询组件中添加以下参数:参数是否必填描述错误代码错误代码描述error必填错误参数不能包含特殊代码 %x20-21、%x23-5B、%x5D-7E。
# C5 ^' D% x. I4 ?, f x- |( w% Z invalid_request请求缺少必须的参数,存在无效的参数值,同一个参数出现多次,个税不正确unauthorized_client使用此方式请求授权码时,客户端未被授权access_denied资源拥有者或者授权服务器拒绝请求
+ I/ o9 n1 S) k1 C4 F unsupported_response_type授权服务器不支持使用此方法获取授权码invalid_scope请求范围无效、未知、或者格式错误server_error授权服务器遇到未知的情况,进而不能完成请求。
* O( q S) o- i) w& \ (此错误代码时必须的,因为不能通过 HTTP 重定向,返回 500 错误到客户端)temporarily_unavailable由于授权服务器当前不能处理超载或维护状态,导致不能处理请求(此错误代码时必须的,因为不能通过 HTTP 重定向,返回 503 状态到客户端)。 4 x% H* s( n9 O
error_description可选具备可读性的文本,提供额外的信息,当发生错误时,协助开发者理解错误来自 error_description 的参数不能包含特殊字符 %x20-21、%x23-5B、%x5D-7E。 - e7 ]6 p2 O# {3 Q$ o2 |9 F: k
error_uri可选指定一个包含错误信息的 web 页面 URI,为客户端开发者提供和错误相关的额外信息error_uri 的参数符合 URI 引用的语法,因此不能包含特殊字符 %x20-21、%x23-5B、%x5D-7E。 9 U e4 ~0 c/ j% C* r& |4 {& B9 F- b
state必填如果客户端授权请求中存在 state 参数,则该参数是必填该参数的值来自客户端例如,授权服务器通过发送以下请求重定向用户代理:HTTP/1.1 302 Found; a: b$ m% n9 e- }. ^# X
Location: https://client.example.com/cb?error=access_denied&state=xyz
+ X3 m5 `3 W+ }2 A! L 。 ) {1 F% M3 l, I# Y8 d" b# O( u! B5 `4 `
令牌端点扩展授权准许类型在令牌端点通过 authorization_code 的 grant_type 的值来识别如果设置此值则需要设置以下额外令牌请求参数:参数是否必填描述code必填来自授权服务器的授权码。 # `# E4 d& B+ K
redirect_uri必填如果授权请求中有 redirect_uri 参数,如[[#授权请求]]中所述,在此情形下,它们的值必须相同如果授权请求中没有 redirect_uri,此参数是可选的code_verifier。
6 |; E$ ^6 S, _+ C3 E' A 必填如果授权请求中有 code_challenge 参数绝对不能在其他地方使用原始的 code verifier 字符串例如,客户端发起如下 HTTP 请求(包含用于显示目的地换行符):POST/token 1 \4 E6 P( G1 ~7 L8 C5 @) B* o& \
HTTP/1.1
% w, V$ S: r- H# \ [- U Host: server.example.com
8 _; ^7 N+ f1 F- x5 D" _/ h( ]+ U Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
) y) ]: _# F' b; W Content-Type: application/x-www-form-urlencoded
" r: A( [3 U) q5 |+ d, L5 F' }3 m# E, o* D8 J
2 g5 A# q) x) f2 O grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA2 A8 Q; v, I& v8 ?+ j* ~
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
: h1 r0 s& K3 M! g/ Z &code_verifier=
3 Q( K3 C( \7 u7 B 3641a2d12d66101249cdf7a79c000c1f8c05d2aafcf14bf146497bed. L8 p* `8 @1 a6 V ?1 J
除了[[请求令牌]]的处理规则外,授权服务器必须:确保授权码颁发给经过认证的受信任的客户端或者有凭据的客户端,如果是公开客户端,确保授权码颁发给请求中的 client_id。 * v4 y$ T( o* Q1 D
验证授权码的有效性验证 code_verifier 参数,当且仅当授权请求中存在 code_challenge 参数时如果存在 code_verifier,则从接收到的 code_verifier 中计算 code challenge,并与之前关联的 code_challenge 经过客户端指定的 code_challenge_method 转换后进行比较,以此验证 code_verifier。
6 Y2 O# P5 `( @$ G' h 确保包含 redirect_uri 参数,如果 redirect_uri 如[[#授权请求]]中描述的那样包含在初始授权请求中,如果存在,则要确保它们的值是相同的客户端证书许可当客户端在其控制范围内请求访问受保护的资源时,客户端能够只使用它的客户端证书请求访问令牌(或者其他支持的认证方式),或者另一个资源拥有者提前安排了授权服务器(处理方式超出了此规约的范围)。 c7 ^$ N1 |3 @0 v* \
客户端证书许可类型必须只能被受信任的客户端或者有凭据的客户端使用。 0 h! n4 E0 ]0 j
客户端证书许可包含以下步骤:(1)通过授权服务器做客户端身份认证,从令牌端点请求访问令牌(2)授权服务器认证客户端身份,如果有效,颁发访问令牌令牌端点扩展授权许可类型通过 grant_type 的值 client_credentials 识别令牌终端。
" p8 @2 p/ I" T3 q0 p0 k& C% s( L 如果设置了此值,则不需要[[#令牌请求]]之外参数:例如,客户端发起如下 HTTP 请求:POST/token HTTP/1.1/ Z6 `- [ ~2 p$ V8 {4 G; y2 w
Host: server.example.com D" G% n: ]% j! m0 v7 Y& ]3 J
Authorization
S) R/ H0 [# p : Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW8 Y4 v* I; L+ y5 X" x) q6 A
Content-Type: application/x-www-form-urlencoded( c5 p+ I9 O) z% ?5 s
/ X% _; g8 F0 J6 M, L% N( c grant_type=client_credentials$ m3 M5 A7 @. Y
' z3 l5 n% i# d0 \/ s7 F
授权服务器必须认证客户端的身份刷新令牌许可刷新令牌是授权服务器颁发给客户端的凭证,可以用它基于现有的许可方式获取新的(刷新)访问令牌客户端使用此选项,要么因为上一个访问令牌过期,要么因为上次获取的访问令牌 scope 比各自通过的许可窄,并且在相同的许可下请求 scope 不同的访问令牌。
2 ~) ^( B' I8 b! b* b6 f 刷新令牌必须安全保存,只在授权服务器和颁发刷新令牌的客户端之间共享授权服务器必须在刷新令牌和颁发给的客户端之间维护绑定关系当客户端身份被认证时,授权服务器必须验证刷新令牌和客户端身份的绑定关系当客户端无法认证身份时,授权服务器。
! W+ W2 L- |/ t 应该颁发 sender-constrained 刷新令牌或者使用刷新令牌反转(参见 [[#刷新访问令牌]])。授权服务器必须确保刷新令牌不能被未授权的第三方生成、修改、或者猜着生成有效的刷新令牌。
+ d2 i4 N6 {& H7 j/ t2 g" V2 f. O4 |( X7 o8 S+ k, w0 c; S
, c0 F+ s' D. o; ^4 ^, ?( E# [" K4 Q1 K) s9 d! }) ~
+ h. T& b* Y$ N# _& S$ L |