- 1 什么是恶意软件?分类、特征及预防方法
- 2 什么是DDoS?识别迹象、应对方法与有效防御指南
- 3 什么是网络钓鱼?识别与防范在线欺诈
- 4 什么是DNS Sinkhole?DNS Sinkhole技术的应用与使用方法
- 5 什么是OAuth 2.0?授权访问与谷歌登录原理
- 6 什么是木马病毒?关于Trojan恶意软件的基本知识
- 7 Zero Trust 是什么?'永不信任,始终验证'安全模型
- 8 VPN是什么?虚拟专用网络与WireGuard、OpenVPN协议
- 9 MFA 是什么?多因素认证与 2FA 对比详解
- 10 什么是防火墙?在网络安全中的角色和功能
- 11 什么是SQL注入?数据库攻击与防护
- 12 什么是XSS?跨站脚本攻击与防护
OAuth 2.0是RFC 6749开放授权协议,允许第三方应用在不知道用户密码的情况下访问其在另一服务上的资源。这是"谷歌账号登录"、"GitHub登录"等功能的技术基础,每天被数十亿用户使用。
需要企业数据解决方案?
自 2019 年起,AlgoData 为企业提供数据工程、分析与 AI 解决方案。
本文将深入探讨OAuth 2.0的工作原理,区分授权与身份验证,逐步解析授权码流程,并说明为何PKCE对SPA和移动应用至关重要。读完本文,你将清楚地了解每次点击"谷歌账号登录"按钮时底层发生了什么。
什么是OAuth 2.0?授权vs身份验证
首先,我们需要区分两个常被混淆的概念:身份验证(Authentication)和授权(Authorization)。
- **身份验证(Authentication)**回答"你是谁?"——验证身份。当你输入密码时,系统验证你就是账户所有者。
- **授权(Authorization)**回答"你被允许做什么?"——在身份确认后授予或限制访问权限。
OAuth 2.0是授权协议,而非身份验证协议。它解决的问题是:如何让一个应用(如Canva)在不知道你谷歌密码的情况下访问你的谷歌云盘?
一个实际的类比:当你在高档餐厅代客泊车时,服务员得到的是代客钥匙(valet key)——一把特殊的钥匙,只能启动引擎和停车,无法打开储物箱或邮箱。这正是OAuth 2.0的工作方式:你不是把谷歌账户的全部控制权交给Canva,而是发给它一把数字"代客钥匙"——访问令牌(access token)——只授权读取云盘文件,无法读取Gmail或更改密码。
OAuth 2.0标准由IETF在RFC 6749(2012年10月发布)中定义,是OAuth 1.0的完整替代版本,简化了流程并扩展了对更多客户端类型的支持。
OAuth 2.0的核心特点:
- 应用获得有时限的访问令牌,而非密码。
- 用户控制scope——已授权权限的具体列表。
- 令牌可以随时撤销,无需更改密码。
- 支持多种客户端类型:Web应用、移动应用、SPA、物联网设备。

