Skip to content

WebAuthn 功能测试


WebAuthn 功能演示

操作日志

技术说明

  • 使用 navigator.credentials.create() 创建公钥凭证
  • 使用 navigator.credentials.get() 进行身份验证
  • 凭证信息存储在浏览器 Cookie 中(仅用于演示)
  • 实际应用中应将公钥存储在服务器端

Node.js/22

服务端的SQL TABLE 定义

sql
-- ==================== 用户表 ====================
-- 存储用户基本信息
CREATE TABLE Users (
    UserId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    Username NVARCHAR(255) NOT NULL UNIQUE,
    DisplayName NVARCHAR(255) NOT NULL,
    -- WebAuthn 需要的用户句柄(随机生成的字节)
    UserHandle VARBINARY(64) NOT NULL UNIQUE,
    CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    UpdatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    IsActive BIT NOT NULL DEFAULT 1,
    
    -- 索引
    INDEX IX_Users_Username (Username),
    INDEX IX_Users_UserHandle (UserHandle)
);
GO

-- ==================== WebAuthn 凭证表 ====================
-- 存储用户的 WebAuthn 凭证信息
CREATE TABLE WebAuthnCredentials (
    CredentialId NVARCHAR(512) PRIMARY KEY,  -- Base64 编码的凭证 ID
    UserId UNIQUEIDENTIFIER NOT NULL,
    
    -- 公钥信息(COSE 格式,Base64 编码)
    PublicKey NVARCHAR(MAX) NOT NULL,
    PublicKeyAlgorithm INT NOT NULL,  -- COSE 算法标识符(-7=ES256, -257=RS256)
    
    -- 凭证类型和传输方式
    CredentialType NVARCHAR(50) NOT NULL DEFAULT 'public-key',
    Transports NVARCHAR(255),  -- JSON 数组:["internal", "usb", "nfc", "ble"]
    
    -- 认证器信息
    AAGUID UNIQUEIDENTIFIER NULL,  -- 认证器的全局唯一标识符
    AttestationFormat NVARCHAR(50),  -- attestation 格式
    
    -- 签名计数器(用于检测克隆的认证器)
    SignCounter BIGINT NOT NULL DEFAULT 0,
    
    -- 凭证元数据
    DeviceName NVARCHAR(255),  -- 设备名称(可选)
    UserAgent NVARCHAR(500),   -- 注册时的 User-Agent
    
    -- 时间戳
    CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    LastUsedAt DATETIME2 NULL,
    
    -- 凭证状态
    IsActive BIT NOT NULL DEFAULT 1,
    
    -- 外键约束
    CONSTRAINT FK_WebAuthnCredentials_Users 
        FOREIGN KEY (UserId) REFERENCES Users(UserId) 
        ON DELETE CASCADE,
    
    -- 索引
    INDEX IX_WebAuthnCredentials_UserId (UserId),
    INDEX IX_WebAuthnCredentials_CreatedAt (CreatedAt),
    INDEX IX_WebAuthnCredentials_LastUsedAt (LastUsedAt)
);
GO

-- ==================== 认证挑战表(可选)====================
-- 存储临时的 challenge,用于验证注册和认证请求
CREATE TABLE WebAuthnChallenges (
    ChallengeId UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    Challenge VARBINARY(64) NOT NULL,  -- 原始的 challenge 字节
    ChallengeBase64 NVARCHAR(128) NOT NULL,  -- Base64 编码的 challenge
    UserId UNIQUEIDENTIFIER NULL,  -- 可以为空(注册时还没有用户)
    Username NVARCHAR(255),  -- 用于注册时关联用户名
    
    -- Challenge 类型
    ChallengeType NVARCHAR(20) NOT NULL,  -- 'registration' 或 'authentication'
    
    -- 时间戳和过期
    CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    ExpiresAt DATETIME2 NOT NULL,  -- 通常 5 分钟后过期
    IsUsed BIT NOT NULL DEFAULT 0,
    
    -- 索引
    INDEX IX_WebAuthnChallenges_ExpiresAt (ExpiresAt),
    INDEX IX_WebAuthnChallenges_Challenge (ChallengeBase64)
);
GO

-- ==================== 认证日志表(可选)====================
-- 记录所有的认证尝试,用于审计和安全分析
CREATE TABLE WebAuthnAuthenticationLogs (
    LogId BIGINT IDENTITY(1,1) PRIMARY KEY,
    UserId UNIQUEIDENTIFIER,
    CredentialId NVARCHAR(512),
    
    -- 认证结果
    IsSuccess BIT NOT NULL,
    FailureReason NVARCHAR(500),
    
    -- 请求信息
    IpAddress NVARCHAR(45),  -- IPv6 最大长度
    UserAgent NVARCHAR(500),
    
    -- 时间戳
    CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    
    -- 索引
    INDEX IX_WebAuthnAuthenticationLogs_UserId (UserId),
    INDEX IX_WebAuthnAuthenticationLogs_CreatedAt (CreatedAt),
    INDEX IX_WebAuthnAuthenticationLogs_IsSuccess (IsSuccess)
);
GO

-- ==================== 清理过期 Challenge 的存储过程 ====================
CREATE PROCEDURE sp_CleanupExpiredChallenges
AS
BEGIN
    SET NOCOUNT ON;
    
    DELETE FROM WebAuthnChallenges
    WHERE ExpiresAt < GETUTCDATE();
    
    SELECT @@ROWCOUNT AS DeletedCount;
END;
GO

-- ==================== 创建定期清理任务(可选)====================
-- 可以使用 SQL Server Agent 定期执行清理过程
/*
EXEC sp_CleanupExpiredChallenges;
*/

-- ==================== 示例查询 ====================

-- 查询用户的所有活跃凭证
/*
SELECT 
    c.CredentialId,
    c.DeviceName,
    c.CreatedAt,
    c.LastUsedAt,
    c.SignCounter
FROM WebAuthnCredentials c
WHERE c.UserId = @UserId 
    AND c.IsActive = 1
ORDER BY c.LastUsedAt DESC;
*/

-- 查询用户的认证历史
/*
SELECT TOP 20
    l.CreatedAt,
    l.IsSuccess,
    l.FailureReason,
    l.IpAddress,
    l.UserAgent
FROM WebAuthnAuthenticationLogs l
WHERE l.UserId = @UserId
ORDER BY l.CreatedAt DESC;
*/

-- ==================== 注释说明 ====================
/*
关键字段说明:

1. CredentialId: 
   - 每个凭证的唯一标识符
   - 由认证器生成,Base64 编码存储
   - 客户端发送认证请求时会包含此 ID

2. PublicKey:
   - COSE 格式的公钥,Base64 编码
   - 用于验证客户端发来的签名
   - 永远不要存储私钥!

3. SignCounter:
   - 认证器的签名计数器
   - 每次认证后应该递增
   - 如果新值小于存储值,可能表示凭证被克隆

4. UserHandle:
   - WebAuthn 规范要求的用户标识符
   - 应该是随机生成的字节,不能是可预测的值
   - 用于隐私保护,不应该包含 PII

5. Challenge:
   - 临时存储,用于防止重放攻击
   - 应该在使用后标记为已使用
   - 建议设置较短的过期时间(如 5 分钟)
*/

Last updated: