跳到主要内容

登录安全设计:设备验证与二次验证

1. 为什么需要这套机制

密码只解决「你是谁」的问题,不解决「你在这台设备上登录是否安全」的问题。

现实中两个常见场景:

  1. 密码泄露:用户在网吧登录后忘记退出,或密码被钓鱼网站窃取。攻击者拿到密码就能在任何设备上登录。
  2. 新设备风险:用户换了新手机/浏览器,系统无法判断是本人还是攻击者。

设备信任解决场景 2——新设备需要额外验证,验证通过后标记为受信,后续免验证。 2FA 解决场景 1——即使密码泄露,攻击者没有邮箱验证码也登不进来。

新设备登录模式(全局配置)

管理员可通过系统设置 Device.NewDeviceMode 控制新设备的处理方式:

模式行为适用场景
verify(默认)新设备必须输入验证码才能登录安全要求高
notify新设备仅发送提醒邮件,不阻断登录不想打扰用户

notify 模式下,邮件使用纯通知模板(无验证码),告知用户「有人在新设备登录了你的账号」,如果不是本人就去改密码。

2. 为什么不做成两套独立机制

传统做法是 2FA 和设备信任各管各的,但这带来两个问题:

用户体验差

用户开启 2FA 后,每次登录都要收邮件、输验证码,即使是常用设备。用了一周后大多数人会关掉 2FA。

管理成本高

用户需要在两个地方分别管理:2FA 开关、设备列表的信任状态。概念重叠,容易混淆。

飞书的做法

飞书将两者合并:设备信任是 2FA 的优化层

  • 2FA 关闭时:新设备验证,受信设备免验证(设备信任发挥作用)
  • 2FA 开启时:每次都验证(安全优先),但设备信任状态保留

这样 2FA 不会因为「太烦」被关掉——因为受信设备在 2FA 关闭时能享受免验证。

3. 核心判定逻辑

bool needVerify = result.RequiresTwoFactor || !isTrusted;

一行代码,四个场景:

2FA设备信任结果为什么
信任直接登录常用设备,无需打扰
未信任发验证码新设备,需要确认身份
信任发验证码2FA 要求每次验证,安全优先
未信任发验证码新设备 + 2FA,双重确认

关键:2FA 开启时信任不跳过验证。这是和「两套独立机制」的核心区别——信任状态不覆盖 2FA 策略,只在 2FA 关闭时生效。

4. 信任设备的设计决策

为什么信任有有效期(180天)

永久信任意味着一旦设备被标记,即使设备被转卖、被盗,攻击者也能永久免验证。180 天是安全性和便利性的平衡点——用户每半年至少验证一次,足够宽松不打扰日常使用。

为什么信任不绑定 IP

指纹 = SHA256(UserAgent)[前32位]

最初设计了 SHA256(UserAgent + "|" + IP) 的方案,但 IP 太不稳定——用户切换 WiFi、4G/5G、VPN 都会变。每次 IP 变化都要求重新验证,体验很差。

只用 UserAgent 的代价是:同一台设备的不同浏览器会被视为不同设备(Chrome 和 Firefox 各自独立)。这是可接受的,因为不同浏览器确实有不同的 Cookie/登录态。

为什么信任操作只在登录时

最初设计了两种信任方式:

  1. 登录验证弹窗中勾选「信任此设备」
  2. 设备管理页面手动点击「信任」

第二种被去掉了,原因:

  • 设备管理页面是「事后管理」,用户在那里信任一台设备时,这台设备可能并没有在登录时通过验证
  • 信任应该和验证绑定——先证明「我能收到验证码」,再标记信任
  • 减少攻击面:如果攻击者能访问设备管理页面(比如通过 XSS),他就能把自己的设备标记为信任

5. 2FA 开关的设计决策

为什么用户可以自己开关

2FA 由用户自主控制,而不是管理员强制。原因:

  • 这是一个 C 端系统,用户有自主权
  • 强制 2FA 会导致用户流失(注册时就要验证邮箱已经是门槛了)
  • 用户决定自己需要多高的安全级别

为什么关闭 2FA 不需要验证码

开启时需要验证码(确认邮箱可达),关闭时不需要。原因:

  • 关闭 2FA 会降低安全性,攻击者没有动机诱导用户关闭
  • 用户已经通过了登录验证(密码 + 可能的设备验证),身份已确认
  • 减少关闭流程的摩擦,避免用户因为「关都关不掉」而抱怨

信任状态在 2FA 关闭后为什么保留

用户开启 2FA → 用了三个月 → 关闭 2FA。此时设备列表中的信任状态仍然有效。

因为信任状态和 2FA 是独立维度:

  • 信任 = 「这台设备是安全的」
  • 2FA = 「每次登录都要额外验证」

关闭 2FA 后,「这台设备是安全的」这个事实没有改变。受信设备立刻恢复免验证,用户不需要重新信任。

6. 验证码 vs 其他方案

为什么用邮箱验证码而不是 TOTP

方案优点缺点
邮箱验证码无需安装 App,所有用户都有邮箱依赖邮件服务可用性
TOTP(Google Authenticator)不依赖网络需要安装 App,换手机时需要迁移
短信验证码用户熟悉有成本,SIM 卡劫持风险

邮箱验证码是最低门槛的方案,适合 C 端系统。如果未来需要更高安全级别,可以加 TOTP 作为可选项。

为什么不做成链接而是验证码

邮件中的链接(magic link)点击后直接登录,但:

  • 移动端邮件客户端点击链接可能跳转到浏览器,而不是 App
  • 链接被邮件安全网关预请求会导致提前失效
  • 验证码让用户在当前页面输入,流程更可控

7. 登录流程中不受影响的路径

以下流程不走 LoginAsync,不受设备验证影响:

流程原因
模拟登录管理员操作,已有管理后台的访问控制
第三方登录已经通过 OAuth 提供商验证身份
验证码登录邮箱验证码本身就是第二因素
刷新 TokenToken 有效期内的续期,不涉及新设备判断