OAuth2.0授权流程解析
候选人小李在面试时被问到:"你用过微信登录吗?它的原理是什么?"
小李说:"就是OAuth2.0授权。"
面试官追问:"OAuth2.0有哪几种授权模式?你用的是哪种?为什么?"
小李支支吾吾:"有授权码模式...还有...其他模式?"
面试官继续:"为什么微信登录要用授权码模式,而不是隐式模式?"
小张彻底卡住了...
OAuth2.0是现代互联网的身份认证基础,但能把这个讲清楚的人其实不多。
今天我们就来把这个彻底讲清楚。
【直观类比】
停车场的授权
想象你去一个停车场:
- 你走到停车场入口 → 你想访问某个服务
- 门卫说:"你需要获得授权" → 需要身份验证
- 你去找物业开具授权证明 → 授权服务器
- 物业说:"我给你一张授权卡,但不能复制" → 访问令牌(Access Token)
- 你拿着授权卡进入停车场 → 使用令牌访问资源
- 停车场管理员看到授权卡,允许你停车 → 验证令牌,授予资源
OAuth2.0的角色
OAuth2.0定义了四个核心角色:
┌────────────────────────────────────────────────────┐
│ 资源所有者(Resource Owner) │
│ 你:能够授权访问自己资源的用户 │
├────────────────────────────────────────────────────┤
│ 客户端(Client) │
│ 第三方应用:想要访问你的资源的应用 │
├────────────────────────────────────────────────────┤
│ 授权服务器(Authorization Server) │
│ 微信:验证用户身份,颁发令牌 │
├────────────────────────────────────────────────────┤
│ 资源服务器(Resource Server) │
│ 微信API:存储你的头像、昵称等资源 │
└────────────────────────────────────────────────────┘
OAuth2.0的核心概念
令牌(Token)
Access Token:访问令牌,客户端用来请求资源
Access Token的特点:
1. 有时效性(通常几小时)
2. 有权限范围(Scope)
3. 可以被撤销
4. 不能用于刷新
Refresh Token:刷新令牌,用于获取新的Access Token
Refresh Token的特点:
1. 时效更长(几天到几周)
2. 只能换取Access Token
3. 必须安全存储
4. 每次刷新后可能轮换
权限范围(Scope)
Scope定义了Token可以访问哪些资源:
常见Scope示例(微信):
- snsapi_base:获取用户的openid
- snsapi_userinfo:获取用户的基本信息
- snsapi_login:网页扫码登录
常见Scope示例(GitHub):
- repo:访问仓库
- read:user:读取用户信息
- user:email:访问用户邮箱
用户授权时,会看到应用请求的权限列表。
四种授权模式
模式1:授权码模式(Authorization Code)
这是最安全、最推荐的模式,适用于有后端服务器的应用:
sequenceDiagram
participant U as 用户
participant C as 客户端应用
participant A as 授权服务器
participant R as 资源服务器
Note over U,C:用户点击"微信登录"
U->>C: 访问登录页面
C->>U: 重定向到授权页面
U->>A: 打开微信授权页
Note over A:用户确认授权
A->>U: 显示权限请求
U->>A: 确认授权
A->>C: 返回授权码(code)
Note over C:后端用授权码换令牌
C->>A: 发送 code + client_secret
A->>C: 返回 access_token + refresh_token
Note over C:用令牌访问用户资源
C->>R: 请求用户信息<br/>Authorization: Bearer xxx
R->>C: 返回用户数据
为什么授权码模式最安全?
1. 授权码换取令牌在后端进行
2. client_secret不会暴露给浏览器
3. access_token不会经过用户浏览器
4. 可以实现刷新令牌
5. 支持短期令牌+长期刷新令牌
微信登录的实际流程:
Step 1: 生成state参数(防CSRF)
Step 2: 重定向到微信授权页
https://open.weixin.qq.com/connect/qrconnect?
appid=APPID&
redirect_uri=ENCODED_URL&
response_type=code&
scope=snsapi_login&
state=STATE
Step 3: 用户扫码确认
Step 4: 微信回调到redirect_uri,携带code
Step 5: 后端用code换token
POST https://api.weixin.qq.com/sns/oauth2/access_token
params: appid, secret, code, grant_type=authorization_code
Step 6: 用access_token获取用户信息
GET https://api.weixin.qq.com/sns/userinfo
params: access_token, openid
模式2:隐式模式(Implicit)
适用于纯前端应用,没有后端服务器的场景:
sequenceDiagram
participant U as 用户
participant C as 客户端应用
participant A as 授权服务器
Note over U,C:用户点击登录
C->>U: 重定向到授权页
U->>A: 打开授权页
U->>A: 确认授权
A->>C: 直接返回access_token<br/>(在URL片段中)
Note over C:access_token在前端使用
C->>A: 请求资源<br/>携带access_token
URL格式示例:
https://example.com/callback#access_token=xxx&expires_in=3600&state=xxx
为什么叫"隐式"?
- 不需要通过授权码换取令牌
- 令牌直接返回,跳过了code步骤
- 没有client_secret验证
隐式模式的问题:
⚠️
隐式模式已被废弃(OAuth 2.1提议),原因:
- 令牌暴露在前端:通过URL传递,有泄露风险
- 无法刷新令牌:没有refresh_token
- 无法验证令牌:客户端无法确认令牌是否有效
- 容易被CSRF攻击:state参数更难保护
现代替代方案:使用PKCE扩展的授权码模式
模式3:密码凭证模式(Password Credentials)
适用于高度可信的应用,用户直接提供用户名密码:
sequenceDiagram
participant U as 用户
participant C as 客户端应用
participant A as 授权服务器
Note over U,C:用户输入密码
U->>C: 输入用户名+密码
C->>A: 发送用户名+密码+client_credentials
A->>A: 验证用户名密码
A->>C: 返回access_token
使用场景:
适用场景:
1. 公司内部应用(员工系统访问内部服务)
2. 操作系统原生应用(官方第一方应用)
3. 高度可信的CLI工具
不适用场景:
1. 第三方应用(你不能把密码给第三方)
2. 不受控的应用(可能有恶意行为)
模式4:客户端凭证模式(Client Credentials)
适用于机器对机器的认证,没有用户参与:
sequenceDiagram
participant C as 客户端应用
participant A as 授权服务器
participant R as 资源服务器
Note over C:应用使用自己的凭证
C->>A: 发送 client_id + client_secret
A->>A: 验证客户端身份
A->>C: 返回access_token
C->>R: 请求资源<br/>携带access_token
R->>C: 返回资源数据
使用场景:
适用场景:
1. 微服务间调用
2. 后台定时任务
3. CI/CD pipeline
4. 服务器监控服务
PKCE:授权码模式的安全增强
什么是PKCE?
PKCE(Proof Key for Code Exchange)是对授权码模式的扩展,用于防止令牌被劫持:
原始问题:
- 攻击者可以拦截授权码,换取令牌
- 如果没有client_secret,攻击者可以伪造
PKCE解决方案:
- 客户端生成一个随机数(code_verifier)
- 用哈希值(code_challenge)替代client_secret
- 换取令牌时必须提供原始随机数
PKCE的流程
# Step 1: 生成code_verifier和code_challenge
import hashlib
import secrets
import base64
code_verifier = secrets.token_urlsafe(32) # 随机字符串
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).decode().rstrip('=')
# Step 2: 授权请求时发送code_challenge
# GET /authorize?code_challenge=xxx&code_challenge_method=S256
# Step 3: 换取令牌时发送code_verifier
# POST /token
# {
# "code": "xxx",
# "code_verifier": "原始随机字符串"
# }
PKCE的应用场景
PKCE适用于:
1. 移动App(原生应用)
2. 单页应用(SPA)
3. 任何不能安全存储client_secret的场景
现代OAuth最佳实践:
- 授权码模式 + PKCE = 所有应用的最佳选择
- 即使有后端,也建议使用PKCE
常见授权平台的Scope对比
微信公众平台
Scope | 说明
-----------------------| ----------------------
snsapi_base | 获取openid(静默登录)
snsapi_userinfo | 获取昵称、头像等
snsapi_login | 网页扫码登录
GitHub
Scope | 说明
-----------------------| ----------------------
repo | 访问仓库(包括私有)
read:user | 读取用户资料
user:email | 访问邮箱地址
workflow | 管理GitHub Actions
Google
Scope | 说明
-----------------------| ----------------------
openid | OpenID Connect
email | 访问用户邮箱
profile | 访问用户基本资料
https://www.googleapis.com/auth/calendar | 日历
常见误区
误区1:OAuth2.0是认证协议
错误。OAuth2.0是授权协议,不是认证协议。
OAuth 2.0:不告诉你"你是谁",只告诉你"你能访问什么"
OpenID Connect:在OAuth 2.0基础上加了认证层,能知道"你是谁"
误区2:access_token可以用很久
错误。Access Token的典型生命周期:
- 短则30分钟
- 长则2小时
- 过期后用refresh_token换取新的
原因:Token泄露后影响时间有限。
误区3:可以不用refresh_token
错误。如果没有refresh_token:
- 用户必须重新登录授权
- 用户体验差
- 每次授权都要用户操作
Refresh Token允许用户在后台自动刷新,不打扰用户。
误区4:Scope越全越好
错误。请求过多Scope的坏处:
- 用户不信任(为什么你要访问我的通讯录?)
- 审批更严格(某些Scope需要审核)
- 安全风险更大(Scope越多,泄露后损失越大)
最佳实践:只请求所需的最小Scope。
JWT与OAuth2.0的关系
JWT不是OAuth2.0的一部分
OAuth 2.0:授权框架(定义如何获取令牌)
JWT:令牌格式(定义令牌长什么样)
它们可以独立使用,也可以组合使用:
- OAuth 2.0 + JWT:最常见的组合
- OAuth 2.0 + 不透明Token:也常见
- 不用OAuth 2.0 + JWT:在微服务间使用JWT作为身份凭证
记忆技巧
口诀
OAuth2.0四模式,授权码是大哥大
隐式已废弃,PKCE来替代
密码模式自己用,凭证模式机对机
Token有时效,Refresh保长久
模式选择速查表
实战检验
检验1:实现微信扫码登录
场景:在自己的网站实现微信扫码登录
实现步骤:
1. 申请微信开放平台账号,创建网页应用
2. 生成state参数存储在session
3. 重定向用户到微信授权页
4. 微信回调,携带code和state
5. 验证state防止CSRF
6. 用code向微信换取access_token
7. 用access_token获取用户信息
8. 创建本地会话,完成登录
检验2:设计一个OAuth授权服务
场景:需要为一个第三方应用提供OAuth授权
设计要点:
1. 授权端点:显示用户授权页面
2. Token端点:颁发和刷新Token
3. 验证端点:验证Token有效性
4. Scope管理:定义支持的权限
5. 存储设计:Token、用户授权记录
6. 安全措施:CSRF防护、恶意登录检测
检验3:排查OAuth失败
场景:用户扫码后报错"redirect_uri mismatch"
排查思路:
1. 检查微信开放平台配置的回调域名
2. 检查代码中的redirect_uri是否匹配
3. 检查URL编码是否正确
4. 检查是否有特殊字符
5. 确认是网页授权而非JS SDK授权
【面试官心理】
面试官问OAuth2.0,其实是在测试你对"授权体系"的理解。知道四种模式是60分,知道为什么用授权码模式是80分,知道PKCE的作用是90分,如果还能讲清楚OAuth和OIDC的区别以及JWT的关系,那就是P7的水平了。
延伸阅读