🔑 授权码和访问令牌的获取
🎯 从哪里获取?
💡 获取地点说明
- 授权码:从授权服务器获取(用户登录后)
- 访问令牌:从授权服务器的令牌端点获取
- 刷新令牌:通常与访问令牌一起提供
📝 具体获取流程
第一步:获取授权码
GET /authorize?response_type=code
&client_id=your_client_id
&redirect_uri=https://your-app.com/callback
&scope=read write
&state=random_string
&resource=https://mcp.example.com HTTP/1.1
Host: auth.example.com
用户登录成功后,授权服务器会重定向到你的应用,URL中包含授权码:
https://your-app.com/callback?code=AUTHORIZATION_CODE&state=random_string
第二步:用授权码换取访问令牌
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https://your-app.com/callback
&client_id=your_client_id
&client_secret=your_client_secret
&resource=https://mcp.example.com
授权服务器返回访问令牌:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200...",
"scope": "read write"
}
🤔 为什么要分两次?不能直接获取令牌吗?
OAuth 采用先获取授权码(Authorization Code)、再交换访问令牌(Access
Token)的两步流程(授权码模式),主要是为了平衡安全性和功能性。这种设计解决了以下几个关键问题:
⚠️ 1. 防止令牌泄露(主要针对Web应用)
- 问题:在传统Web应用中,OAuth的重定向是通过浏览器完成的。如果直接返回访问令牌(如隐式模式),令牌会暴露在URL或浏览器历史记录中,可能被恶意脚本或中间人攻击窃取。
- 解决方案:授权码是一个短期有效的临时凭证,即使被拦截,攻击者也无法直接用授权码获取资源(还需配合客户端密钥交换)。而令牌通过后端信道(服务端到服务端)传输,避免暴露给浏览器。
🔒 2. 客户端身份验证
- 授权码模式要求客户端(应用服务器)在交换令牌时验证身份(如
client_id +
client_secret)。这确保了只有合法的客户端能换取令牌,而拦截授权码的第三方无法完成这一步(除非他们同时窃取了客户端密钥)。
🛡️ 3. 减少令牌传输环节
令牌只在以下两个安全信道中传输:
- 授权服务器 → 客户端后端(通过HTTPS)
- 客户端后端 → 资源服务器(通过HTTPS)
避免了令牌经过用户浏览器或移动端App(可能被恶意应用读取)。
💡 生活比喻
这就像银行的双重验证:
- 第一次:你出示身份证,银行确认你的身份(获得授权码)
- 第二次:你提供银行卡和密码,银行给你现金(获得访问令牌)
- 为什么分两次:如果银行直接给你现金,万一身份证被偷了,钱就没了
🔐 安全机制详解
| 阶段 |
安全措施 |
目的 |
| 授权码获取 |
用户必须亲自登录 重定向到可信域名 |
确保用户真实授权 |
| 令牌交换 |
需要客户端密钥 授权码一次性使用 |
防止授权码被滥用 |
| 令牌使用 |
HTTPS传输 令牌有时效性 |
保护令牌安全 |
🔍 授权服务器发现
MCP服务器需要告诉客户端"我的授权服务器在哪里":
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp.example.com",
resource="https://mcp.example.com/.well-known/oauth-authorization-server"
客户端收到这个响应后,就知道去哪里申请授权了。
🎫 访问令牌的使用
获得访问令牌后,客户端需要这样使用:
GET /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
重要:令牌必须放在HTTP头部的Authorization字段中,不能放在URL里!