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 .