OAuth2 标准流程逐字段拆解:写注册机 / 接第三方登录必看
OAuth2 是现在第三方登录、API 授权、单点登录的事实标准。所有”用微信/GitHub/Google 登录”的按钮背后都是这套协议。但大多数文档把它写得太学术——一堆”授权服务器""资源所有者”看完更晕。
这篇用人话把 OAuth2 的4 个角色 + 10 个关键参数 + PKCE 扩展 + 易错点 全梳理一遍,理解之后写注册机、接第三方登录、抓别人的 token 都不再卡。
一、4 个参与角色
用户本人] C[Client
第三方 App] A[Authorization Server
授权服务器
如 Google / GitHub] R[Resource Server
资源服务器
存数据的那台]
U —>|1. 我同意| A C —>|2. 我要 token| A A —>|3. 给 token| C C —>|4. 拿 token 访问数据| R R —>|5. 验证 token 后给数据| C
| 角色 | 通俗解释 |
|---|---|
| Resource Owner(用户) | 就是你 |
| Client(客户端应用) | 想借你数据的第三方 App。两种:有后端的(Confidential Client,能藏密码)、纯前端/手机的(Public Client,藏不住密码) |
| Authorization Server(授权服务器) | 掌管你账号的平台:Google / GitHub / 微信 |
| Resource Server(资源服务器) | 存你真实数据的服务器(如 Google 存头像/邮箱的)。常常和授权服务器是同一家 |
二、10 个关键参数
2.1 client_id(客户端 ID)
App 注册后拿到的公开 ID,相当于应用的”身份证号”。公开,可以出现在 URL 里。
2.2 client_secret(客户端密钥)
App 的”密码”,只有 App 后端服务器知道,绝对不能暴露到前端。换 Token 时必须带上,授权服务器用它确认”是这个 App 自己来的,不是冒充的”。
🪤 手机 App / 纯前端网页没办法藏 secret,所以用 PKCE 代替(下一节讲)。
2.3 redirect_uri(回调地址)
授权完成后授权服务器把用户”送回”的地址。授权码 + state 会附在这个地址后面。
必须在授权服务器提前登记——不然黑客填自己的地址就能偷授权码。
2.4 scope(权限范围)
App 申请要访问哪些数据:
scope=email profile # 只要邮箱 + 基本信息scope=read:repo # 只读代码仓库scope=write:repo # 写代码仓库授权页面让你确认的那个权限列表就是 scope。
2.5 state(防伪标签)
App 发起授权时随机生成的字符串,授权服务器原样带回。App 拿到回调时对比 state 一致才接受——防 CSRF 攻击。
2.6 authorization_code(授权码)
用户同意授权后,授权服务器给 App 的”兑换券”。特点:
- 一次性(用完即废)
- 短时效(几分钟)
- 本身不是 Token(还要再换一步)
为什么不直接发 Token?因为授权码通过 URL 跳转传,可能被浏览器历史 / 服务器日志记录。授权码单独没价值,必须配合 client_secret 才能换 Token,安全性大大提高。
2.7 access_token(访问令牌)
最终目的地。访问资源服务器时放在请求头:
Authorization: Bearer <access_token>有效期短(几十分钟到几小时),过期失效。
2.8 refresh_token(刷新令牌)
access_token 过期后,用它换新的——不用用户重新登录。
- 有效期长(几天到几个月)
- 只在换 Token 时发给你
- 应该只在后端存储,不要发到前端
2.9 id_token(身份令牌)
OpenID Connect (OIDC) 扩展协议才有。
access_token= “进门证”id_token= “身份证”(JWT 格式,编码了邮箱/用户名/头像)
App 解码 id_token 知道”刚才授权的人是谁”。
2.10 grant_type(授权类型)
告诉授权服务器”我用哪种方式换 Token”。标准 OAuth2 有 4 种:
| grant_type | 用途 |
|---|---|
authorization_code | 最常用,本文重点 |
refresh_token | 用 refresh_token 续期 |
client_credentials | 机器对机器(没用户参与) |
password | 直接拿用户名密码换(已不推荐,不安全) |
三、完整流程示意
最常用的 Authorization Code Flow:
U->>C: 1. 点”用 GitHub 登录”
C->>U: 2. 跳转到授权 URL
带 client_id, redirect_uri, scope, state
U->>A: 3. 用户在授权页同意
A->>U: 4. 回调 redirect_uri
带 code + state
U->>C: 5. 浏览器跳回 App
App 拿到 code + state
C->>A: 6. App 后端调 /token
带 code + client_id + client_secret
A->>C: 7. 返回 access_token + refresh_token
C->>R: 8. 拿 access_token 访问数据
R->>C: 9. 返回用户数据
四、Public Client 必备:PKCE
Public Client(手机 App / SPA / CLI)藏不住 client_secret,所以不能用标准流程——黑客能截获 code 然后用偷来的 secret 换 token。
PKCE(Proof Key for Code Exchange) 解决这个问题:
4.1 流程
1. Client 生成随机字符串 code_verifier(43-128 字符)2. Client 算 code_challenge = base64url(sha256(code_verifier))3. Client 在授权 URL 里带 code_challenge4. 授权服务器记住这个 challenge,发授权码5. Client 拿 code 换 token 时,带原始 code_verifier6. 授权服务器对比 sha256(code_verifier) == challenge?7. 一致 → 发 token;不一致 → 拒绝关键:黑客即使偷到 code,也没有 code_verifier,换不到 token。
4.2 Microsoft Entra ID 的客户端分类
| 类型 | 需要 client_secret? | 必须用 PKCE? | 典型场景 |
|---|---|---|---|
| Public Client | 不需要 | 是 | 桌面程序、CLI、移动 App、SPA |
| Confidential Client | 需要 | 否(可选) | Web 应用(有后端)、后台服务 |
五、写注册机 / 抓 Token 的实战姿势
很多场景需要手动走 OAuth2 拿到 token,比如:
- 给 Outlook 邮箱写自动化脚本
- 抓 ChatGPT / Claude 的 access_token
- 给 CLI 工具加第三方账号支持
5.1 找到授权 URL
通过抓包(Charles / Reqable / 浏览器开发者工具)找到这种 URL:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize ?client_id=<某个 App 的 ID> &redirect_uri=https://localhost &scope=offline_access https://graph.microsoft.com/.default &state=random_string &response_type=code &code_challenge=<PKCE challenge> &code_challenge_method=S2565.2 手动模拟流程
人工流程:1. 把 URL 粘到浏览器 → 登录授权2. 跳转到 redirect_uri(带 code)→ 浏览器显示"无法连接"是正常的3. 从地址栏复制 code4. 用 curl 调 /token 端点换 token5.3 curl 示例
curl -X POST 'https://login.microsoftonline.com/common/oauth2/v2.0/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'grant_type=authorization_code' \ -d 'code=<刚才复制的 code>' \ -d 'client_id=<同 App ID>' \ -d 'redirect_uri=https://localhost' \ -d 'code_verifier=<原始 verifier>'返回的 JSON 里就有 access_token 和 refresh_token。
5.4 ThunderBird 等公共客户端的流程
1. 浏览器授权2. 回调 https://localhost:某端口3. ThunderBird 本地起 HTTP 服务监听这个端口4. 程序自动提取 code5. 通过 code 自动换 token和人工的区别只是第 4 步是程序自动化的。
六、易错点 / 重要细节
6.1 client_id 没有可以用公共的
很多大厂应用都有”公共 client_id”,比如某些命令行工具会用 Visual Studio Code 的 client_id 走授权——这是一个很好的解决思路(如果你只是想拿用户授权而不想注册自己的 App)。
6.2 多资源服务器靠 scope 区分
Outlook 的 scope 可能是:
https://graph.microsoft.com/.default offline_accesshttps://outlook.office.com/IMAP.AccessAsUser.All offline_access请求的资源服务器分别是 graph.microsoft.com 和 outlook.office.com。获取到的 access_token 不能混用——只能请求对应的资源服务器。
6.3 用户能扩展 scope(Confidential Client)
App 注册时申请了”读邮件”权限,用户在授权时可以勾选额外权限(让 App 也能读联系人)——这叫 Consent。
但 Public Client 不行,只能用 App 注册时申请的权限。
6.4 token 实际权限可能多于请求 scope
你请求 scope = "read"但 token 返回的 scope 包含 "read write delete"原因:用户之前对这个 App 同意过其它权限,token 里会包含已同意的所有权限。
💡 这意味着 scope 不能严格限制 token 的实际权限——只要权限出现在 token 的 scp claim 里,你就能用。这也是为什么很多人觉得”精确 scope 没意义”。
6.5 redirect_uri 伪造(针对 CLI / 公共客户端)
如果你在写注册机,redirect_uri 可以随意填(在自己控制流程的情况下):
https://localhosthttps://localhost:1455/auth/callback # Codex 用的https://localhost:<随便端口> # ThunderBird 之类核心是拿到 code——浏览器跳转到 https://localhost 显示”无法连接”无所谓,你看地址栏的 code 就行。
🪤 但 redirect_uri 必须和 App 在授权服务器登记的一致,否则授权服务器拒绝。这也是为什么用大厂的公共 client_id 时,redirect_uri 也得是大厂登记过的那些。
七、refresh_token 玩法
refresh_token 是注册机的核心——拿到一次,长期复用:
# 用 refresh_token 换新的 access_tokencurl -X POST 'https://login.microsoftonline.com/common/oauth2/v2.0/token' \ -d 'grant_type=refresh_token' \ -d 'refresh_token=<refresh_token>' \ -d 'client_id=<client_id>'每次 access_token 过期就调一次,整个注册机就能几个月不用重新登录。
⚠️ refresh_token 滥用会触发风控(IP 频繁切换、User-Agent 异常)。
八、常见漏洞和防御
| 漏洞 | 防御 |
|---|---|
| CSRF(伪造授权请求) | state 参数 |
| 授权码截获 | PKCE |
| Token 泄漏 | 短 TTL + refresh_token 后端存储 |
| redirect_uri 篡改 | 授权服务器严格匹配 |
| Open Redirect | redirect_uri 白名单 |
| Mix-up Attack | iss 参数 + 严格 client 配置 |
九、几条实用经验
- 理解流程比记 API 重要:所有 OAuth2 实现都是 4 角色 + 10 参数的变形。
- 看抓包就懂:拿浏览器开发者工具看一次”用 Google 登录”的完整流程,所有概念都活了。
- Public Client 永远用 PKCE——是安全标配。
- state 不能省,省了就是 CSRF 漏洞。
- 写注册机用大厂公共 client_id:省去注册 App 的麻烦,redirect_uri 也用大厂的。
- refresh_token 是续命神器:拿到一次能用几个月,但要小心风控。
- scope 拿大不拿小:反正最终 token 包含的权限不取决于这次请求。
把这套理解透,再去看任何”接第三方登录""注册机""自动化登录”的需求,都是清晰的逐步操作,不再是黑盒。