OAuth 密码模式 #

概述 #

密码模式(Resource Owner Password Credentials Grant,简称 ROPC)允许客户端直接使用用户的用户名和密码获取访问令牌。这种模式将用户的凭据直接暴露给客户端,因此只应在高度信任的场景下使用。

text
┌─────────────────────────────────────────────────────────────┐
│                    ⚠️ 重要警告                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  密码模式存在严重安全风险:                                  │
│                                                             │
│  ❌ 用户密码暴露给客户端                                    │
│  ❌ 违反最小权限原则                                        │
│  ❌ 无法实现细粒度授权                                      │
│  ❌ 用户无法撤销特定应用授权                                │
│                                                             │
│  OAuth 2.1 规范已移除此模式                                 │
│                                                             │
│  仅在以下情况考虑使用:                                      │
│  ✅ 第一方应用(同一组织开发)                              │
│  ✅ 高度信任环境                                            │
│  ✅ 无法使用其他模式的遗留系统迁移                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

为什么会有密码模式? #

历史背景 #

text
┌─────────────────────────────────────────────────────────────┐
│                    密码模式的历史                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  设计初衷:                                                 │
│                                                             │
│  1. 迁移场景                                                │
│     - 从传统用户名/密码认证迁移到 OAuth                     │
│     - 保持向后兼容                                          │
│                                                             │
│  2. 第一方应用                                              │
│     - 同一组织的官方应用                                    │
│     - 用户已信任应用                                        │
│                                                             │
│  3. 简化流程                                                │
│     - 避免复杂的授权流程                                    │
│     - 用户体验更直接                                        │
│                                                             │
│  当时认为的合理性:                                         │
│  - 用户已经把密码给应用了                                   │
│  - 应用本身就是可信的                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

为什么不推荐? #

text
┌─────────────────────────────────────────────────────────────┐
│                    密码模式的问题                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 安全风险                                                │
│     ┌─────────────────────────────────────────────────┐    │
│     │ 用户密码直接暴露给客户端                         │    │
│     │ 客户端可以获取完全访问权限                       │    │
│     │ 密码可能被存储或泄露                             │    │
│     └─────────────────────────────────────────────────┘    │
│                                                             │
│  2. 授权控制缺失                                            │
│     ┌─────────────────────────────────────────────────┐    │
│     │ 无法限制访问范围                                 │    │
│     │ 用户无法选择授权内容                             │    │
│     │ 无法实现最小权限原则                             │    │
│     └─────────────────────────────────────────────────┘    │
│                                                             │
│  3. 撤销困难                                                │
│     ┌─────────────────────────────────────────────────┐    │
│     │ 用户修改密码后所有令牌失效                       │    │
│     │ 无法单独撤销某个应用的访问                       │    │
│     └─────────────────────────────────────────────────┘    │
│                                                             │
│  4. 用户体验差                                              │
│     ┌─────────────────────────────────────────────────┐    │
│     │ 用户需要在应用中输入密码                         │    │
│     │ 增加钓鱼风险                                     │    │
│     └─────────────────────────────────────────────────┘    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

授权流程 #

流程图 #

text
┌─────────────────────────────────────────────────────────────┐
│                    密码模式流程                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────┐     ┌─────────┐     ┌─────────┐               │
│  │  用户   │     │  客户端  │     │ 授权服务│               │
│  │Resource │     │ Client  │     │ Server  │               │
│  │ Owner   │     │         │     │         │               │
│  └────┬────┘     └────┬────┘     └────┬────┘               │
│       │               │               │                     │
│       │ (A) 输入用户名密码            │                     │
│       │──────────────>│               │                     │
│       │               │               │                     │
│       │               │ (B) 请求令牌  │                     │
│       │               │ 携带用户凭据  │                     │
│       │               │──────────────>│                     │
│       │               │               │                     │
│       │               │               │ 验证凭据            │
│       │               │               │                     │
│       │               │ (C) 返回令牌  │                     │
│       │               │<──────────────│                     │
│       │               │               │                     │
│       │               │ (D) 使用令牌访问资源                │
│       │               │────────────────────────────────────>│
│       │               │               │            │        │
│       │               │ (E) 返回数据  │            │        │
│       │               │<────────────────────────────────────│
│       │               │               │            │        │
└─────────────────────────────────────────────────────────────┘

步骤详解 #

步骤 A:用户提供凭据 #

text
用户在客户端应用中输入:
- 用户名(username)
- 密码(password)

步骤 B:请求令牌 #

http
POST /token HTTP/1.1
Host: auth.example.com
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
Content-Type: application/x-www-form-urlencoded

