Appearance
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 分钟)
*/