一文彻底搞懂 OAuth 2.0:从设计思想到四种授权模式全解析

0 阅读11分钟

前言

在日常开发中,我们几乎每天都在接触 OAuth 2.0——微信登录、GitHub 授权、第三方应用获取用户信息……它已经成为互联网授权的事实标准。但很多开发者对它的理解停留在"点一下授权按钮"的表层,对于它为什么这么设计、四种授权模式分别适用于什么场景,往往一知半解。

本文将从最朴素的设计思想出发,带你彻底理解 OAuth 2.0 的本质,并详细拆解四种授权模式的完整流程与适用场景。读完这篇,你不仅能说清 OAuth 是什么,还能在实际项目中选出最合适的授权方案。

一、OAuth 2.0 到底解决了什么问题?

1.1 一个生活化的类比:快递员与小区门禁

我们先抛开技术术语,看一个每个人都能理解的场景:

你住在一个需要密码才能进入的小区,每天都有快递员来送货。怎么让快递员进来?

  • 方案一:把家门密码告诉快递员 问题很明显:快递员知道了你的密码,就拥有了和你完全一样的权限;哪天你不想让他进来了,只能改密码,还得通知所有其他快递员,麻烦又不安全。
  • 方案二:每次快递到了都下楼开门 安全是安全了,但体验极差,一天十趟快递谁受得了。

那有没有一种方案,既能让快递员进入小区送货,又不泄露你的密码,还能随时收回权限、限制他只能送货不能干别的?

当然有,这就是 OAuth 的设计初衷。

1.2 授权机制的核心设计

我们可以设计这样一套机制:

  1. 门禁系统增加一个"获取授权"按钮,快递员先按按钮发起申请
  2. 你的手机收到授权请求,显示快递员姓名、工号、所属公司,你点击确认
  3. 门禁系统生成一个短期有效的令牌(token) ,交给快递员
  4. 快递员凭令牌进入小区,令牌只有送货权限,且 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 系列博客