grant_type=password
&username=johndoe
&password=A3ddj3w
&scope=profile%20email

步骤 C:返回令牌 #

json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8xLOxBtZp8",
  "scope": "profile email"
}

请求参数 #

必需参数 #

参数 描述
grant_type 固定值 password
username 用户名
password 用户密码

可选参数 #

参数 描述
scope 请求的权限范围
client_id 客户端标识(公共客户端)
client_secret 客户端密钥(机密客户端)

适用场景 #

唯一合理的场景 #

text
┌─────────────────────────────────────────────────────────────┐
│                    适用场景                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 第一方官方应用                                          │
│     ┌─────────────────────────────────────────────────┐    │
│     │ 同一组织开发的官方应用                           │    │
│     │ 用户已经信任该组织                               │    │
│     │ 例如:Google 官方移动应用                        │    │
│     └─────────────────────────────────────────────────┘    │
│                                                             │
│  2. 遗留系统迁移                                            │
│     ┌─────────────────────────────────────────────────┐    │
│     │ 从传统认证迁移到 OAuth                           │    │
│     │ 作为过渡方案                                     │    │
│     │ 逐步迁移到更安全的模式                           │    │
│     └─────────────────────────────────────────────────┘    │
│                                                             │
│  3. 高度信任的企业内部应用                                  │
│     ┌─────────────────────────────────────────────────┐    │
│     │ 企业内部系统                                     │    │
│     │ 完全控制的部署环境                               │    │
│     │ 严格的审计和监控                                 │    │
│     └─────────────────────────────────────────────────┘    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

不适用场景 #

text
❌ 第三方应用
   - 用户不应信任第三方应用
   - 应使用授权码模式

❌ 公共网络环境
   - 密码可能被截获
   - 应使用更安全的模式

❌ 需要细粒度授权
   - 无法限制访问范围
   - 应使用授权码模式

❌ 新项目
   - OAuth 2.1 已移除此模式
   - 应使用授权码 + PKCE

实现示例 #

Node.js 实现 #

javascript
const axios = require('axios');

