前言
在日常开发中,我们几乎每天都在接触 OAuth 2.0——微信登录、GitHub 授权、第三方应用获取用户信息……它已经成为互联网授权的事实标准。但很多开发者对它的理解停留在"点一下授权按钮"的表层,对于它为什么这么设计、四种授权模式分别适用于什么场景,往往一知半解。
本文将从最朴素的设计思想出发,带你彻底理解 OAuth 2.0 的本质,并详细拆解四种授权模式的完整流程与适用场景。读完这篇,你不仅能说清 OAuth 是什么,还能在实际项目中选出最合适的授权方案。
一、OAuth 2.0 到底解决了什么问题?
1.1 一个生活化的类比:快递员与小区门禁
我们先抛开技术术语,看一个每个人都能理解的场景:
你住在一个需要密码才能进入的小区,每天都有快递员来送货。怎么让快递员进来?
- 方案一:把家门密码告诉快递员 问题很明显:快递员知道了你的密码,就拥有了和你完全一样的权限;哪天你不想让他进来了,只能改密码,还得通知所有其他快递员,麻烦又不安全。
- 方案二:每次快递到了都下楼开门 安全是安全了,但体验极差,一天十趟快递谁受得了。
那有没有一种方案,既能让快递员进入小区送货,又不泄露你的密码,还能随时收回权限、限制他只能送货不能干别的?
当然有,这就是 OAuth 的设计初衷。
1.2 授权机制的核心设计
我们可以设计这样一套机制:
- 门禁系统增加一个"获取授权"按钮,快递员先按按钮发起申请
- 你的手机收到授权请求,显示快递员姓名、工号、所属公司,你点击确认
- 门禁系统生成一个短期有效的令牌(token) ,交给快递员
- 快递员凭令牌进入小区,令牌只有送货权限,且 7 天后自动失效
这样一来:
- 快递员不需要知道你的密码
- 你可以随时撤销令牌,让它立即失效
- 令牌有权限范围,只能用来进小区送货,干不了别的
- 令牌短期有效,即使丢了风险也可控
把这个例子搬到互联网世界,就是完整的 OAuth 2.0 模型:
- 小区 = 资源服务器(比如微信,存储着你的好友、头像等数据)
- 快递员 = 第三方应用(比如某个网站,想要获取你的微信头像)
- 你 = 资源所有者(用户本人,决定是否授权)
- 门禁系统 = 授权服务器(负责颁发令牌)
一句话总结:OAuth 2.0 是一种授权机制,数据所有者同意后,系统向第三方应用颁发一个短期、有限权限的令牌,第三方应用凭令牌访问数据,全程不接触用户密码。
1.3 令牌(Token) vs 密码(Password)
很多人会问:令牌不就是另一种密码吗?还真不是,两者有三个本质区别:
| 特性 | 令牌(Token) | 密码(Password) |
|---|---|---|
| 有效期 | 短期,到期自动失效,用户无法修改 | 长期有效,用户不修改就一直可用 |
| 可撤销性 | 资源所有者可随时撤销,立即失效 | 一般不允许被他人撤销 |
| 权限范围 | 可精细控制(只读、读写、部分数据) | 通常是完整权限 |
这三点设计保证了:第三方应用能拿到够用的权限,同时一切都在用户的掌控之中,不会因为一次授权就"家门大开"。
⚠️ 注意:令牌本身必须保密。一旦泄露,后果和泄露密码一样严重。这也是为什么令牌有效期通常设置得很短——降低泄漏后的风险窗口。
二、前置知识:客户端备案
在讲四种授权模式之前,先明确一个前提:
任何第三方应用在申请令牌之前,都必须先到授权平台备案。
备案后平台会颁发两个身份凭证:
- client_id(客户端 ID) :公开信息,用来标识应用身份
- client_secret(客户端密钥) :保密信息,只能在服务端使用,用来证明应用身份
这是防止令牌滥用的第一道防线——没有备案的应用,根本拿不到令牌。
三、四种授权模式全解析
OAuth 2.0 标准(RFC 6749)定义了四种获取令牌的方式,分别对应不同的业务场景。下面我们逐一拆解。
3.1 授权码模式(Authorization Code)
这是最常用、安全性最高的模式,也是 Web 应用的首选方案。
适用于:有后端服务的 Web 应用。核心思想是"授权码走前端,令牌存后端",前后端分离避免令牌直接暴露在浏览器中。
完整流程四步走
第一步:引导用户跳转到授权页
第三方网站(A 站)提供一个授权链接,用户点击后跳转到授权服务器(B站):
https://b.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
参数说明:
response_type=code:表示要求返回授权码client_id:告诉 B 站是谁在请求redirect_uri:用户同意/拒绝后跳转的地址scope:申请的权限范围,比如read只读
第二步:用户确认授权,返回授权码
用户在 B 站登录后,会看到授权确认页面,显示 A 站想要获取哪些权限。用户点击同意后,B 站会自动跳回 redirect_uri 指定的地址,并带上授权码:
https://a.com/callback?code=AUTHORIZATION_CODE
这里的 code 就是授权码。注意:授权码是一次性的,而且有效期很短。
第三步:后端用授权码换取令牌
A 站的后端拿到授权码后,在服务端向 B 站请求令牌:
https://b.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
关键参数:
client_secret:客户端密钥,证明 A 站的真实身份,必须在后端请求,绝不能暴露在前端grant_type=authorization_code:表示采用授权码模式code:上一步拿到的授权码
第四步:授权服务器返回令牌
B 站验证通过后,返回 JSON 格式的令牌信息:
{
"access_token": "ACCESS_TOKEN",
"token_type": "bearer",
"expires_in": 2592000,
"refresh_token": "REFRESH_TOKEN",
"scope": "read"
}
至此,A 站后端就拿到了访问令牌。整个过程中,令牌从未经过浏览器,最大程度降低了泄漏风险。
💡 为什么多一步授权码更安全?
很多人会疑惑:为什么不直接返回令牌,非要多一道授权码?
原因在于:浏览器跳转是不安全的,容易被中间人窃取。如果直接返回令牌,令牌就会暴露在浏览器历史、日志里。
而授权码模式下,前端只拿到一次性的授权码,真正的令牌由后端通过安全的服务端通信获取。即使授权码被截获,没有 client_secret 也换不出令牌。
3.2 隐藏式(Implicit)
适用于:纯前端应用(没有后端),比如单页应用 SPA。
这种模式没有授权码这个中间步骤,直接向前端返回令牌,所以叫"隐藏式"(也叫隐式授权)。
流程两步走
第一步:跳转到授权页申请令牌
https://b.com/oauth/authorize?
response_type=token&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
注意 response_type=token,表示直接返回令牌。
第二步:授权后直接返回令牌
用户同意授权后,B 站跳回回调地址,令牌放在 URL 的**锚点(# 后面)**中:
https://a.com/callback#token=ACCESS_TOKEN
这里有个细节:令牌放在锚点而不是查询参数里。因为浏览器跳转时,锚点部分不会发送到服务器,一定程度上减少了令牌在网络传输中被截获的风险。
安全性说明
隐藏式把令牌直接交给前端,安全性较低。因此:
- 令牌有效期必须非常短,通常仅会话级有效(关掉浏览器就失效)
- 只适用于安全要求不高的场景
- 目前行业趋势是逐渐用 PKCE 增强的授权码模式替代隐式授权
3.3 密码式(Resource Owner Password Credentials)
适用于:用户高度信任的应用,比如官方客户端、企业内部系统。
这种模式下,用户直接把用户名和密码告诉第三方应用,应用拿着密码去申请令牌。
流程两步走
第一步:用户向第三方应用提供账号密码
第二步:应用用密码申请令牌
https://b.com/oauth/token?
grant_type=password&
username=USERNAME&
password=PASSWORD&
client_id=CLIENT_ID
B 站验证通过后,直接返回令牌。整个过程不需要页面跳转。
风险与适用场景
密码式风险极高——用户把密码明文交给了第三方应用。因此:
- 绝对不能用于不可信的第三方
- 只适用于其他授权方式都走不通的场景
- 通常是同一公司旗下的产品之间使用(比如微信客户端登录微信开放平台)
3.4 客户端凭证模式(Client Credentials)
适用于:没有用户参与的场景,比如纯后端服务之间的调用、命令行工具。
这种模式颁发的令牌是给"应用"本身的,而不是给某个用户的。
流程两步走
第一步:应用直接用自身凭证申请令牌
https://b.com/oauth/token?
grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
第二步:授权服务器返回令牌
这种模式最简单,全程没有用户参与,完全是服务对服务的认证。多个用户可能共享同一个令牌。
典型场景:后端服务调用第三方开放 API(比如调用地图服务、短信服务),不需要关联具体用户。
四、令牌怎么用?如何刷新?
4.1 使用令牌访问资源
拿到 access_token 后,每次调用 API 都要带上它。标准做法是放在 HTTP 请求头的 Authorization 字段中:
curl -H "Authorization: Bearer ACCESS_TOKEN" \
"https://api.b.com/userinfo"
Bearer 是令牌类型,表示"持有者即有效"——谁拿着这个令牌,谁就能访问。这也再次说明了令牌保密的重要性。
4.2 刷新令牌(Refresh Token)
access_token 有效期通常比较短(比如几小时),如果过期了就让用户重新走一遍授权流程,体验会很差。
OAuth 2.0 的解决方案是:颁发令牌时,同时下发一个 refresh_token。
access_token 快过期时,后端用 refresh_token 去换取新的 access_token,全程用户无感知:
https://b.com/oauth/token?
grant_type=refresh_token&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
refresh_token=REFRESH_TOKEN
验证通过后,授权服务器返回新的 access_token(有时也会一并更新 refresh_token)。
💡 设计思想:
access_token频繁使用,所以有效期短,降低泄漏风险;refresh_token只在刷新时使用,可以设较长有效期,但必须严格保存在服务端。
五、四种模式对比与选型建议
最后用一张表总结四种模式的差异,帮你快速选型:
| 授权模式 | 核心特点 | 适用场景 | 安全性 | 用户参与 |
|---|---|---|---|---|
| 授权码模式 | 先拿 code 再换 token,令牌存后端 | 有后端的 Web 应用、服务端渲染 | ⭐⭐⭐⭐⭐ 最高 | 需要 |
| 隐藏式 | 直接向前端返回令牌 | 纯前端 SPA、无后端应用 | ⭐⭐ 较低 | 需要 |
| 密码式 | 用户直接给账号密码 | 高度可信的内部应用、官方客户端 | ⭐ 最低 | 需要 |
| 客户端凭证 | 应用自身凭证,无用户参与 | 服务间调用、命令行工具、后端 API | ⭐⭐⭐ 中等 | 不需要 |
选型原则:
- 绝大多数 Web 场景,首选授权码模式
- 纯前端应用优先考虑带 PKCE 的授权码模式,隐式授权作为兜底
- 不是自己家的产品,绝对不要用密码式
- 纯服务调用、不涉及用户,用客户端凭证模式
结语
OAuth 2.0 之所以能成为行业标准,核心不是它的技术有多复杂,而是它在"便利"和"安全"之间找到了极佳的平衡点——用户不用把密码交给第三方,第三方也能合法获取所需数据,平台方则掌握着授权的全部控制权。
理解了"令牌代替密码、权限可控、短期有效"这三个核心设计思想,再去看四种授权模式,就会发现它们只是在不同场景下对同一套思想的具体落地。
希望这篇文章能帮你建立起对 OAuth 2.0 的完整认知。如果觉得有帮助,欢迎点赞收藏,也欢迎在评论区交流你在实际项目中遇到的 OAuth 相关问题。
参考资料
- RFC 6749 - The OAuth 2.0 Authorization Framework
- 阮一峰 OAuth 2.0 系列博客