为什么 WebAuthn 看起来很容易——直到你尝试交付它
Source: Dev.to
每个 WebAuthn 演示都能正常工作。真正的生产环境才是问题悄然出现的地方。
WebAuthn 演示具有危险的说服力。你按照教程操作,注册一个通行密钥,成功认证,一切似乎… 已经解决。
没有密码。没有一次性验证码。没有摩擦。
然后你将其上线。瞬间:
- 用户在不该认证的情况下进行认证
- 计数器表现异常
- 不同浏览器之间出现分歧
- 企业客户提出你的演示从未准备过的问题
这种差距——介于 “它能工作” 与 “它能在生产环境中存活” 之间——正是大多数 WebAuthn 实现失败的地方。
在观察团队多年重复同样错误后,三个差距一次又一次地显现。
Gap #1: 演示级别的数据库访问
大多数 WebAuthn 演示都会包含以下这种代码:
async function getUser(username) {
const query = `SELECT * FROM users WHERE username = '${username}'`;
return db.query(query);
}
在开发、测试,甚至预发布环境中它可以正常工作——直到真实的用户名中出现引号,或有人尝试 ' OR '1'='1。
这不是 WebAuthn 的问题,而是演示忽视的 生产工程 问题。生产系统会对所有查询进行参数化,包括动态的 IN 子句:
function buildInClause(values) {
if (!Array.isArray(values) || values.length === 0) {
return { clause: '(NULL)', params: [] };
}
const placeholders = values.map(() => '?').join(',');
return {
clause: `(${placeholders})`,
params: Array.from(values)
};
}
区别不在于代码的优雅——而在于 生存能力。
差距 #2:“一次调用”验证幻觉
演示验证通常是这样的:
await fido2.verify(response);
简洁、最小,但完全不足。在生产环境中,这一次调用隐藏了多个攻击面:
- 重放攻击
- 计数器回滚(克隆的认证器)
- 来源伪造
- 跨会话的挑战重用
- 原生应用来源的边缘情况
真实的验证逻辑会对每一层进行校验:
if (counter <= prevCounter && counterSupported) {
throw new Error("counter rollback detected");
}
if (origin !== expectedOrigin) {
throw new Error("origin mismatch");
}
计数器、来源、编码、挑战绑定、RP ID——这些在生产环境中都不是可选的。如果跳过其中任何一步,认证仍然“能工作”——直到它不再工作。
Gap #3: “单域” WebAuthn 的神话
演示假设:
- 一个 RP ID
- 一个域名
- 一个策略
生产环境并非如此。真实系统需要处理:
- 多个子域名
- 通配符 RP ID
- 企业级认证器白名单(基于 AAGUID)
- 每个用户的设备数量限制
- 按租户的超时设置
- 设备绑定
配置不再是常量,而是 数据:
{
"domain": ".example.com",
"device_limit": 2,
"registration_session_timeout": 999
}
这并非为了制造复杂性,而是支持真实组织的最低要求。
“Production‑Ready” 实际含义
| 领域 | 演示现实 | 生产现实 |
|---|---|---|
| 数据库 | String queries | Parameterized everywhere |
| 验证 | Single function | Multi‑layer validation |
| 域名 | localhost | Wildcards & subdomains |
| 计数器 | Ignored | Rollback detection |
| 策略 | Hardcoded | Per‑tenant configuration |
这些失败在演示中并不明显。它们全部会在用户信任你的系统后显现。
关于 WebAuthn 的严酷真相
WebAuthn 很容易演示。却很难安全运营。问题不在于标准本身——而在于把一次成功的演示等同于可交付系统的错觉。
如果你计划在原型之外部署 Passkey,请把演示当作教育工具,而不是架构参考。在身份验证领域,失败往往不像错误那样显现;它表现为成功——但却是错误的用户。
我从事需要在真实流量、浏览器以及企业约束下生存的 WebAuthn 系统工作。上面的大多数经验教训,都是在修复那些“运行良好”——直到它们不再正常的情况时得到的。