密码作为唯一的安全凭证,在今天看来已经不那么可靠了。无论是钓鱼攻击、数据库泄露还是暴力破解,任何一种威胁都可能让你的登录凭据失效。为了应对这些挑战,我们通常会在 Web 应用中引入额外的安全层。本文将深入探讨在 ASP.NET Core 中实现两大主流安全加固方案的路径:传统的双因子认证(2FA)和代表未来的 Passkey 无密码认证。我们将剖析它们的工作原理,并为你提供在不同场景下的选型建议。

为什么密码不够用
攻击者获取密码的手段主要集中为三类:通过伪造的钓鱼页面诱导用户输入、攻击数据库导致用户凭证批量泄露、以及利用其他网站泄露的账号密码进行“撞库”攻击。在这些场景下,双因子认证 之所以有效,是因为它要求攻击者除了密码之外,还必须通过第二个独立的验证因素,这大大增加了攻击的难度。
通常,第二验证因素可以分为以下三类:
- 你知道的:密码、PIN码。
- 你拥有的:手机、TOTP验证器 App(如 Google Authenticator)、硬件安全密钥(如 YubiKey)。
- 你本身的:指纹、面容 ID 等生物特征。
一旦为账户启用了 2FA,即使密码不幸泄露,攻击者也无法仅凭密码完成登录,系统安全级别因此得到显著提升。
ASP.NET Core Identity 的 2FA 支持
ASP.NET Core Identity 框架内置了对多种 2FA 方式的支持,包括电子邮件验证码、短信验证码、基于 TOTP 的验证器 App,以及用于应急的恢复码。
整个认证流程可以清晰地分为两个步骤:
第一步:用户名与密码登录
当用户提交登录表单后,后端通常使用 SignInManager 进行验证:
var result = await _signInManager.PasswordSignInAsync(
model.Email,
model.Password,
model.RememberMe,
lockoutOnFailure: true
);
if (result.RequiresTwoFactor)
{
return RedirectToAction("VerifyCode");
}
如果用户账户已启用双因子认证,PasswordSignInAsync 方法将返回 RequiresTwoFactor = true,此时应用需要将流程引导至第二步的验证码输入页面。
第二步:第二因素验证码校验
Identity 支持通过不同的 Provider(如“Email”、“Phone”、“Authenticator”)来生成和验证一次性验证码。
生成验证码并发送的示例(以邮件为例):
// 生成邮件验证码并发送
var code = await _userManager.GenerateTwoFactorTokenAsync(user, "Email");
验证用户提交的验证码:
var result = await _userManager.VerifyTwoFactorTokenAsync(
user,
_userManager.Options.Tokens.AuthenticatorTokenProvider,
"Email",
code
);

配置 TOTP 验证器 App
Google Authenticator、Microsoft Authenticator、Authy 等 App 基于 TOTP(基于时间的一次性密码)算法工作,它会每隔 30 秒生成一个新的 6 位数字验证码。
为 ASP.NET Core 用户启用 TOTP 验证的典型流程如下:
- 服务端为用户生成一个唯一的密钥(secret key)。
- 将这个密钥编码为 QR 码,并展示给用户。
- 用户使用验证器 App 扫描此 QR 码,完成设备绑定。
- 此后每次登录,用户在 App 中看到的动态码就是需要输入的第二因素。
生成用户密钥的核心代码如下:
var authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(authenticatorKey))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
获取到 authenticatorKey 后,通常可以使用像 QRCoder 这样的 NuGet 库将其渲染成 QR 码图片并嵌入到前端页面中,方便用户扫描。
恢复码:用户丢失设备时的退路
如果用户更换了手机或丢失了已绑定 TOTP 的设备,第二因素验证就会失败。为此,我们需要提供“恢复码”作为备用方案。恢复码是系统预先生成的一组一次性使用码:
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
生成后,必须提示用户立即并妥善保存这些恢复码(例如截图、打印或存入密码管理器)。服务端不应存储这些恢复码的明文,以保障其安全性。
Passkey:下一代无密码认证
2FA 是在密码基础上增加安全层,而 Passkey 的思路则是彻底抛弃密码。Passkey 基于 FIDO2 / WebAuthn 标准,它使用非对称加密技术替代了传统的共享密钥(密码)模型。
它的核心机制非常巧妙:
- 注册时:用户的设备(手机、电脑)在本地安全区域生成一对密钥(公钥和私钥)。私钥永远不离开设备,公钥则被上传并存储于服务器。
- 登录时:服务器发送一个随机生成的“挑战”(challenge)字符串给浏览器。用户的设备使用本地私钥对该挑战进行签名,然后将签名结果返回。
- 验证时:服务器使用之前存储的对应公钥来验证收到的签名是否有效。
由于私钥从未离开过用户设备,并且签名是针对特定网站域名(origin)的,这使得传统的钓鱼攻击从根本上失效——攻击者即便伪造了一个一模一样的登录页面,也无法获得能够通过目标服务器验证的有效签名。

