身份验证模式Skill authentication-patterns

本技能提供全面的身份验证系统实施指导,涵盖JWT最佳实践、OAuth 2.0/OIDC流程、Passkeys/FIDO2/WebAuthn、MFA模式和安全会话管理。适用于实施登录系统、基于令牌的身份验证、SSO、无密码身份验证或审查身份验证安全。关键词:身份验证、JWT、OAuth、Passkeys、MFA、会话管理、网络安全。

身份认证 0 次安装 0 次浏览 更新于 3/11/2026

name: authentication-patterns description: 全面的身份验证实施指导,包括JWT最佳实践、OAuth 2.0/OIDC流程、Passkeys/FIDO2/WebAuthn、MFA模式和安全会话管理。在实施登录系统、基于令牌的身份验证、SSO、无密码身份验证或审查身份验证安全时使用。 allowed-tools: Read, Glob, Grep, Task

身份验证模式

实施安全身份验证系统的全面指导,涵盖JWT、OAuth 2.0、OIDC、Passkeys、MFA和会话管理。

何时使用此技能

在以下情况使用此技能:

  • 实施基于JWT的身份验证
  • 设置OAuth 2.0或OpenID Connect流程
  • 实施无密码身份验证(Passkeys/FIDO2)
  • 添加多因素身份验证(MFA/2FA)
  • 设计会话管理和安全Cookie
  • 实施单点登录(SSO)
  • 审查身份验证安全
  • 选择身份验证方法

身份验证方法选择

方法 最适用 安全等级 用户体验
Passkeys/WebAuthn 主要身份验证、无密码 ★★★★★ 优秀
OAuth 2.0 + PKCE 第三方登录、单页应用 ★★★★☆ 良好
JWT + 刷新令牌 API、微服务 ★★★★☆ 良好
会话Cookie 传统Web应用 ★★★☆☆ 优秀
密码 + MFA 遗留系统升级 ★★★★☆ 中等

推荐: 新应用首选Passkeys。SPA使用OAuth 2.0 + PKCE。始终添加MFA作为第二因素。

JWT最佳实践快速参考

算法选择

算法 使用场景 推荐
RS256 公钥验证、分布式系统 ✅ 推荐
ES256 较小令牌、基于ECDSA ✅ 推荐
HS256 简单系统、同方验证 ⚠️ 谨慎使用
None 永不使用 ❌ 禁止

令牌结构

// 头部
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-id-for-rotation"  // 密钥ID用于密钥轮换
}

// 负载(声明)
{
  "iss": "https://auth.example.com",  // 签发者
  "sub": "user-123",                   // 主体(用户ID)
  "aud": "https://api.example.com",   // 受众
  "exp": 1735300000,                   // 过期时间(短期)
  "iat": 1735296400,                   // 签发时间
  "jti": "unique-token-id",            // JWT ID(用于撤销)
  "scope": "read write"                // 权限
}

令牌生命周期

令牌类型 推荐生命周期 存储
访问令牌 5-15分钟 仅内存
刷新令牌 7-30天 安全HttpOnly Cookie或加密存储
ID令牌 匹配访问令牌 仅内存

详细JWT安全: 参见 JWT安全参考

OAuth 2.0 流程选择

流程 使用场景 需PKCE
授权码 + PKCE SPA、移动应用、Web应用 ✅ 是
客户端凭证 服务到服务 N/A
设备授权 智能电视、CLI工具 N/A
隐式 已弃用 - 勿用 N/A
资源所有者密码 已弃用 - 勿用 N/A

授权码 + PKCE 流程

┌──────────┐                              ┌───────────────┐
│  客户端  │                              │ 认证服务器   │
└────┬─────┘                              └───────┬───────┘
     │                                            │
     │ 1. 生成code_verifier(随机)                │
     │    code_challenge = SHA256(code_verifier)  │
     │                                            │
     │ 2. 授权请求 ───────────────────────────────>│
     │    (response_type=code, code_challenge)    │
     │                                            │
     │ 3. 用户认证和同意                           │
     │                                            │
     │ 4. <────────── 授权码 ──────────────────────│
     │                                            │
     │ 5. 令牌请求 ───────────────────────────────>│
     │    (code, code_verifier)                   │
     │                                            │
     │ 6. <────────── 访问 + 刷新令牌 ─────────────│
     └────────────────────────────────────────────┘

详细OAuth流程: 参见 OAuth流程参考

Passkeys/WebAuthn 快速开始

