OAuth2授权码、客户端凭证、PKCE、设备码授权流程详解
# 前言
本教程将结合 Gemini 2.5 Pro 来为各位进行讲解
OAuth 2.0 是一个行业标准的授权框架,它允许第三方应用程序在不获取用户凭证(如用户名和密码)的情况下,有限度地访问用户在某一网站上存储的私有资源。
OAuth 2.0 中的四个核心角色:
-
资源所有者 (Resource Owner):通常是终端用户,拥有受保护资源的最终控制权。
-
客户端 (Client):希望访问资源所有者受保护资源的第三方应用程序。
-
授权服务器 (Authorization Server):负责验证资源所有者的身份,并在获得资源所有者授权后,向客户端颁发访问令牌(Access Token)。
-
资源服务器 (Resource Server):存储受保护资源,并接受和验证访问令牌,以允许客户端访问资源。
# 授权码模式 (Authorization Code Grant)
这是功能最完善、安全性最高的授权模式,因为需要保持 client_secret 非公开,推荐用于传统的 Web 应用(即拥有后端的应用)。
下文的
客户端
包括客户端的前端与后端,资源所有者一般为用户
sequenceDiagram
participant ResourceOwner as 资源所有者
participant Client as 客户端
participant AuthServer as 授权服务器
participant ResourceServer as 资源服务器
ResourceOwner->>Client: 1. 访问客户端
Client-->>ResourceOwner: 2. 返回授权链接
ResourceOwner->>AuthServer: 3. 通过链接访问授权页面
AuthServer-->>ResourceOwner: 4. 展示授权界面
ResourceOwner->>AuthServer: 5. 确认授权
AuthServer->>Client: 6. 重定向回调(携带code)
Client->>AuthServer: 7. 发送code + client_secret请求token
AuthServer-->>Client: 8. 返回access_token
Client->>ResourceServer: 9. 携带access_token请求资源
ResourceServer-->>Client: 10. 返回受保护资源
note left of ResourceOwner: 用户操作流
note right of AuthServer: 安全校验:
note right of AuthServer: - 验证code有效性<br/>- 验证client_secret<br/>- 检查重定向URI
# 发起授权请求
当用户使用客户端时,客户端需要发起授权请求。
该请求实际由用户发起,客户端负责构建一个包含 client_id
、 redirect_uri
、 response_type
和 scope
等参数的 URL 授权请求链接:
Base URL
:授权服务器的授权 Endpoint,例如 https://xxx.com/api/authorize
response_type
: code
client_id
:客户端 ID
redirect_uri
:回调 URL
scope
:资源(权限)范围,例如 openid
、 email
、 profile
等
state
:随机字符,可选(推荐),用于防止跨站请求伪造(CSRF)攻击
例如:
1 | GET /authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=SCOPE |
# 确认授权
用户定向到授权链接,如果此时用户未登录,则会要求用户进行登录
登录完成后,授权服务器会展示本次授权请求相关信息,例如客户端的详情,请求的资源内容等
如果用户确认授权,则重定向至 redirect_uri(上文示例中的 CALLBACK_URL 内容)并在 URL 参数中携带本次确认授权的 AUTHORIZATION_CODE
Tips:正常情况下,授权服务器会对 redirect_uri 的内容进行校验,确保当前授权链接中的 redirect_uri 内容与客户端在授权服务器注册时预留的 redirect_uri 内容一致,否则重定向至钓鱼链接会导致本次确认授权的 AUTHORIZATION_CODE 泄露
# 客户端请求 access_token
客户端通过 redirect_uri 拿到本次授权的 AUTHORIZATION_CODE 后,使用 AUTHORIZATION_CODE 和 client_secret 向授权服务器请求 access_token
如果客户端启用了 state ,还会校验当前回调链接中的 state 值是否和当初授权链接中的 state 值一致,确保 AUTHORIZATION_CODE 仅用于本次授权请求会话,防止跨站请求伪造(CSRF)攻击
Base URL:授权服务器的令牌 Endpoint,例如 https://xxx.com/api/token
客户端向 Base URL 发送 POST 请求,请求体 Content-Type
为 application/x-www-form-urlencoded
,请求体内容如下:
grant_type
: authorization_code
code
:上文的 AUTHORIZATION_CODE
redirect_uri
:回调 URL
client_id
:客户端 ID
client_secret
:客户端 Secret
client_secret 不可直接暴露给客户端前端,必须由安全的客户端后端使用,否则任何人(包括攻击者)只需要拿到 AUTHORIZATION_CODE 即可向授权服务器发起 access_token 请求
# 客户端请求资源
当授权服务器返回 access_token 后,使用 access_token 向资源服务器请求所需要的资源
资源服务器对 access_token 进行一系列校验,确认身份后返回客户端所需要的资源
# 客户端凭证模式 (Client Credentials Grant)
该模式适用于没有特定用户参与的场景,即客户端以自己的名义访问资源(例如,机器对机器的通信)。
因为没有用户参与,因此这里客户端可以直接向令牌 Endpoint 请求 access_token
该模式需要具体参考授权服务器的文档,例如 authentik 身份识别基于服务账户,身份验证基于应用密码令牌(client_secret),因此还需要传递服务账户的 username 和 password,这里仅示例通用情况。
# 客户端请求 access_token
Base URL:授权服务器的令牌 Endpoint,例如 https://xxx.com/api/token
客户端向 Base URL 发送 POST 请求,请求体 Content-Type
为 application/x-www-form-urlencoded
,请求体内容如下:
grant_type
: client_credentials
scope
:权限范围,具体参考授权服务器文档
请求头:
Authorization
: Basic ${将 client_id:client_secret 进行 Base64 编码后的字符串}
当然也可以将客户端凭证放在请求体中:
grant_type
: client_credentials
scope
:权限范围,具体参考授权服务器文档
client_id
:客户端 ID
client_secret
:客户端 Secret
# 客户端请求资源
当授权服务器返回 access_token 后,客户端使用 access_token 向资源服务器请求所需要的资源
资源服务器对 access_token 进行一系列校验,确认身份后返回客户端所需要的资源
# 授权码 + PKCE (Proof Key for Code Exchange) 模式
PKCE 是对授权码模式的增强,现在被认为是所有类型客户端(包括原生应用、单页应用和传统 Web 应用)的最佳实践。
核心思想:在授权请求时,客户端生成一个随机的验证器( code_verifier
),并将其哈希值( code_challenge
)发送给授权服务器。当客户端用授权码交换访问令牌时,必须同时提供原始的 code_verifier
。授权服务器会验证 code_verifier
和 code_challenge
是否匹配,从而确保即使授权码被截获,攻击者也无法在没有 code_verifier
的情况下冒用。
sequenceDiagram
participant C as 客户端 (App)
participant U as 资源所有者
participant AS as 授权服务器
participant RS as 资源服务器
Note over C: 1. 生成 code_verifier, <br/> 计算 code_challenge
C->>U: 2. 发起授权请求 (携带 code_challenge)
U->>AS: 用户浏览器重定向至授权服务器
AS-->>U: 要求用户登录并授权
U->>AS: 同意授权
AS-->>C: 4. 重定向并返回授权码 (code)
Note over C: 截获此处的 code 也无用,<br/>因为攻击者没有 code_verifier
C->>AS: 5. 交换令牌请求 <br/>(携带 code 和 code_verifier)
Note over AS: 6. 验证 code_verifier 和 <br/> 之前存储的 code_challenge 是否匹配
AS-->>C: 验证通过,返回 Access Token
C->>RS: 7. 使用 Access Token 请求资源
RS-->>C: 返回受保护的资源
# 发起授权请求
客户端首先生成一个随机的 code_verifier
,将这个 code_verifier
存储在本地(例如,App 的内存或浏览器的 sessionStorage
中),根据 code_verifier
计算出对应的 code_challenge
。
code_challenge
由 code_verifier
先按照 code_challenge_method
进行计算,然后进行 Base64URL
编码
然后构建授权请求链接:
Base URL
:授权服务器的授权 Endpoint,例如 https://xxx.com/api/authorize
response_type
: code
client_id
:客户端 ID
redirect_uri
:回调 URL
scope
:资源(权限)范围,例如 openid
、 email
、 profile
等
state
:随机字符,可选(推荐),用于防止跨站请求伪造(CSRF)攻击
code_challenge
:第一步通过 code_verifier 计算出的 code_challenge
code_challenge_method
: S256
(具体看授权服务器的支持类型)
授权服务器收到请求后,会存储 code_challenge 并将其与即将生成的授权码关联起来。
# 确认授权
用户在授权服务器的页面上登录,并同意授权给客户端。
然后重定向至 redirect_uri,并附带 code
与 state
内容
# 客户端请求 access_token
与标准的授权码模式相比,请求体中必须额外包含第一步生成的 code_verifier
Base URL:授权服务器的令牌 Endpoint,例如 https://xxx.com/api/token
客户端向 Base URL 发送 POST 请求,请求体 Content-Type
为 application/x-www-form-urlencoded
,请求体内容如下:
grant_type
: authorization_code
code
:上文的返回的 code
redirect_uri
:回调 URL
client_id
:客户端 ID
code_verifier
:第一步生成的 code_verifier
收到请求后,授权服务器计算 code_verifier
在使用算法(S256)后是否与开始提供的 code_challenge
一致,一致则下发 access_token
。
# 客户端请求资源
当授权服务器返回 access_token 后,客户端使用 access_token 向资源服务器请求所需要的资源
资源服务器对 access_token 进行一系列校验,确认身份后返回客户端所需要的资源。
# 设备码流程 (Device Code Flow)
设备码流程(Device Code Flow)非常巧妙地解决了那些没有浏览器或输入不方便的设备(比如智能电视、游戏机、命令行工具、树莓派等)如何进行 OAuth 2.0 授权的问题。
整个流程的核心思想是:在输入受限的设备上发起授权,然后在另一个功能齐全的设备(如手机或电脑)上完成授权确认。
sequenceDiagram
participant TV as 客户端设备 (智能电视)
participant User as 用户
participant AS as 授权服务器
participant Phone as 授权设备 (手机)
TV->>AS: 请求设备码和用户码
AS-->>TV: 返回 device_code, user_code 等
TV->>User: 显示 user_code 和验证网址
User->>Phone: 在手机上访问网址
Phone->>AS: 提交 user_code 并登录授权
Note over TV, AS: 设备在后台持续轮询, 直到用户授权成功
AS-->>TV: (轮询成功后) 返回 Access Token
TV->>User: 显示登录成功
# 设备发起授权请求
设备向授权服务器的一个特定端点 —— 设备授权端点(Device Authorization Endpoint)发起 POST 请求,
Base URL:授权服务器的设备授权 Endpoint,例如 https://xxx.com/api/device_authorization
请求体 Content-Type
为 application/x-www-form-urlencoded
,请求体内容如下:
client_id
:客户端 ID
scope
:资源(权限)范围,例如 openid
、 email
、 profile
等
授权服务器将返回临时凭证
示例:
1 | { |
device_code
:设备代码,是保存在设备上的代码
verification_uri
:显示给最终用户以输入代码的 URL
verification_uri_complete
:与上述相同的 URL,不同之处在于代码已被预填充,用户无需再输入代码
user_code
:最终用户输入的原始代码
expires_in
:此令牌将在多少秒后过期
interval
:设备应多久检查一次令牌状态的时间间隔(以秒为单位)
# 设备端显示信息并开始轮询
设备开始向用户展示 verification_uri
与 user_code
并引导用户访问该网址,填写代码
感觉和 HMCL 的登录 MC 正版账户流程很像啊
然后设备开始轮询,按照建议的 interval
,持续向授权服务器的令牌 Endpoint 发起 POST 请求。
请求体 Content-Type
同样为 application/x-www-form-urlencoded
,请求体内容如下:
grant_type
: urn:ietf:params:oauth:grant-type:device_code
(设备码流程专用)
device_code
:上文的 device_code
client_id
:客户端 ID
需要注意的是,部分授权服务器返回的 device_code 过于抽象(说的就是你 authentik),可能会内嵌部分需要转义的字符,因此返回的 device_code 内容是已经加了 \
转义符的,但是该转义符不属于 device_code 内容,如果设备对于发出请求的内容自带转义将会使 \
也被提交,导致出现 invalid_grant
错误!
# 确认授权
用户访问展示的 verification_uri
,并填入 user_code
,在授权页面确认授权
当然也可以直接展示 verification_uri_complete
# 设备获取 access_token
在用户同意授权后,下一次轮询将获得授权服务器下发的 access_token
# 设备请求资源
当授权服务器返回 access_token 后,设备使用 access_token 向资源服务器请求所需要的资源
资源服务器对 access_token 进行一系列校验,确认身份后返回客户端所需要的资源。
# 总结
隐式授权模式和密码凭证模式这两个不安全的模式就不写了。
授权模式 | 适用场景 | 安全性 | 推荐度 |
---|---|---|---|
授权码 + PKCE | 所有类型的应用,特别是公共客户端(SPA、移动应用)和机密客户端(Web 应用) | 非常高 | 强烈推荐 |
授权码模式 | 有后端的 Web 应用(机密客户端) | 高 | 推荐 |
客户端凭证模式 | 机器对机器(M2M)通信,无用户参与 | 高 | 推荐 |
设备码流程 | 无浏览器或输入受限的设备 | 中高 | 特定场景推荐 |
隐式授权模式 | 已废弃 | 低 | 不推荐 |
密码凭证模式 | 已废弃,仅用于高度信任的遗留系统 | 非常低 | 不推荐 |
# 后记
暑假意外的高产😁
Use this card to join MyBlog and participate in a pleasant discussion together .
Welcome to GoodBoyboy 's Blog,wish you a nice day .