2FA 与 Passkey 的核心对比
| 特性 |
传统 2FA |
Passkey |
| 是否需要密码 |
是 |
否 |
| 用户体验 |
多步骤(输密码 + 输验证码) |
一步完成(生物识别或设备 PIN) |
| 安全模型 |
共享密钥(密码 + 动态码) |
公钥加密(挑战-响应) |
| 抗钓鱼能力 |
中等(验证码可能被实时钓鱼) |
非常高(签名与域名绑定) |
| 设备集成 |
依赖独立的验证器 App |
深度集成系统生物识别与安全芯片 |
在 ASP.NET Core 中集成 Passkey
为 ASP.NET Core 应用添加 Passkey 支持,可以使用开源的 Fido2.AspNet 库来简化 WebAuthn 协议的集成。
首先,通过 NuGet 安装包:
dotnet add package Fido2.AspNet --version 4.0.0
Passkey 注册流程:
- 用户在前端点击“注册 Passkey”按钮。
- 服务端生成一个 WebAuthn 注册挑战(challenge)并发送给浏览器。
- 浏览器唤起操作系统级的生物识别验证对话框(如指纹、面容识别)。
- 用户验证通过后,设备在安全模块内生成密钥对,并将公钥发送回服务器。
- 服务器存储该公钥,与用户账户关联,完成注册。
Passkey 登录流程:
- 用户选择“使用 Passkey 登录”。
- 服务器生成登录挑战并发送。
- 浏览器再次唤起生物识别验证。
- 用户验证通过,设备使用对应的私钥为挑战签名,并将签名结果返回。
- 服务器用存储的公钥验证签名,验证通过即登录成功。

安全实践要点
无论你最终选择哪种认证加固方案,在 ASP.NET Core 应用中实施时,以下几点都是必须落实的安全基线:
- 启用账户锁定:在登录失败多次后自动锁定账户(
PasswordSignInAsync 中的 lockoutOnFailure: true 参数)。
- 强制邮箱验证:确保用户注册邮箱的真实性,这对于密码重置和邮件2FA至关重要。
- 安全处理恢复码:恢复码必须设计为一次性使用,生成后服务端不保存明文。
- 全站强制 HTTPS:防止认证过程中的中间人攻击,这是使用 WebAuthn 的基本要求。
- 记录认证日志:详细记录登录成功、失败、账户锁定等关键安全事件,便于审计和异常分析。
- 提供用户管理界面:允许用户自行启用/禁用 2FA、查看恢复码、注册或删除 Passkey。
选型与实施建议
对于现有的、已基于密码运行的 ASP.NET Core 系统,一个稳妥的升级策略是:首先引入 TOTP 验证器 App 作为主要的 2FA 方式,同时提供邮件验证码作为备选方案,并为用户生成恢复码。这种组合能在显著提升安全性的同时,保持较好的用户接受度。
对于新建系统或对用户体验有更高要求的场景,可以考虑将 Passkey 作为可选的无密码登录方式,与传统的密码+2FA 认证方案并存。这样既可以允许用户逐步适应新技术,也能为未来的全面无密码化做好准备。
你可以在 GitHub 上找到一些技术文档和完整的示例项目来深入学习,例如搜索包含 ASP.NET Core Identity、2FA 和 Fido2 等关键词的开源仓库,它们通常能提供生产级的最佳实践参考。
希望这篇关于 ASP.NET Core 安全认证的梳理能对你有所帮助。如果你想与其他开发者交流此类架构或安全实践,欢迎到云栈社区的相关板块参与讨论。