async function loginWithPassword(username, password) {
  const credentials = Buffer.from(
    `${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`
  ).toString('base64');

  const response = await axios.post(
    process.env.TOKEN_ENDPOINT,
    new URLSearchParams({
      grant_type: 'password',
      username: username,
      password: password,
      scope: 'profile email'
    }),
    {
      headers: {
        'Authorization': `Basic ${credentials}`,
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    }
  );

  return response.data;
}

Python 实现 #

python
import requests
import base64

def login_with_password(username, password):
    credentials = base64.b64encode(
        f"{CLIENT_ID}:{CLIENT_SECRET}".encode()
    ).decode()
    
    response = requests.post(
        TOKEN_ENDPOINT,
        data={
            'grant_type': 'password',
            'username': username,
            'password': password,
            'scope': 'profile email'
        },
        headers={
            'Authorization': f'Basic {credentials}',
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    )
    
    return response.json()

安全风险详解 #

风险一:密码暴露 #

text
┌─────────────────────────────────────────────────────────────┐
│                    密码暴露风险                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  攻击场景:                                                 │
│                                                             │
│  1. 客户端存储密码                                          │
│     - 应用可能缓存密码                                      │
│     - 设备被盗后密码泄露                                    │
│                                                             │
│  2. 传输过程                                                │
│     - 密码在请求中明文传输                                  │
│     - 依赖 TLS 保护                                         │
│                                                             │
│  3. 日志记录                                                │
│     - 密码可能被记录在日志                                  │
│     - 调试信息可能包含密码                                  │
│                                                             │
│  4. 恶意客户端                                              │
│     - 钓鱼应用获取密码                                      │
│     - 用户无法区分真伪                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

风险二:权限过度 #

text
密码模式的问题:

传统授权码模式:
用户 ──授权──> 授权服务器 ──限制范围──> 客户端
                    │
                    └── 用户可以选择授权范围

密码模式:
用户 ──密码──> 客户端 ──完全访问──> 授权服务器
                    │
                    └── 客户端获得完全访问权限

替代方案 #

推荐替代:授权码 + PKCE #

text
┌─────────────────────────────────────────────────────────────┐
│                    替代方案对比                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  密码模式:                                                 │
│  用户输入密码 ──────────────> 客户端获取令牌                 │
│  (密码暴露)                                               │
│                                                             │
│  授权码 + PKCE:                                            │
│  用户授权 ──> 授权服务器 ──> 客户端获取令牌                  │
│  (密码不暴露)                                             │
│                                                             │
│  优势:                                                     │
│  ✅ 用户密码不暴露给客户端                                  │
│  ✅ 可以限制授权范围                                        │
│  ✅ 用户可以撤销授权                                        │
│  ✅ 更好的用户体验                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

迁移策略 #

text
┌─────────────────────────────────────────────────────────────┐
│                    迁移步骤                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 评估现状                                                │
│     - 识别使用密码模式的应用                                │
│     - 评估安全风险                                          │
│                                                             │
│  2. 实现替代方案                                            │
│     - 实现授权码 + PKCE                                     │
│     - 保持向后兼容                                          │
│                                                             │
│  3. 逐步迁移                                                │
│     - 新用户使用新模式                                      │
│     - 引导现有用户迁移                                      │
│                                                             │
│  4. 弃用密码模式                                            │
│     - 设置弃用时间表                                        │
│     - 完全禁用密码模式                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

如果必须使用密码模式 #

安全措施 #

text
┌─────────────────────────────────────────────────────────────┐
│                    安全措施                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 限制使用范围                                            │
│     - 仅限第一方应用                                        │
│     - 验证客户端身份                                        │
│                                                             │
│  2. 短期令牌                                                │
│     - 访问令牌有效期短                                      │
│     - 不提供长期刷新令牌                                    │
│                                                             │
│  3. 监控和审计                                              │
│     - 记录所有令牌请求                                      │
│     - 检测异常行为                                          │
│                                                             │
│  4. 多因素认证                                              │
│     - 结合 MFA 使用                                         │
│     - 增加安全层                                            │
│                                                             │
│  5. 限制 Scope                                              │
│     - 只授予必要的权限                                      │
│     - 不授予完全访问权限                                    │
│                                                             │
│  6. 客户端安全                                              │
│     - 不存储密码                                            │
│     - 使用安全传输                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

实现最佳实践 #

javascript
async function securePasswordLogin(username, password, mfaCode) {
  if (!isValidClient()) {
    throw new Error('Unauthorized client');
  }
  
  if (!isFirstPartyApp()) {
    throw new Error('Password grant not allowed for third-party apps');
  }
  
  const rateLimitKey = `login_attempts:${getClientId()}:${username}`;
  if (await checkRateLimit(rateLimitKey)) {
    throw new Error('Too many login attempts');
  }
  
  const response = await axios.post(TOKEN_ENDPOINT, {
    grant_type: 'password',
    username,
    password,
    scope: 'limited_scope',
    mfa_code: mfaCode
  });
  
  await auditLog({
    event: 'password_grant',
    client_id: getClientId(),
    username,
    timestamp: new Date()
  });
  
  return {
    accessToken: response.data.access_token,
    expiresIn: Math.min(response.data.expires_in, 900)
  };
}

密码模式 vs 其他模式 #

特性 密码模式 授权码模式 客户端凭证
用户参与 需要 需要 不需要
密码暴露
授权控制
安全性
OAuth 2.1 已移除 推荐 推荐
适用场景 遗留系统 Web/移动应用 服务间通信

OAuth 2.1 的变化 #

text
┌─────────────────────────────────────────────────────────────┐
│                 OAuth 2.1 移除密码模式                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  OAuth 2.1 规范变化:                                       │
│                                                             │
│  1. 移除密码模式                                            │
│     - 不再作为标准授权模式                                  │
│     - 推荐使用授权码 + PKCE                                 │
│                                                             │
│  2. 原因                                                    │
│     - 安全风险太大                                          │
│     - 有更好的替代方案                                      │
│     - 现代应用不需要                                        │
│                                                             │
│  3. 影响                                                    │
│     - 新项目不应使用                                        │
│     - 现有系统应迁移                                        │
│     - 授权服务器可能弃用                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

常见问题 #

问题一:为什么还要学习密码模式? #

text
原因:
1. 理解安全风险
   - 了解为什么不推荐使用
   - 避免在新项目中使用

2. 维护遗留系统
   - 可能需要维护使用密码模式的系统
   - 了解如何安全地迁移

3. 完整性
   - 全面了解 OAuth 2.0
   - 理解协议演进

问题二:现有系统如何迁移? #

text
迁移步骤:

1. 实现授权码 + PKCE
   - 添加新的授权流程
   - 保持向后兼容

2. 逐步迁移用户
   - 新用户使用新模式
   - 引导现有用户迁移

3. 设置弃用时间表
   - 通知用户即将弃用
   - 提供迁移指南

4. 完全禁用
   - 停止接受密码模式请求
   - 清理相关代码

下一步 #

密码模式已不推荐使用,建议学习 PKCE 扩展,了解如何安全地实现移动端和单页应用授权!

最后更新:2026-03-28