Daimon's Blog
主页 归档 关于 RSS 探针 常用工具
主页
归档
关于
RSS
探针
常用工具

OAuth2 标准流程逐字段拆解:写注册机 / 接第三方登录必看

daimon daimon 2025-06-28 #OAuth2#授权#登录#逆向#PKCE

OAuth2 是现在第三方登录、API 授权、单点登录的事实标准。所有”用微信/GitHub/Google 登录”的按钮背后都是这套协议。但大多数文档把它写得太学术——一堆”授权服务器""资源所有者”看完更晕。

这篇用人话把 OAuth2 的4 个角色 + 10 个关键参数 + PKCE 扩展 + 易错点 全梳理一遍,理解之后写注册机、接第三方登录、抓别人的 token 都不再卡。

一、4 个参与角色#

graph LR U[Resource Owner
用户本人] 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 申请要访问哪些数据:

1
scope=email profile # 只要邮箱 + 基本信息
2
scope=read:repo # 只读代码仓库
3
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(访问令牌)#

最终目的地。访问资源服务器时放在请求头:

1
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:

sequenceDiagram participant U as 用户浏览器 participant C as Client App participant A as 授权服务器 participant R as 资源服务器

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
1. Client 生成随机字符串 code_verifier(43-128 字符)
2
2. Client 算 code_challenge = base64url(sha256(code_verifier))
3
3. Client 在授权 URL 里带 code_challenge
4
4. 授权服务器记住这个 challenge,发授权码
5
5. Client 拿 code 换 token 时,带原始 code_verifier
6
6. 授权服务器对比 sha256(code_verifier) == challenge?
7
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:

1
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
2
?client_id=<某个 App 的 ID>
3
&redirect_uri=https://localhost
4
&scope=offline_access https://graph.microsoft.com/.default
5
&state=random_string
6
&response_type=code
7
&code_challenge=<PKCE challenge>
8
&code_challenge_method=S256

5.2 手动模拟流程#

1
人工流程:
2
1. 把 URL 粘到浏览器 → 登录授权
3
2. 跳转到 redirect_uri(带 code)→ 浏览器显示"无法连接"是正常的
4
3. 从地址栏复制 code
5
4. 用 curl 调 /token 端点换 token

5.3 curl 示例#

Terminal window
1
curl -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 等公共客户端的流程#

1
1. 浏览器授权
2
2. 回调 https://localhost:某端口
3
3. ThunderBird 本地起 HTTP 服务监听这个端口
4
4. 程序自动提取 code
5
5. 通过 code 自动换 token

和人工的区别只是第 4 步是程序自动化的。

六、易错点 / 重要细节#

6.1 client_id 没有可以用公共的#

很多大厂应用都有”公共 client_id”,比如某些命令行工具会用 Visual Studio Code 的 client_id 走授权——这是一个很好的解决思路(如果你只是想拿用户授权而不想注册自己的 App)。

6.2 多资源服务器靠 scope 区分#

Outlook 的 scope 可能是:

1
https://graph.microsoft.com/.default offline_access
2
https://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 可以随意填(在自己控制流程的情况下):

1
https://localhost
2
https://localhost:1455/auth/callback # Codex 用的
3
https://localhost:<随便端口> # ThunderBird 之类

核心是拿到 code——浏览器跳转到 https://localhost 显示”无法连接”无所谓,你看地址栏的 code 就行。

🪤 但 redirect_uri 必须和 App 在授权服务器登记的一致,否则授权服务器拒绝。这也是为什么用大厂的公共 client_id 时,redirect_uri 也得是大厂登记过的那些。

七、refresh_token 玩法#

refresh_token 是注册机的核心——拿到一次,长期复用:

Terminal window
1
# 用 refresh_token 换新的 access_token
2
curl -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 Redirectredirect_uri 白名单
Mix-up Attackiss 参数 + 严格 client 配置

九、几条实用经验#

  • 理解流程比记 API 重要:所有 OAuth2 实现都是 4 角色 + 10 参数的变形。
  • 看抓包就懂:拿浏览器开发者工具看一次”用 Google 登录”的完整流程,所有概念都活了。
  • Public Client 永远用 PKCE——是安全标配。
  • state 不能省,省了就是 CSRF 漏洞。
  • 写注册机用大厂公共 client_id:省去注册 App 的麻烦,redirect_uri 也用大厂的。
  • refresh_token 是续命神器:拿到一次能用几个月,但要小心风控。
  • scope 拿大不拿小:反正最终 token 包含的权限不取决于这次请求。

把这套理解透,再去看任何”接第三方登录""注册机""自动化登录”的需求,都是清晰的逐步操作,不再是黑盒。

Claude Code / Codex / Gemini CLI 三件套:装、配、用
解 BL → Root → KernelSU / Magisk:把安卓变成生产力机
本博客所有文章除特别声明外,均遵循 CC BY-NC-SA 4.0 协议,转载请注明出处。
博客框架 Astro & Fuwari
冀ICP备20260167号
1
一、4 个参与角色
2
二、10 个关键参数
2.1 client_id(客户端 ID)
2.2 client_secret(客户端密钥)
2.3 redirect_uri(回调地址)
2.4 scope(权限范围)
2.5 state(防伪标签)
2.6 authorization_code(授权码)
2.7 access_token(访问令牌)
2.8 refresh_token(刷新令牌)
2.9 id_token(身份令牌)
2.10 grant_type(授权类型)
3
三、完整流程示意
4
四、Public Client 必备:PKCE
4.1 流程
4.2 Microsoft Entra ID 的客户端分类
5
五、写注册机 / 抓 Token 的实战姿势
5.1 找到授权 URL
5.2 手动模拟流程
5.3 curl 示例
5.4 ThunderBird 等公共客户端的流程
6
六、易错点 / 重要细节
6.1 client_id 没有可以用公共的
6.2 多资源服务器靠 scope 区分
6.3 用户能扩展 scope(Confidential Client)
6.4 token 实际权限可能多于请求 scope
6.5 redirect_uri 伪造(针对 CLI / 公共客户端)
7
七、refresh_token 玩法
8
八、常见漏洞和防御
9
九、几条实用经验