Passkeys使用公钥密码学提供抗钓鱼、无密码身份验证。

注册流程

// 1. 从服务器获取挑战
const options = await fetch('/api/webauthn/register/options').then(r => r.json());

// 2. 创建凭证
const credential = await navigator.credentials.create({
  publicKey: {
    challenge: base64ToBuffer(options.challenge),
    rp: { name: "示例应用", id: "example.com" },
    user: {
      id: base64ToBuffer(options.userId),
      name: options.username,
      displayName: options.displayName
    },
    pubKeyCredParams: [
      { type: "public-key", alg: -7 },   // ES256
      { type: "public-key", alg: -257 }  // RS256
    ],
    authenticatorSelection: {
      authenticatorAttachment: "platform",  // 或 "cross-platform"
      residentKey: "required",              // 可发现凭证
      userVerification: "required"          // 需要生物识别/PIN
    },
    timeout: 60000
  }
});

// 3. 发送凭证到服务器存储
await fetch('/api/webauthn/register/verify', {
  method: 'POST',
  body: JSON.stringify({
    id: credential.id,
    rawId: bufferToBase64(credential.rawId),
    response: {
      clientDataJSON: bufferToBase64(credential.response.clientDataJSON),
      attestationObject: bufferToBase64(credential.response.attestationObject)
    }
  })
});

完整Passkeys实现: 参见 Passkeys实现指南

MFA 实现模式

MFA 方法(按安全)

方法 抗钓鱼 安全 用户体验
Passkeys/安全密钥 ✅ 是 ★★★★★ 良好
认证器应用(TOTP) ❌ 否 ★★★★☆ 良好
推送通知 ⚠️ 部分 ★★★★☆ 优秀
SMS OTP ❌ 否 ★★☆☆☆ 中等
邮件 OTP ❌ 否 ★★☆☆☆ 中等

TOTP 实现

using System.Security.Cryptography;
using OtpNet;  // 安装:Otp.NET包

/// <summary>
/// TOTP(基于时间的一次性密码)服务,用于MFA。
/// </summary>
public sealed class TotpService
{
    private const int SecretLength = 20;  // 160位

    /// <summary>
    /// 为用户注册生成新的TOTP密钥。
    /// </summary>
    public static string GenerateSecret()
    {
        var secretBytes = RandomNumberGenerator.GetBytes(SecretLength);
        return Base32Encoding.ToString(secretBytes);
    }

    /// <summary>
    /// 生成认证器应用(如Google Authenticator)的配置URI。
    /// </summary>
    public static string GetProvisioningUri(string secret, string email, string issuer)
    {
        return $"otpauth://totp/{Uri.EscapeDataString(issuer)}:{Uri.EscapeDataString(email)}" +
               $"?secret={secret}&issuer={Uri.EscapeDataString(issuer)}&algorithm=SHA1&digits=6&period=30";
    }

    /// <summary>
    /// 登录时验证TOTP代码。允许1步时间漂移。
    /// </summary>
    public static bool VerifyTotp(string secret, string otp)
    {
        var secretBytes = Base32Encoding.ToBytes(secret);
        var totp = new Totp(secretBytes, step: 30, totpSize: 6);

        // VerificationWindow允许时钟漂移(1步 = 每方向30秒)
        return totp.VerifyTotp(otp, out _, VerificationWindow.RfcSpecifiedNetworkDelay);
    }
}

会话管理

安全Cookie配置

// ASP.NET Core Cookie配置
app.UseCookiePolicy(new CookiePolicyOptions
{
    HttpOnly = HttpOnlyPolicy.Always,        // 防止JavaScript访问(XSS防护)
    Secure = CookieSecurePolicy.Always,      // 仅HTTPS
    MinimumSameSitePolicy = SameSiteMode.Lax // CSRF防护(或Strict更高安全)
});

// 每个Cookie配置
Response.Cookies.Append("session_id", sessionId, new CookieOptions
{
    HttpOnly = true,             // 防止JavaScript访问
    Secure = true,               // 仅HTTPS
    SameSite = SameSiteMode.Lax, // CSRF防护
    MaxAge = TimeSpan.FromHours(1),
    Domain = ".example.com",
    Path = "/",
    IsEssential = true           // GDPR必需Cookie
});

会话安全检查清单

  • [ ] 生成加密随机会话ID(128+位)
  • [ ] 认证后重新生成会话ID
  • [ ] 设置HttpOnly标志在会话Cookie上
  • [ ] 设置Secure标志(仅HTTPS)
  • [ ] 设置SameSite属性(Lax或Strict)
  • [ ] 实现会话超时(空闲和绝对)
  • [ ] 注销时使会话无效(服务器端)
  • [ ] 绑定会话到用户指纹(可选,考虑隐私)

