Refinition
OAuth2 是在WEB基础上发展出来的一个授权框架(Authorization Framework),也可以认为它是一套协议,一套能解决第三方授权问题的解决方案,优势在于它允许第三方应用在不获取用户密码的情况下,获得访问用户资源(用户的ID信息等)的有限权限。
它是如何做到的?下面来解析一下这个协议。
搞懂这个协议,需要理解下面几个部分:
四个核心角色
OAuth2 流程中有四个关键角色:
-
资源所有者 (Resource Owner)
用户(一般指使用客户端的人),他拥有受保护资源(一般指他的账号信息);
-
客户端 (Client)
一个前端UI应用,方便用户操作的面板。一般来说客户端和授权服务器是前后端关系。
-
授权服务器 (Authorization Server)
验证用户身份并颁发令牌的 绝对控制者。
-
资源服务器 (Resource Server)
一般指可以提供给用户使用的服务器,能给用户提供专属服务,比如网盘服务、点外卖服务,这种也叫第三方应用。
资源服务器一般需要用户信息,才能提供服务。
获得用户信息,可以让用户注册,但是APP太多了,很多用户不愿意注册了。就发展出第三方应用去授权服务器拿取用户ID的免注册模式。
四类常见授权模式
OAuth2 定义了多种授权方式,适应不同场景,下面看4个常见:
- 授权码模式 (Authorization Code) - 最常用、较安全(本文讨论这个模式)
- 密码模式 (Resource Owner Password Credentials) - 需要信任客户端
- 客户端凭证模式 (Client Credentials) - 应用访问自己的资源
- 隐式模式 (Implicit) - 较不安全,已不推荐
授权模式中的术语
-
令牌 (Tokens)
- 访问令牌 (Access Token):相当于"临时门禁卡",用于访问资源
- 刷新令牌 (Refresh Token):用于获取新的访问令牌,避免用户频繁重新登录
-
......
OAuth2 协议流程解析
在看时序图之前,必须先清楚各个角色的作用,不然就会混乱。
时序图,简版流程:
sequenceDiagram
actor User as 用户
participant Client as 客户端应用
participant AuthServer as 授权服务器
participant ResourceServer as 资源服务器
Note over User, Client: 1. 启动授权流程
User->>Client: 访问应用
Client->>User: 重定向到授权服务器
Note over User, AuthServer: 2. 用户认证与授权
User->>AuthServer: 在授权页面登录并授权
AuthServer->>User: 重定向回应用并携带授权码
Note over Client, AuthServer: 3. 交换令牌(后端通信)
Client->>AuthServer: 发送授权码 + 客户端凭证
AuthServer->>Client: 返回访问令牌
Note over Client, ResourceServer: 4. 访问资源
Client->>ResourceServer: 使用访问令牌请求资源
ResourceServer->>Client: 返回受保护资源
Client->>User: 显示用户请求的内容
更接近实际的流程,以企微授权一个工作台应用案例为例子:
sequenceDiagram
actor 用户
participant 客户端 as 企业微信客户端<br>(浏览器/WebView)
participant 应用 as 企微工作台应用<br>(Your Server)
participant 授权服务器 as 企业微信<br>授权服务器
participant 资源服务器 as 企业微信<br>资源服务器
用户->>客户端: 1. 点击工作台应用图标
客户端->>应用: 2. 访问应用首页 (GET /)
Note over 应用, 授权服务器: 认证与授权阶段
应用->>客户端: 3. 重定向到企微授权页<br>?response_type=code&redirect_uri=...
客户端->>授权服务器: 4. 跳转到授权页面
授权服务器->>用户: 5. (可选) 向用户显示授权同意页面
用户->>授权服务器: 6. 点击同意授权
授权服务器->>客户端: 7. 重定向到 redirect_uri 并携带 code
客户端->>应用: 8. 请求 Callback URL (GET /callback?code=...)
Note over 应用, 资源服务器: 获取访问令牌与用户信息
应用->>授权服务器: 9. 用 code 换取 access_token<br>(POST /token) + secret
授权服务器->>应用: 10. 返回 access_token
应用->>资源服务器: 11. 使用 access_token 获取用户信息<br>(GET /userinfo?access_token=...)
资源服务器->>应用: 12. 返回用户信息 (e.g., UserId)
Note over 应用, 用户: 正常业务访问
应用->>应用: 13. 根据UserId处理业务逻辑<br>(e.g., 查询数据库)
应用->>客户端: 14. 返回个性化应用页面
客户端->>用户: 15. 显示应用内容
以上都是以一个用户视角看的流程,其实后端之间还做了很多事情,才能保障整个流程的安全,不过下面的流程图需要以一个开发者的角度看待,下面继续解构。
后端之间的交互流程-包含安全方案设计
下面以企业微信OAuth2授权码流程,以开发者(应用服务器/资源服务器)的视角,看待应用如何与企微授权服务器建立联系,包括如何获取安全凭证(如Secret)、验证的流程。
企业微信OAuth2授权码流程中,开发者需要先在企微管理后台创建应用,获取应用的安全凭证(corp_id, secret等),然后在应用服务器中使用这些凭证。
整体流程分为两个主要部分:
- 应用注册与安全凭证发放(静态配置)
- OAuth2授权码流程(动态交互)
理解几个密码学上关键的工具包:
- AgentId
- Secret
- 随机数
- 签名-HMAC
- ......
应用注册与安全凭证发放
这一步是确保整体安全的关键流程,Secret参数作为一个关键密钥(可以申请更换),它也是标识身份,防抵赖性作用。
一个AgentId 对应 一把Secret密钥。
Secret必须安全地存储在应用服务器内,只做打签名用。不可以暴露,否则有被冒充安全风险!
sequenceDiagram
participant 开发者
participant 企微管理后台
participant 应用服务器
开发者->>企微管理后台: 1. 登录管理后台
开发者->>企微管理后台: 2. 创建应用(设置应用名称、logo等)
企微管理后台->>开发者: 3. 返回应用凭证:AgentId、Secret等
开发者->>应用服务器: 4. 配置AgentId和Secret
OAuth2授权码流程
State:
客户端生成 State 流程:
- 收集数据:随机数 + 时间戳 + 会话ID + 业务参数
- 构建结构:
- 计算签名:HMAC-SHA256(排序后的数据字符串, client_secret)
- 添加签名:
- Base64编码:生成最终的state参数
客户端/服务端验证 State 流程:
- Base64解码state,得到原始数据结构
- 提取签名值,从数据结构中移除签名字段
- 重新计算签名:HMAC-SHA256(相同的排序数据, client_secret)
- 对比签名:恒定时间比较计算签名与提取签名
- 如果一致 → 数据未被篡改,验证通过
- 如果不一致 → 可能被篡改,拒绝请求
graph TB
A[客户端生成state] --> B[使用client_secret签名]
C[客户端验证state] --> D[使用client_secret验签]
B --> E[确保数据完整性]
D --> E
再次看OAuth2授权码流程:
流程图是AI生成的,有些角色关系描述得不是那么准确,但是大致理解state、code的安全流程是没问题的。请辩证的看。
sequenceDiagram
participant User as 用户
participant Client as 客户端应用
participant Browser as 浏览器
participant AuthServer as 授权服务器
participant TokenStore as 令牌存储
Note over Client, AuthServer: State 生成与签名阶段
User->>Client: 1. 访问应用
Client->>Client: 2. 生成 State 参数
Note right of Client: - 收集会话ID<br/>- 生成随机数<br/>- 记录时间戳<br/>- 添加业务上下文
Client->>Client: 3. State 签名
Note right of Client: - 排序数据字段<br/>- 计算HMAC签名<br/>- Base64编码
Client->>Browser: 4. 存储State到Session
Client->>Browser: 5. 重定向到授权页(带state)
Note over Browser, AuthServer: 用户认证与授权阶段
Browser->>AuthServer: 6. 请求授权页面
AuthServer->>User: 7. 显示登录页面
User->>AuthServer: 8. 输入凭证并授权
AuthServer->>AuthServer: 9. 验证用户凭证
Note over AuthServer, TokenStore: Code 生成阶段
AuthServer->>AuthServer: 10. 生成授权码(Code)
Note right of AuthServer: - 关联用户ID<br/>- 绑定客户端ID<br/>- 设置权限范围<br/>- 添加时间戳
AuthServer->>TokenStore: 11. 存储Code元数据
Note right of TokenStore: - code值<br/>- 用户信息<br/>- 客户端信息<br/>- 过期时间
AuthServer->>Browser: 12. 重定向回客户端(带code+state)
Note over Client, TokenStore: State 校验与 Code 验证阶段
Browser->>Client: 13. 请求回调URL
Client->>Client: 14. 提取并验证State
Note right of Client: - Base64解码<br/>- 验证签名<br/>- 检查时效性<br/>- 会话匹配
Client->>Client: 15. 清理已用State
Client->>AuthServer: 16. 用Code交换Token
AuthServer->>TokenStore: 17. 验证Code有效性
TokenStore->>AuthServer: 18. 返回Code关联信息
AuthServer->>AuthServer: 19. Code使用标记
AuthServer->>Client: 20. 返回访问令牌
TokenStore->>TokenStore: 21. 清理已用Code
Client->>User: 22. 完成授权流程
为什么用code不直接返回token?
授权码(code)的安全作用介绍:
授权码(code)是OAuth2授权码流程中的核心凭证,它通过前端信道传递(浏览器重定向),然后由客户端应用在后端信道中与授权服务器交换访问令牌。授权码的安全作用主要包括:
- 避免访问令牌通过前端信道传输,降低令牌泄露风险。
- 授权码是短暂的,一次性使用的,降低被窃取后的风险。
- 授权码与客户端身份绑定,防止被其他客户端使用。
假设code被截取:
- 即使授权码被窃取,攻击窗口很短
- 自动清理过期的授权码,减少存储负担
所以code也不能保证绝对安全,但是相对安全,防CSRF类攻击时,它是有效的。