# 修管理后台登录 2FA 设备验证失效 (P0 安全降级)

**日期**: 2026-05-06
**类型**: fix (P0 安全降级,后端配置默认值修复,无前端影响)
**PR**: wx/HL #1699 (已合并 dev,等运维/用户合 main 部署正式)
**关联工单**: #1698
**引入**: 2026-04-03 PR #144 (commit 538635e)

## 问题

切换电脑/浏览器登录管理后台,**应该**触发企业微信扫码 2 次验证 (设备指纹+企微 2FA);**实际**任何账号(不只内置 admin)直接账号密码即可登录,2FA 完全失效。

影响 dev/test/prod 全环境近一个月。

## 根因

`hl-user-service/src/main/resources/application.yml:69-70`:

```yaml
auth:
  2fa:
    enabled: false   # ← 默认关
```

`AdminAuthService.java:155` 跳过判断:

```java
if ("admin".equals(admin.getUsername()) || !twoFactorEnabled) {
    // 直接发 token,跳过设备指纹比对+企微 2FA 流程
}
```

`!twoFactorEnabled` = `!false` = `true` → 任何账号都跳过。

Nacos dev/test/prod 三套 `hl-user-service-{env}.yml` 均**未覆写** `auth.2fa.enabled`,全部吃 application.yml 默认值。

## 引入溯源

PR #144 (2026-04-03 commit `538635e`) "代码安全审计修复(8项)":

> CRITICAL: 2FA跳过改为显式配置(auth.2fa.enabled),移除环境名判断

意图把"用环境名判 dev 跳过"换成"用配置开关",**默认值意外写反** —— 安全修复想让默认 ON,实际写成 OFF。

## 修复

| 文件 | 改动 |
|------|------|
| `hl-user-service/src/main/resources/application.yml` | `enabled: false → true` + 注释说明 |
| `nacos-config/hl-user-service-dev.yml` | 显式覆写 `auth.2fa.enabled: true` 防回归 |
| `hl-user-service/.../config/Auth2faDefaultEnabledAuditTest.java` | 新增守护单测,防 PR #144 类反转再发 |

内置 `admin` 账号在 line 155 第一条件 `"admin".equals(username)` 不变,仍跳过 2FA 避免 super-admin 锁死。

## 影响范围

- **受波及账号**: 所有非内置 admin 的管理员 (即所有真实业务账号)
- **数据污染**: 无 (仅认证流程降级,未污染数据)
- **回滚**: `git revert` 1 commit

## 验证

### 单测
- `mvn test -Dtest=AdminAuthServiceTest` → 58/58 全绿 (含已有的 `login_untrustedDevice_requires2FA` / `login_trustedDevice_issuesToken` / `login_superAdmin_skips2FA_issuesTokenDirectly`)
- `mvn test -Dtest=Auth2faDefaultEnabledAuditTest` → 1/1 (反向验证: 改回 false 立即报 `expected: <true> but was: <false>`)

### 测试服 round-trip (dev 部署后)

```bash
# 期望 1: 非 admin 账号新设备登录 → requireTwoFa=true && token=null
curl -k -X POST 'https://api.test.1814.love:9443/admin/auth/login' \
  -H 'Content-Type: application/json' \
  -H 'X-Device-Fingerprint: NEW_DEVICE_TEST' \
  -d '{"username":"<非admin账号>","password":"<密码>"}'
# 期望: {"code":"200","data":{"requireTwoFa":true,"token":null,...}}

# 期望 2: 内置 admin 仍跳过 2FA
curl -k -X POST 'https://api.test.1814.love:9443/admin/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"Admin@123456"}'
# 期望: {"code":"200","data":{"token":"eyJ...",...}} (token 直接发)

# 期望 3: 已信任设备直发 token (admin_device.is_trusted=true)
# 期望 4: 换浏览器 (不同 fingerprint) → admin_device 不命中 → requireTwoFa=true
```

## 部署计划

- ✅ dev 测试服: 自动部署 (PR 合并触发)
- ⏳ prod: 用户手动合 dev → main 后,运维/管理员通过 [Deploy Panel](https://dep.test.1814.love:9443) 触发 hl-user-service prod 部署。**仅 application.yml 改动,nacos prod 无需推送**(prod nacos 未覆写,jar 内默认值 true 立即生效)。
- 如需 prod nacos 也加显式覆写防回归,在 `正式环境Nacos配置/hl-user-service-prod.yml` 已落本地参考,运维同步推 nacos prod 即可。

## 经验沉淀

落 `memory/experience/backend-dev/yml-default-value-must-be-secure-by-default.md` —
**配置默认值反转规则**: 任何"启用/禁用"类配置 (`enabled`/`required`/`strict`/`validate`),默认值必须取**安全/严格**那一侧:
- `auth.2fa.enabled` 默认 `true` (开启)
- `validate-on-migrate` 默认 `true` (校验)
- `strict-mode` 默认 `true` (严格)
- `cors.allow-all-origins` 默认 `false` (拒绝)

把不安全/松散值留给 nacos 显式覆写,**绝不让 application.yml 默认值成为安全降级源**。

防回归手段: 给关键 `enabled: true` 配置写 `*DefaultEnabledAuditTest` 守护测试,任何回退立即编译期/单测期阻断。

## 通知

- 后端独立修复,无前端改动需求 (mmg/yst 不用动)
- 用户/运维: 部署 prod 后请告知,以便 round-trip 4 个验证场景闭环 + 关闭工单 #1698
