文章目录
- [OAuth 登录](#OAuth 登录)
-
- OAuth登录完整流程
- [OAuth 登录和普通登录有什么区别?](#OAuth 登录和普通登录有什么区别?)
- 举例
-
- [OAuth 中的四个角色](#OAuth 中的四个角色)
- 整个流程
- [第一步:MyAgent 发起授权请求](#第一步:MyAgent 发起授权请求)
-
- [MyAgent 还没登录,OpenAI 怎么就知道它是谁?](#MyAgent 还没登录,OpenAI 怎么就知道它是谁?)
- 为什么点击登录会打开浏览器?
- 第二步:用户在服务商页面登录
- 第三步:生成授权码,并通过回调返回
- [第四步:为什么不直接返回 Access Token?](#第四步:为什么不直接返回 Access Token?)
- [第五步:MyAgent 使用授权码换 Token](#第五步:MyAgent 使用授权码换 Token)
-
- [四、为什么还需要 Refresh Token?](#四、为什么还需要 Refresh Token?)
- [PKCE 又是什么?为什么有授权码还不够?](#PKCE 又是什么?为什么有授权码还不够?)
- 小测试
OAuth 登录
OAuth 登录,就是让你通过一个已经有的账号(比如
Google、GitHub、OpenAI),授权另一个应用使用某些功能,而不需要把账号密码告诉那个应用
OAuth 的核心是:用户在官方平台确认授权,第三方应用获得有限权限的 Token,而不是拿到用户密码。
OAuth 是一个授权协议
- 认证(Authentication):确认你是谁。
- 授权(Authorization):确认你允许某个应用做什么。
一、OAuth 是什么?假设你正在使用一个第三方软件 A,它需要访问你的 GitHub 仓库。
通常有两种方式:
方式一:直接提供账号密码
你把 GitHub 的账号和密码告诉软件 A,软件 A 使用你的账号密码访问 GitHub。
问题是:软件 A 知道了你的密码,不安全。
方式二:使用 OAuth 授权
你不需要告诉软件 A 密码,而是:
- 软件 A 让你跳转到 GitHub 官方登录页面。
- 你在 GitHub 页面登录账号。
- GitHub 询问你:「是否允许软件 A 访问你的某些仓库?」
- 你点击同意。
- GitHub 给软件 A 一个访问凭证(Access Token)。
- 软件 A 使用这个凭证访问你授权的 GitHub 资源。 关键区别:软件 A 拿到的是访问凭证,不是你的 GitHub 密码。
OAuth登录完整流程



OAuth 登录和普通登录有什么区别?

举例
OAuth 授权码模式(Authorization Code Flow)的核心就是:
MyAgent 先告诉 OpenAI 自己是哪个应用、想要什么权限、授权后回到哪里 → 用户在 OpenAI 登录并同意授权 → 浏览器把授权码交给 MyAgent → MyAgent 使用授权码和 PKCE 验证材料换取 Access Token → MyAgent 使用 Token 调用获准的服务->Token过期后用 Refresh Token 换新的
用一个完整场景解释: 你开发了一个 MyAgent,用户希望通过自己的 ChatGPT账号登录,让 MyAgent 使用获得授权的 GPT 模型。(MyAgent 想使用你的 OpenAI 账号,但它不能直接拿你的账号密码,所以它要先去 OpenAI 申请授权。)
OAuth 中的四个角色
-
用户(Resource Owner)
就是拥有 ChatGPT 账号的你,决定是否允许 MyAgent 获得访问权限。
-
客户端(Client)
就是你开发的 MyAgent。它希望代表用户调用获得授权的服务。
-
授权服务器(Authorization Server)
负责用户登录、确认授权、签发授权码及 Token。
-
资源服务器(Resource Server)
真正提供 API 服务,例如执行获得授权的 GPT 模型请求。它验证 Access Token 及权限。
整个流程


第一步:MyAgent 发起授权请求
假设你在 MyAgent 中点击「使用 OpenAI 登录」。 MyAgent 就会让浏览器打开: 前面的地址
https://auth.example.com/authorize是 OpenAI 提供的授权入口(这里是虚构的)。后面的参数,就是 MyAgent 告诉 OpenAI 的信息。
点击「Login with OpenAI」后,软件让浏览器 打开OpenAI 的 OAuth 授权地址。
例如(以下全部为虚构地址和示例参数):
https://auth.example.com/authorize
?response_type=code
&client_id=myagent123
&redirect_uri=http://127.0.0.1:3000/callback
&scope=model.access
&state=xyz789
&code_challenge=HASH_VALUE
&code_challenge_method=S256
| 参数 | 作用 |
|---|---|
| response_type=code | 告诉服务商我要使用授权码模式 |
| client_id | 告诉服务商申请授权的是哪个应用 |
| redirect_uri | 授权完成后,浏览器应该跳回哪个地址 |
| scope | 申请哪些访问权限 |
| state | 防止别人伪造授权回调 |
| code_challenge | PKCE 验证材料,防止授权码被别人冒用 |
| code_challenge_method=S256 | 这个结果是通过 SHA-256 计算的 |
这里有个关键点: 用户还没登录,MyAgent 就已经告诉服务商"我是谁"和"授权完成后该返回哪里"。服务商会根据已注册或支持的客户端信息检查 client_id 和回调地址
response_type=code意思是:
「OpenAI,我希望你采用授权码模式。用户同意授权后,先给我一个授权码。」
为什么不直接给 Access Token?
因为授权码可以先交给 MyAgent,再由 MyAgent 通过另外一次请求兑换 Access Token,并完成相应的安全验证。
你暂时记住:
- code:临时授权码。
- access_token:真正用于访问 API 的凭证。
client_id=myagent123这是最重要的参数之一。
假设你开发了一个叫 MyAgent 的应用。
你想让 MyAgent 支持「使用 OpenAI 登录」。
那么通常需要先到 OpenAI 的开发者平台注册你的应用(这里假设平台支持这种注册方式)。
注册后,OpenAI 给你一个编号:
应用名称:MyAgent
应用编号:myagent123
这个编号就是 client_id。
以后 MyAgent 发起授权请求时,带上:
client_id=myagent123
OpenAI 收到请求就知道:
「哦,这是编号为 myagent123 的应用在申请授权。」
注意两点:
第一,client_id 是应用的编号,不是你个人账号的编号。
第二,client_id 不属于密码。 任何人都可能看到这个编号,它不能单独证明请求方一定是合法应用。
redirect_uri=http://127.0.0.1:3000/callback这个参数告诉 OpenAI:
「用户完成授权以后,请让浏览器跳转到这个地址。」
/callback
是 MyAgent 专门用来接收授权结果的路径。
假设 MyAgent 在你的电脑上启动了一个本地服务,监听 3000 端口。
当你完成登录和授权后,OpenAI 就让浏览器跳转到:
http://127.0.0.1:3000/callback?code=abc123
这样,浏览器就会将授权码送到 MyAgent 的本地服务。
scope=model.access意思是:
「我想申请哪些权限?」
举个例子:
假设 OpenAI 提供以下权限:
权限(假设) 作用
model.access 调用 AI 模型
profile.read 读取用户基本信息
files.read 读取用户文件
MyAgent 如果只需要调用 AI 模型,就可以申请:
scope=model.access
当然,这些权限名称都是我们假设的。实际有哪些权限,取决于服务商。
state=xyz789这个参数是为了防止别人伪造授权回调。
假设 MyAgent 发起登录时,随机生成了:
state=xyz789
MyAgent 会把这个值保存在本地。
当 OpenAI 授权完成后,浏览器跳转到:
http://127.0.0.1:3000/callback
?code=abc123
&state=xyz789
MyAgent 收到后就会检查:
自己保存的 state:xyz789
收到的 state: xyz789
两者相同 ✓
如果不一样,就拒绝这次回调。
你可以把 state 理解为 MyAgent 给这一次登录申请设置的随机编号。
注意:真正使用时,每次登录都应该重新随机生成,而不是一直使用 xyz789。
code_challenge=HASH_VALUE这个参数涉及一个叫 PKCE 的安全机制。
你可以先理解一个问题:
假设 MyAgent 完成授权后,OpenAI 返回:
code=abc123
如果这个授权码被别人偷走了怎么办?
为了降低这个风险,MyAgent 会在发起登录之前做两件事。
第一步:生成一个随机秘密值。
code_verifier = 一长串随机字符
这个值保存在 MyAgent 本地,不会在最初的授权请求中发送出去。
第二步:根据这个秘密值计算一个结果。
code_challenge = SHA256(code_verifier)
这里为方便理解省略了 Base64URL 编码步骤。
MyAgent 把计算结果 code_challenge 告诉 OpenAI。
等到 MyAgent 拿着授权码去换 Access Token 时,还必须提供最初那个 code_verifier。
OpenAI 就能检查:
「这个人提供的秘密值,是否能够计算出之前提交的 code_challenge?」
如果一致,才可能继续发放 Token。
因此,别人即使偷走授权码,如果不知道 code_verifier,一般也无法成功兑换 Access Token。
code_challenge_method=S256这个参数比较简单。
它告诉 OpenAI:
「我是用 SHA-256 计算 code_challenge 的。」
实际计算方式是:
S256 就是这种计算方式的名称
MyAgent 还没登录,OpenAI 怎么就知道它是谁?
这里其实存在两个不同的身份:
- 应用身份:MyAgent 是哪个应用?
- 用户身份:当前是谁在使用 MyAgent?
这两个身份的确定时间不同。

为什么点击登录会打开浏览器?
因为需要通过 OpenAI 官方控制的网页验证你的身份,并完成授权。
服务商通常通过网页让用户登录和确认授权,所以需要浏览器访问这个网址。
登录界面就是 OpenAI 自己开发的前端页面,浏览器负责把这个页面展示出来。
浏览器输入网址,发送的是什么请求?get
假设你在浏览器地址栏输入:
https://example.com/login 然后按回车。
浏览器通常会自动发送一个 HTTP GET请求:
GET /login HTTP/1.1
Host: example.com
这个请求被发送到 example.com 的服务器。
浏览器通过 GET 向服务器请求网页资源,获取 HTML、CSS、JavaScript 后,由浏览器运行这些前端代码并显示界面。
Nginx 本身没有前端代码,但你可以把打包好的前端代码放在服务器的某个目录下,然后配置 Nginx 去这个目录查找文件。
Nginx 不需要懂 Vue、React,它只需要知道:前端文件存放在哪里,收到请求后应该返回哪个文件。
在 Vue、React 前后端分离架构中,前端代码本身不会直接接收浏览器发出的 HTTP GET 请求。
你可以这样理解:
- Nginx:接收浏览器发送的 GET 请求。
- 前端代码(Vue、React):由浏览器下载并执行,负责显示页面、处理用户交互和切换路由。
第二步:用户在服务商页面登录
浏览器打开刚才的授权链接。
你看到的是服务商自己的登录页面,而不是 MyAgent 收集账号密码。
用户完成登录后,服务商知道:
登录的用户:张三
申请授权的应用:MyAgent
申请的权限:model.access(示例)
如果用户同意,服务商就会记录这次授权,并签发一个临时授权码。
这里要注意:登录成功不一定等于授权成功。用户可能拒绝授权,服务商也可能因策略限制而拒绝请求。
第三步:生成授权码,并通过回调返回
假设服务商生成:
Authorization Code = ABC123
它怎么把授权码发给 MyAgent?
答案就是Callback(回调)。
在 OAuth 授权码模式中,授权服务器通常给浏览器返回一个HTTP 重定向响应:
HTTP/1.1 302 Found
Location: http://127.0.0.1:3000/callback?code=ABC123\&state=xyz789
浏览器看到 Location,就自动访问这个地址。
注意:
不是服务商服务器直接给 MyAgent 发一条 TCP消息,而是浏览器按照重定向地址去访问 MyAgent。 如果 MyAgent 在你的电脑上运行,并监听:
127.0.0.1:3000
那么浏览器向这个本地端口发送 HTTP 请求,MyAgent 就可以收到: code = ABC123 state = xyz789
所以回调的本质就是: 授权服务器通知浏览器跳转到应用提前指定的地址,并通过这次跳转把授权结果带回来。
第四步:为什么不直接返回 Access Token?
这是整个 OAuth 设计最重要的安全思想。 假设服务商直接在浏览器跳转 URL 中返回 Access Token:
http://127.0.0.1:3000/callback
?access_token=SECRET_TOKEN 那么一旦这个 Token 因浏览器历史、日志、恶意软件或其他渠道泄漏,攻击者就可能直接拿去请求 API。 所以授权码模式采用两次交换:
第五步:MyAgent 使用授权码换 Token
MyAgent 接收到授权码后,会直接向服务商的 Token Endpoint 发送 HTTPS 请求。
示例:
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
client_id=myagent123&
code=ABC123&
redirect_uri=http://127.0.0.1:3000/callback&
code_verifier=ORIGINAL_RANDOM_SECRET
其中最重要的是: code=ABC123:证明 MyAgent 已经通过前面的授权流程拿到了授权码。 code_verifier:PKCE
使用的原始随机秘密,证明兑换授权码的客户端掌握了发起授权请求时生成的验证材料。
code_verifier:证明兑换授权码的一方掌握最初生成的 PKCE 秘密值。
第一次用 code_challenge 提前设定验证条件,第二次用 code_verifier 证明自己掌握原始秘密,两次匹配成功,才能通过 PKCE 验证。
对于需要客户端密钥的机密客户端,还需要相应的客户端认证,例如
client_secret。本地 CLI 这种公开客户端通常不能安全地保存一个固定客户端密钥,因此经常使用 PKCE。PKCE 全称是 Proof Key for Code Exchange,中文可以理解为"授权码交换证明机制"。
服务商检查通过后,返回类似: { "access_token": "ACCESS_123", "refresh_token":
"REFRESH_456", "token_type": "Bearer", "expires_in": 3600 }
以上值都只是示例;Refresh Token 不保证每次都下发。 这四个字段分别表示:
| 字段 | 意思 |
|---|---|
| access_token | 真正调用受保护 API 使用的凭证 |
| refresh_token | 用来获取新 Access Token 的凭证 |
| token_type | Token 的使用类型,示例是 Bearer |
| expires_in | Access Token 的有效期(单位:秒) |
至此,MyAgent 才真正拿到了可用于获授权 API 请求的 Access Token
四、为什么还需要 Refresh Token?



PKCE 又是什么?为什么有授权码还不够?


小测试


Authorization Code 是一次性兑换凭证,Callback 是让授权结果回到应用的机制,Access Token 是访问服务的凭证,Refresh Token 是更新访问凭证的工具,而 PKCE 是保证授权码兑换过程安全的重要机制。






















