OAuth 2.0
是一种授权框架(Authorization Framework)
,用于允许第三方应用在用户授权后,代表用户访问受保护的资源(如用户存储在服务提供方的数据),而无需直接共享用户的凭据(如用户名和密码)。它是现代互联网中广泛使用的协议,支持单点登录(SSO)、API 访问控制等场景。
基础
相关角色
OAuth 2.0 定义了以下关键角色:
资源所有者(Resource Owner)
:通常是用户,拥有对受保护资源的控制权
客户端(Client)
:请求访问资源的第三方应用(如网站、移动应用)
授权服务器(Authorization Server)
:验证用户身份并颁发访问令牌(Access Token)
资源服务器(Resource Server)
:存储受保护资源的服务(如 API 服务器),根据访问令牌决定是否授权请求
授权类型(Grant Types)
OAuth 2.0 支持多种授权方式,适用于不同场景:
-
授权码模式(Authorization Code)
- 最安全的模式,适用于 Web 应用和移动应用
- 客户端通过前端渠道获取授权码,再通过后端渠道用授权码换取令牌
支持刷新令牌(Refresh Token)
,避免用户频繁登录
-
简化模式(Implicit)
- 适用于纯前端应用(如单页应用 SPA)
- 直接在前端返回访问令牌(无授权码步骤),但安全性较低
-
密码模式(Resource Owner Password Credentials)
- 用户直接将用户名/密码提供给客户端(仅适用于高度信任的客户端,如自家应用)
- 风险较高,需谨慎使用
-
客户端凭证模式(Client Credentials)
- 客户端用自己的身份(而非用户)获取令牌,适用于服务器间通信(如内部服务)
-
扩展模式(如 PKCE)
核心概念
-
访问令牌(Access Token)
- 短期的令牌,用于访问资源
- 通常通过 HTTPS 传输,避免泄露
-
刷新令牌(Refresh Token)
- 长期令牌,用于获取新的访问令牌(无需用户重新授权)
- 存储需严格保护(如服务器端)
-
Scope(权限范围)
- 定义客户端请求的权限(如
read:profile
、write:file
)
-
令牌有效期
- 访问令牌通常较短(如 1 小时),刷新令牌较长(如 30 天)
流程
OAuth 2.0 的典型流程分为以下步骤:
用户发起授权请求
:客户端将用户重定向到授权服务器,请求访问特定资源
用户授权
:用户在授权服务器上登录并同意客户端的访问请求
颁发授权许可(Authorization Grant)
:授权服务器返回一个授权许可(如授权码)给客户端
客户端获取访问令牌
:客户端将授权许可发送给授权服务器,换取访问令牌(Access Token)
访问资源
:客户端使用访问令牌向资源服务器发起请求,资源服务器验证令牌后返回数据
授权码模式
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
Note: The lines illustrating steps (A), (B), and (C) are broken into
two parts as they pass through the user-agent.
Figure 3: Authorization Code Flow
以下是 OAuth 2.0 的典型流程图
(以最常用的 授权码模式
为例),展示了客户端、用户、授权服务器和资源服务器之间的交互:
+--------+ +--------------+ +---------------+
| | | | | |
| 用户 | | 客户端 | | 授权服务器 |
| | | (Client) | | |
+---+----+ +-----+---+----+ +-------+-------+
| | |
| 1. 访问客户端 | |
+--------------------->| |
| | |
| 2. 重定向到授权服务器 | |
|<---------------------+ |
| (携带 client_id, redirect_uri, scope, state) |
| | |
| 3. 用户登录并授权 | |
+--------------------->| |
| | |
| 4. 返回授权码 | |
| (重定向到 redirect_uri,附带 code 和 state) |
|<---------------------+ |
| | |
| 5. 用授权码换取令牌 | |
| +-------------------------->|
| | 6. 验证并返回访问令牌 |
| | (access_token, refresh_token)|
| |<--------------------------+
| | |
| 7. 使用访问令牌访问资源 | |
| +-------------------------->|
| | | +--------------+
| | +->| 资源服务器 |
| | | | |
| | 8. 返回受保护资源 | +--------------+
| |<--------------------------+
| | |
+---+----+ +------+-------+ +--------+------+
| | | | | |
| 用户 | | 客户端 | | 授权服务器 |
| | | (Client) | | |
+--------+ +--------------+ +---------------+
-
用户访问客户端
:用户通过浏览器访问客户端应用(例如一个第三方网站)
-
客户端重定向用户到授权服务器
:客户端将用户重定向到授权服务器的授权端点,并携带以下参数:
client_id
:客户端的唯一标识
redirect_uri
:授权成功后重定向的 URI
scope
:请求的权限范围(如 read:profile
)
response_type=code
:表示使用授权码模式
state
:随机生成的防 CSRF 令牌(可选但推荐)
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
-
用户登录并授权
:用户在授权服务器上登录(例如输入用户名密码),并同意客户端请求的权限
-
授权服务器返回授权码
:授权服务器生成一个短期有效的 授权码(Authorization Code)
,通过重定向 URI 返回给客户端(附加到 URL 参数中),同时返回 state
参数(如果客户端发送了的话)
# 成功
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
# 失败
HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz
-
客户端用授权码换取访问令牌
:客户端通过后端服务将授权码发送到授权服务器的令牌端点,并附带以下参数:
grant_type=authorization_code
:表示授权类型为授权码模式
code
:上一步收到的授权码
redirect_uri
:必须与步骤 2 中的一致
client_id
和 client_secret
:客户端凭证(用于身份验证)
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
-
授权服务器返回访问令牌
:授权服务器验证授权码和客户端身份后,返回:
access_token
:用于访问资源的令牌
refresh_token
(可选):用于刷新访问令牌的长期令牌
expires_in
:访问令牌的有效期(秒)
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
-
客户端访问资源服务器
:客户端使用 access_token
访问资源服务器(例如调用 API),资源服务器验证令牌的有效性
-
资源服务器返回数据
:如果令牌有效,资源服务器返回请求的受保护资源(如用户数据)
-
刷新授权码
+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+
Figure 2: Refreshing an Expired Access Token
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
&client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw
简化模式
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+
Note: The lines illustrating steps (A) and (B) are broken into two
parts as they pass through the user-agent.
Figure 4: Implicit Grant Flow
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
# 成功
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
&state=xyz&token_type=example&expires_in=3600
# 失败
HTTP/1.1 302 Found
Location: https://client.example.com/cb#error=access_denied&state=xyz
密码模式
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
Figure 5: Resource Owner Password Credentials Flow
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
客户端凭证模式
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
Figure 6: Client Credentials Flow
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"example_parameter":"example_value"
}
Extension Grants
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Asaml2-
bearer&assertion=PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU
[...omitted for brevity...]aG5TdGF0ZW1lbnQ-PC9Bc3NlcnRpb24-
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
安全性
HTTPS 强制要求
:所有令牌传输必须通过 HTTPS
令牌泄露风险
:访问令牌可能被截获,需设置较短有效期
CSRF 防护
:使用 state
参数防止跨站请求伪造
客户端身份验证
:机密客户端(如 Web 服务端)需通过 client_id
和 client_secret
验证
常见场景
社交登录
:用户使用 Google/Facebook 账号登录第三方应用
API 访问
:第三方应用访问用户存储在服务商的数据(如获取 GitHub 仓库列表)
微服务通信
:服务间通过客户端凭证模式授权
与 OAuth 1.0 的区别
- OAuth 2.0 不再强制使用加密签名,而是依赖 HTTPS
- 更简洁的流程,支持移动端和单页应用
- 分离了角色(授权服务器和资源服务器)
常见库与工具
- 服务端:
Spring Security OAuth
、Auth0
、Keycloak
- 客户端:
Passport.js
、OAuth2-client(Python)
、AppAuth(移动端)
总结
OAuth 2.0 通过授权机制平衡了安全性与便利性,成为现代应用授权的标准。开发者需根据场景选择合适的授权类型,并严格遵循安全实践(如保护令牌、使用 HTTPS)。对于身份认证需求,可结合 OpenID Connect(基于 OAuth 2.0 的扩展协议)实现