OAuth 2.0中的4个角色
RFC 6749定义了完整OAuth 2.0流程中的四个角色。理解这四个角色有助于明确整个过程中各方的职责。
1. 资源所有者(Resource Owner)
资源所有者是终端用户——拥有资源并可以授予访问权限的实体。在"Canva访问谷歌云盘"的例子中,资源所有者是你——拥有谷歌账户和云盘文件的人。
资源所有者不一定是人。在机器对机器流程(Client Credentials)中,资源所有者和客户端可以是同一实体。
2. 客户端(Client)
客户端是代表资源所有者请求访问资源的应用。在我们的例子中,Canva就是客户端。客户端必须提前在授权服务器注册,以获取client_id和client_secret。
客户端有两种类型:
- 机密客户端(Confidential client):运行在服务器上的Web应用,可以安全保存
client_secret。 - 公开客户端(Public client):SPA(React、Vue)或移动应用——无法安全存储密钥,因为代码运行在用户设备上。必须使用PKCE。
3. 授权服务器(Authorization Server)
授权服务器是在验证资源所有者身份并获得其同意后颁发令牌的服务。谷歌、GitHub、Facebook和微软都运营各自的授权服务器。
授权服务器公开两个主要端点:
- 授权端点(Authorization Endpoint)(
/auth):接收授权请求,显示同意页面,返回授权码。 - 令牌端点(Token Endpoint)(
/token):接收授权码,验证客户端,返回访问令牌和刷新令牌。
4. 资源服务器(Resource Server)
资源服务器是存储客户端想要访问数据的API。它在请求头中接受访问令牌,验证令牌,如果令牌有效且具有所需权限,则返回数据。
实际上,授权服务器和资源服务器通常部署在一起(例如,accounts.google.com颁发令牌,www.googleapis.com是资源服务器),但RFC 6749在概念上将它们分开。
授权码流程(Authorization Code Flow)——逐步解析
授权码流程是最广泛使用的授权类型,推荐用于Web应用和移动应用。这是最安全的流程,因为访问令牌永远不会经过浏览器。
1. 用户在应用中点击"谷歌账号登录"
2. 应用重定向 → https://accounts.google.com/o/oauth2/auth
?client_id=APP_CLIENT_ID
&redirect_uri=https://myapp.com/callback
&response_type=code
&scope=openid email profile
&state=RANDOM_CSRF_TOKEN
3. 用户登录并同意 → 谷歌重定向回:
https://myapp.com/callback?code=AUTH_CODE&state=TOKEN
4. 应用后端向谷歌发送POST请求:
https://oauth2.googleapis.com/token
client_id, client_secret, code, redirect_uri, grant_type=authorization_code
5. 谷歌响应:
{ access_token, expires_in, refresh_token, id_token }
6. 应用调用API:
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ACCESS_TOKEN
逐步解析:
第1-2步:授权请求 应用构造包含以下参数的重定向URL:
client_id:已在谷歌注册的应用标识符。redirect_uri:用户同意后谷歌将重定向回的URL(必须与注册URI精确匹配)。response_type=code:指定授权码流程。scope:请求的权限列表。state:防止CSRF的随机随机数——应用必须在收到回调时验证此值。
第3步:授权码
用户在谷歌的同意页面登录并授权后,谷歌将code(授权码)附在redirect_uri后重定向回来——授权码的有效期约10秒,且只能使用一次。这是一个关键的安全特性:短暂的、一次性的授权码可以防止重放攻击。
第4步:令牌交换
应用后端(不是浏览器!)向谷歌的令牌端点发送POST请求,包含client_secret。这次交换直接通过HTTPS在服务器之间进行,不经过浏览器——这就是访问令牌不会出现在浏览器历史记录或日志中的原因。
第5步:令牌响应 谷歌返回:
access_token:用于调用API的令牌,通常1小时后过期。expires_in:到期前的秒数。refresh_token:用于在不需要用户交互的情况下获取新访问令牌的长期令牌(仅在请求offline_access范围时颁发)。id_token:包含用户身份信息的JWT(仅在使用带openid范围的OIDC时存在)。
第6步:API调用
应用在调用资源服务器时,在Authorization: Bearer <token>头中包含访问令牌。这是RFC 6750定义的标准Bearer Token语法。
授权类型(Grant Types)
OAuth 2.0为不同使用场景定义了多种授权类型。每种授权类型都针对特定类型的客户端和场景设计。
授权码(推荐)
如上所述,这是以下场景最安全的授权类型:
- Web应用(机密客户端):在服务器上使用
client_secret。 - SPA和移动应用(公开客户端):使用PKCE代替
client_secret。
客户端凭证(Client Credentials)
用于**机器对机器(M2M)**流程——不涉及人类用户时。例如,微服务A需要调用微服务B的API。
POST /token
grant_type=client_credentials
client_id=SERVICE_A_ID
client_secret=SERVICE_A_SECRET
scope=read:orders write:inventory
服务A用client_id和client_secret进行身份验证,直接获取访问令牌,无需显示同意页面。
设备授权(Device Code)
用于没有浏览器或输入受限的设备:智能电视、游戏机、物联网设备、命令行工具。设备显示一个短URL和验证码;用户在手机或电脑上输入该URL,登录并授权;设备轮询授权服务器,直到收到令牌。
隐式流程(已废弃)
隐式流程曾用于CORS广泛支持之前的SPA。它直接在URL片段中返回访问令牌,跳过了授权码交换步骤——方便但不安全(令牌暴露在浏览器历史记录、Referer头中)。隐式流程已不再推荐;OAuth 2.1(草案)完全删除了它。SPA请使用授权码+PKCE。