密码安全(当需要时)

密码哈希

using System.Security.Cryptography;
using Konscious.Security.Cryptography;  // 安装:Konscious.Security.Cryptography.Argon2

/// <summary>
/// Argon2id密码哈希服务(OWASP推荐)。
/// </summary>
public sealed class PasswordHasher
{
    private const int SaltSize = 16;
    private const int HashSize = 32;
    private const int Iterations = 3;      // 时间成本
    private const int MemorySize = 65536;  // 64 MB
    private const int Parallelism = 4;     // 线程数

    /// <summary>
    /// 使用Argon2id哈希密码。
    /// </summary>
    public static string HashPassword(string password)
    {
        var salt = RandomNumberGenerator.GetBytes(SaltSize);

        using var argon2 = new Argon2id(System.Text.Encoding.UTF8.GetBytes(password))
        {
            Salt = salt,
            DegreeOfParallelism = Parallelism,
            MemorySize = MemorySize,
            Iterations = Iterations
        };

        var hash = argon2.GetBytes(HashSize);

        // 组合盐 + 哈希用于存储
        var combined = new byte[SaltSize + HashSize];
        Buffer.BlockCopy(salt, 0, combined, 0, SaltSize);
        Buffer.BlockCopy(hash, 0, combined, SaltSize, HashSize);

        return Convert.ToBase64String(combined);
    }

    /// <summary>
    /// 验证密码与存储哈希。
    /// </summary>
    public static bool VerifyPassword(string password, string storedHash)
    {
        var combined = Convert.FromBase64String(storedHash);
        if (combined.Length != SaltSize + HashSize) return false;

        var salt = combined[..SaltSize];
        var expectedHash = combined[SaltSize..];

        using var argon2 = new Argon2id(System.Text.Encoding.UTF8.GetBytes(password))
        {
            Salt = salt,
            DegreeOfParallelism = Parallelism,
            MemorySize = MemorySize,
            Iterations = Iterations
        };

        var actualHash = argon2.GetBytes(HashSize);

        // 时间安全比较
        return CryptographicOperations.FixedTimeEquals(actualHash, expectedHash);
    }
}

密码策略

要求 推荐
最小长度 12+ 字符
最大长度 128+ 字符(防止DoS)
复杂性 无任意规则(允许所有字符)
泄露检查 检查已知泄露密码
速率限制 5次尝试,然后指数退避
账户锁定 失败后临时锁定(15-30分钟)

快速决策树

您正在实施什么身份验证?

  1. 新Web/移动应用 → Passkeys + OAuth 2.0 后备
  2. SPA带API后端 → OAuth 2.0 + PKCE 带JWT访问令牌
  3. 服务到服务 → 客户端凭证流程或mTLS
  4. 向现有添加MFA → TOTP认证器应用(最小),Passkeys(理想)
  5. 传统Web应用 → 会话Cookie + CSRF令牌
  6. CLI/设备无浏览器 → 设备授权流程

安全检查清单

令牌安全

  • [ ] 短期访问令牌(5-15分钟)
  • [ ] 安全刷新令牌存储
  • [ ] 令牌撤销机制
  • [ ] 适当令牌验证(签名、声明、过期)

OAuth/OIDC安全

  • [ ] 所有公共客户端使用PKCE
  • [ ] 严格验证重定向URI
  • [ ] 验证state参数
  • [ ] 为OIDC验证nonce
  • [ ] 使用精确重定向URI匹配

会话安全

  • [ ] HttpOnly、Secure、SameSite Cookie
  • [ ] 认证后会话重新生成
  • [ ] 适当会话无效化
  • [ ] 空闲和绝对超时

MFA安全

  • [ ] 所有账户MFA(强制执行或鼓励)
  • [ ] 安全恢复代码
  • [ ] 速率限制MFA尝试
  • [ ] 优先抗钓鱼方法

参考

相关技能

技能 关系
authorization-models 认证后,应用授权(RBAC、ABAC)
cryptography 令牌和密码的基础密码学
api-security 用认证保护API端点
secure-coding 通用安全模式

版本历史

  • v1.0.0 (2025-12-26): 初始发布,含JWT、OAuth 2.0、Passkeys、MFA、会话

最后更新: 2025-12-26