OAuth2 标准流程逐字段拆解:写注册机 / 接第三方登录必看
OAuth2 是现在第三方登录、API 授权、单点登录的事实标准。所有”用微信/GitHub/Google 登录”的按钮背后都是这套协议。但大多数文档把它写得太学术——一堆”授权服务器""资源所有者”看完更晕。
这篇用人话把 OAuth2 的4 个角色 + 10 个关键参数 + PKCE 扩展 + 易错点 全梳理一遍,理解之后写注册机、接第三方登录、抓别人的 token 都不再卡。
一、4 个参与角色#
| 角色 | 通俗解释 |
|---|---|
| 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 申请要访问哪些数据:
1scope=email profile # 只要邮箱 + 基本信息2scope=read:repo # 只读代码仓库3scope=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(访问令牌)#
最终目的地。访问资源服务器时放在请求头:
1Authorization: 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:
四、Public Client 必备:PKCE#
Public Client(手机 App / SPA / CLI)藏不住 client_secret,所以不能用标准流程——黑客能截获 code 然后用偷来的 secret 换 token。
PKCE(Proof Key for Code Exchange) 解决这个问题:
4.1 流程#
11. Client 生成随机字符串 code_verifier(43-128 字符)22. Client 算 code_challenge = base64url(sha256(code_verifier))33. Client 在授权 URL 里带 code_challenge44. 授权服务器记住这个 challenge,发授权码55. Client 拿 code 换 token 时,带原始 code_verifier66. 授权服务器对比 sha256(code_verifier) == challenge?77. 一致 → 发 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:
1https://login.microsoftonline.com/common/oauth2/v2.0/authorize2 ?client_id=<某个 App 的 ID>3 &redirect_uri=https://localhost4 &scope=offline_access https://graph.microsoft.com/.default5 &state=random_string6 &response_type=code7 &code_challenge=<PKCE challenge>8 &code_challenge_method=S2565.2 手动模拟流程#
1人工流程:21. 把 URL 粘到浏览器 → 登录授权32. 跳转到 redirect_uri(带 code)→ 浏览器显示"无法连接"是正常的43. 从地址栏复制 code54. 用 curl 调 /token 端点换 token5.3 curl 示例#
1curl -X POST 'https://login.microsoftonline.com/common/oauth2/v2.0/token' \2 -H 'Content-Type: application/x-www-form-urlencoded' \3 -d 'grant_type=authorization_code' \4 -d 'code=<刚才复制的 code>' \5 -d 'client_id=<同 App ID>' \6 -d 'redirect_uri=https://localhost' \7 -d 'code_verifier=<原始 verifier>'返回的 JSON 里就有 access_token 和 refresh_token。
5.4 ThunderBird 等公共客户端的流程#
11. 浏览器授权22. 回调 https://localhost:某端口33. ThunderBird 本地起 HTTP 服务监听这个端口44. 程序自动提取 code55. 通过 code 自动换 token和人工的区别只是第 4 步是程序自动化的。
六、易错点 / 重要细节#
6.1 client_id 没有可以用公共的#
很多大厂应用都有”公共 client_id”,比如某些命令行工具会用 Visual Studio Code 的 client_id 走授权——这是一个很好的解决思路(如果你只是想拿用户授权而不想注册自己的 App)。
6.2 多资源服务器靠 scope 区分#
Outlook 的 scope 可能是:
1https://graph.microsoft.com/.default offline_access2https://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#
1你请求 scope = "read"2但 token 返回的 scope 包含 "read write delete"原因:用户之前对这个 App 同意过其它权限,token 里会包含已同意的所有权限。
💡 这意味着 scope 不能严格限制 token 的实际权限——只要权限出现在 token 的 scp claim 里,你就能用。这也是为什么很多人觉得”精确 scope 没意义”。
6.5 redirect_uri 伪造(针对 CLI / 公共客户端)#
如果你在写注册机,redirect_uri 可以随意填(在自己控制流程的情况下):
1https://localhost2https://localhost:1455/auth/callback # Codex 用的3https://localhost:<随便端口> # ThunderBird 之类核心是拿到 code——浏览器跳转到 https://localhost 显示”无法连接”无所谓,你看地址栏的 code 就行。
🪤 但 redirect_uri 必须和 App 在授权服务器登记的一致,否则授权服务器拒绝。这也是为什么用大厂的公共 client_id 时,redirect_uri 也得是大厂登记过的那些。
七、refresh_token 玩法#
refresh_token 是注册机的核心——拿到一次,长期复用:
1# 用 refresh_token 换新的 access_token2curl -X POST 'https://login.microsoftonline.com/common/oauth2/v2.0/token' \3 -d 'grant_type=refresh_token' \4 -d 'refresh_token=<refresh_token>' \5 -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 包含的权限不取决于这次请求。
把这套理解透,再去看任何”接第三方登录""注册机""自动化登录”的需求,都是清晰的逐步操作,不再是黑盒。