OAuth 2.0 vs OpenID Connect(OIDC)
这是关于OAuth 2.0最容易被误解的方面之一:当你"谷歌账号登录"时,你不仅仅在使用OAuth 2.0——你在使用OpenID Connect(OIDC)。
纯OAuth 2.0解决授权问题:授予应用访问资源的权限。它没有提供回答"这个用户是谁?"的标准机制。OAuth 2.0访问令牌是不透明的字符串——资源服务器知道令牌有效,但没有额外信息的话,不知道令牌属于哪个用户。
**OpenID Connect(OIDC)**是构建在OAuth 2.0之上的身份验证层,由OpenID基金会规范定义。OIDC增加了:
-
ID Token:与访问令牌一起返回的JWT,包含
sub(主体/用户ID)、name、email、picture和其他用户身份声明。客户端无需额外API调用即可读取ID Token。 -
/userinfo端点:标准化的资源服务器端点——用访问令牌调用它以获取JSON格式的用户信息。 -
openidScope:激活OIDC的特殊权限范围。当授权请求包含scope=openid时,授权服务器返回ID Token。 -
发现文档(Discovery Document):
/.well-known/openid-configuration端点返回描述所有端点、支持的范围和签名密钥的JSON——客户端可以自动配置,无需硬编码URL。
总结:
- OAuth 2.0:"Canva被允许读取我的谷歌云盘。"
- OIDC:"我是张伟,邮箱z@gmail.com——这是谷歌的加密签名确认。"
大多数主流身份提供商(谷歌、GitHub、微软、Apple)在同一端点上同时实现了OAuth 2.0和OIDC。在scope中添加openid即切换到OIDC模式。
常见安全漏洞
不正确地实现OAuth 2.0可能导致严重漏洞。以下是最常见的错误:
1. 未验证redirect_uri——授权码劫持
这是最危险的漏洞。如果授权服务器未严格验证redirect_uri,攻击者可以:
- 构造包含
redirect_uri=https://attacker.com/steal的链接。 - 将链接发送给受害者。
- 受害者登录;授权码被发送到
attacker.com。 - 攻击者用授权码换取访问令牌。
防护规则:
- 与注册URI进行精确匹配——不使用通配符,不允许新子域名。
- 不做前缀匹配检查(
https://myapp.com不够安全,攻击者可以使用https://myapp.com.evil.com)。 - 拒绝任何不在白名单中的URI。
2. 忽略state参数——CSRF攻击
state参数是客户端创建的随机数,在授权请求中发送,并在回调时验证。如果应用不使用或不验证state:
- 攻击者发起自己的授权请求并获得授权码。
- 攻击者将代码嵌入页面并诱骗受害者访问。
- 受害者的应用自动交换攻击者的代码,将攻击者的账户绑定到受害者的会话中。
始终为每个授权请求生成新的state,存储在会话中,并在回调中交换代码前先验证它。
3. URL中的访问令牌——日志泄露
已废弃的隐式流程和一些不谨慎的实现将访问令牌放在URL片段或查询字符串中。URL中的令牌可能通过以下途径泄露:
- 浏览器历史记录。
- 浏览器跳转到其他页面时的
Referer头。 - 服务器访问日志。
- 代理日志。
始终通过Authorization: Bearer头传输令牌,绝不放在URL中。
4. SPA和移动应用不使用PKCE
公开客户端(SPA、移动应用)无法保护client_secret。没有PKCE,任何知道client_id(公开信息)的应用都可以用截获的授权码换取令牌。
SPA和移动应用的PKCE
PKCE(代码交换证明密钥,RFC 7636)解决了这个问题。思路:不使用静态的client_secret,每个授权请求生成一个随机的code_verifier和作为其哈希值的code_challenge。
1// SPA — PKCE flow (Web Crypto API)
2async function generatePKCE() {
3 const array = new Uint8Array(32);
4 crypto.getRandomValues(array);
5 const verifier = btoa(String.fromCharCode(...array))
6 .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
7 const encoder = new TextEncoder();
8 const data = encoder.encode(verifier);
9 const digest = await crypto.subtle.digest('SHA-256', data);
10 const challenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
11 .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
12 return { verifier, challenge };
13}
PKCE流程:
- SPA生成
code_verifier(43-128个字符的随机字符串)。 - SPA用SHA-256对
code_verifier进行哈希 →code_challenge。 - SPA将
code_challenge(不是verifier!)与授权请求一起发送。 - 授权服务器存储
code_challenge。 - SPA用授权码换取令牌时,发送原始的
code_verifier。 - 授权服务器对
code_verifier进行哈希并与存储的code_challenge比较——只有在请求来自同一个发起流程的客户端时才会匹配。
如果攻击者截获了授权码,他们没有code_verifier → 无法换取令牌。PKCE将每个授权请求变成绑定到特定客户端实例的一次性密钥对。
从OAuth 2.1(草案)开始,PKCE对所有授权码流程都是强制要求的,包括机密客户端。

总结
OAuth 2.0是现代API生态系统的基础。请记住以下要点:
- OAuth 2.0是授权,而非身份验证——需要"谷歌账号登录"时请使用OIDC。
- 4个角色:资源所有者(用户)、客户端(应用)、授权服务器(谷歌/GitHub)、资源服务器(API)。
- 授权码流程是推荐的流程——短暂的授权码,令牌在服务器间交换。
- PKCE对SPA和移动应用是强制要求的——不使用
client_secret,使用一次性的verifier/challenge对。 - Scope实施最小权限原则——只请求你真正需要的权限。
- 严格验证
redirect_uri,始终使用state,绝不在URL中放置令牌。

