跳到主要内容

设备验证

设备验证是登录流程的一部分(并非独立的微服务或独立控制器),用于在用户登录时识别登录设备、对新设备/未受信设备做二次验证或提醒,从而提升账号安全。

  • 设备记录与信任逻辑由领域服务 IUserLoginDeviceManagerUserLoginDeviceManager)实现。
  • 对外接口集成在 AccountControllerapi/identity/account)的 登录设备验证 两个接口中。

⚠️ 注意:本模块没有 DeviceService / IDeviceService 类,也没有 AddDeviceVerification(...) 扩展方法,更没有 Security:DeviceVerification 配置节或独立的 GET/POST/DELETE /api/identity/device* 接口。设备验证是默认内置行为,无需显式注册。

工作原理

登录时(POST /api/identity/account/login)的处理顺序:

  1. 校验用户名/邮箱与密码。
  2. 计算设备指纹 SHA256(UserAgent) 取前 32 位十六进制字符串,调用 CheckAndRecordDeviceAsync 记录或更新该设备的登录信息。
  3. 调用 IsDeviceTrustedAsync 判断当前设备是否受信任(已信任且未过期)。
  4. 统一判断是否需要二次验证:
    • 需要验证 的条件:RequiresTwoFactor(开启了 2FA) 设备未受信。
    • 不需要验证:受信设备且未开启 2FA → 直接签发 Token 返回。
  5. 若需要验证:
    • 开启 2FA,或新设备且 Device.NewDeviceMode = verify → 生成 6 位邮箱验证码与 VerifyToken(缓存 5 分钟),发送验证邮件,返回「需要验证 + VerifyToken + 脱敏邮箱 + 是否可信任」。
    • 新设备且 Device.NewDeviceMode = notify默认)→ 仅发送「新设备登录提醒」邮件,不阻断登录,直接签发 Token 返回。
  6. 前端拿到 VerifyToken 后调用 POST /api/identity/account/verify-device,传入 VerifyToken / VerifyCode / TrustDevice 完成验证并签发 Token;若 TrustDevice = true,则将该设备标记为受信(默认有效期 180 天)。

设备指纹

DeviceFingerprint = SHA256(UserAgent) 的前 32 位十六进制
  • 基于 UserAgent 识别设备,IP 变化(如切换 WiFi / 4G)不影响设备识别
  • 同一浏览器在不同网络下仍会被识别为同一设备。

配置

设备验证相关配置有两类,均不是 Security:DeviceVerification

配置位置默认值说明
Device.NewDeviceMode系统设置(动态,可在后台「系统设置 → 设备验证」中配置)notify新设备登录模式:notify = 仅发送提醒邮件(不阻断登录);verify = 要求邮箱验证码
Device:TrustExpireDaysappsettings.json180信任设备的有效期(天),到期后需重新验证

示例(appsettings.json):

{
"Device": {
"TrustExpireDays": 180
}
}

没有 MaxDevices 设备数量上限逻辑,文档中若出现「设备数量超限」属于错误描述。

实体 UserLoginDevice

设备记录实体以 UserId 为外键,核心字段是 DeviceFingerprint(指纹)、IsTrusted(是否受信)、TrustExpireTime(信任过期时间,为 null 表示未信任或已过期)。IsTrustedAndValid 是运行时计算字段(已信任且未过期),IsCurrentDevice 标记是否为当前登录设备。完整字段请看源码 FreeKit.Identity/Models/UserLoginDevice.cs

文档中出现的 OnlineUser(含 ConnectionId / SignalR)实体与本模块无关,属于错误内容。

异常

  • 设备验证相关错误统一抛出 BusinessException(如「验证码已过期,请重新登录」「验证码错误」「设备不存在」),由全局过滤器转换为 400,而非 NotFoundException

相关文档