Files
zyt/FINAL_IMPLEMENTATION_SUMMARY.md
T
2026-03-27 18:06:12 +08:00

7.4 KiB
Raw Blame History

首次登录强制修改密码 - 最终实现总结

功能概述

实现了一个安全的首次登录强制修改密码功能,当管理员的 is_paw 字段为 0 时,登录后必须修改密码才能使用系统。

核心修改

后端修改(3 个文件)

1. server/app/adminapi/logic/LoginLogic.php

修改内容:

  • 登录接口返回 is_paw 字段
  • 添加 changeFirstPassword() 方法处理密码修改

关键代码:

// 登录时返回 is_paw
return [
    'name' => $adminInfo['name'],
    'avatar' => $avatar,
    'role_name' => $adminInfo['role_name'],
    'token' => $adminInfo['token'],
    'is_paw' => $admin->is_paw ?? 1,
];

// 修改密码方法
public function changeFirstPassword($token, $password) {
    // 验证 token
    // 更新密码和 is_paw 字段
    // 使 token 过期
}

2. server/app/adminapi/controller/LoginController.php

修改内容:

  • 添加 changeFirstPassword 接口
  • 将该接口添加到 $notNeedLogin 白名单

关键代码:

public array $notNeedLogin = ['account', 'workWechatConfig', 'workWechatLogin', 'checkDbColumn', 'changeFirstPassword'];

public function changeFirstPassword() {
    // 验证参数
    // 调用 LoginLogic::changeFirstPassword()
}

3. server/app/adminapi/logic/auth/AdminLogic.php

修改内容:

  • detail() 方法的字段列表中添加 is_paw

关键代码:

$admin = Admin::field([
    'id', 'account', 'name', 'disable', 'root',
    'multipoint_login', 'avatar', 'is_paw',  // 添加此字段
    // ... 其他字段
])->findOrEmpty($params['id'])->toArray();

前端修改(5 个文件)

1. admin/src/stores/modules/user.ts

修改内容:

  • 添加 isPaw 状态
  • 登录时保存 isPaw
  • 获取用户信息时更新 isPaw(解决刷新问题)

关键代码:

export interface UserState {
    isPaw: number // 0=需要修改密码,1=正常
}

login(playload: any) {
    this.isPaw = data.is_paw ?? 1
}

getUserInfo() {
    this.isPaw = data.user.is_paw ?? 1  // 关键:刷新时从后端获取
}

2. admin/src/permission.ts

修改内容:

  • 添加路由守卫,在获取用户信息后检查 isPaw
  • 强制跳转到修改密码页面

关键代码:

router.beforeEach(async (to, from, next) => {
    if (hasGetUserInfo) {
        // 已获取用户信息,检查 isPaw
        if (userStore.isPaw === 0) {
            next({ path: changePasswordPath })
            return
        }
    } else {
        // 首次获取用户信息
        await userStore.getUserInfo()
        // 获取后检查 isPaw
        if (userStore.isPaw === 0) {
            next({ path: changePasswordPath })
            return
        }
    }
})

3. admin/src/views/account/login.vue

修改内容:

  • 登录成功后检查 is_paw 字段
  • 如果为 0,跳转到修改密码页面

关键代码:

const result: any = await userStore.login(formData)
if (result.is_paw === 0) {
    router.push('/change-password')
    return
}

4. admin/src/views/account/change-password.vue

修改内容:

  • 新建修改密码页面
  • 验证密码规则
  • 修改成功后更新状态并退出登录

5. admin/src/router/routes.ts

修改内容:

  • 添加修改密码路由

关键代码:

{
    path: '/change-password',
    component: () => import('@/views/account/change-password.vue')
}

数据库修改

add_is_paw_field.sql

ALTER TABLE `la_admin` 
ADD COLUMN `is_paw` TINYINT(1) NOT NULL DEFAULT 1 
COMMENT '是否已修改初始密码:0=需要修改,1=正常' 
AFTER `multipoint_login`;

安全机制

1. 强制跳转

  • 登录时检查 is_paw,为 0 则跳转到修改密码页面
  • 路由守卫拦截所有路由,强制跳转

2. URL 防护

  • 即使直接输入 URL,路由守卫也会拦截
  • 无法绕过修改密码页面

3. 刷新防护

  • 页面刷新时从后端重新获取 is_paw 状态
  • 确保状态始终与数据库一致

4. 多标签页防护

  • 所有标签页共享同一个 Vuex store
  • 新打开的标签页也会被拦截

工作流程

1. 管理员登录 (is_paw = 0)
   ↓
2. 后端返回 is_paw = 0
   ↓
3. 前端保存 isPaw = 0 到 store
   ↓
4. 登录页面检查 isPaw,跳转到修改密码页面
   ↓
5. 用户尝试访问其他页面(输入 URL 或刷新)
   ↓
6. 路由守卫检查 isPaw
   ↓
7. 如果 isPaw = 0,强制跳转回修改密码页面
   ↓
8. 用户修改密码成功
   ↓
9. 后端更新 is_paw = 1
   ↓
10. 前端更新 isPaw = 1
   ↓
11. 退出登录
   ↓
12. 使用新密码重新登录
   ↓
13. 路由守卫检查 isPaw = 1,允许正常访问

关键技术点

1. 状态持久化问题

问题: 页面刷新后 Vuex store 中的 isPaw 状态丢失

解决方案:getUserInfo() 方法中从后端重新获取 is_paw 状态

2. 路由守卫时机

问题: 在获取用户信息之前检查 isPaw,此时状态还是初始值

解决方案: 在获取用户信息之后再检查 isPaw

3. 密码加密方式

问题: 使用错误的密码加密方式导致无法登录

解决方案: 使用系统的 create_password() 函数,而不是 password_hash()

测试验证

必测场景

  1. 登录后自动跳转到修改密码页面
  2. 直接输入 URL 无法绕过
  3. 刷新页面仍然在修改密码页面
  4. 修改密码成功后 is_paw 更新为 1
  5. 使用新密码登录后正常进入系统
  6. 多标签页测试
  7. 密码验证规则测试
  8. 企业微信登录测试

测试工具

  • 浏览器开发者工具(Network、Console
  • Vue DevTools
  • 数据库查询工具

文档清单

文档 说明
FIRST_LOGIN_PASSWORD_CHANGE.md 完整功能文档
QUICK_START_FIRST_LOGIN.md 快速开始指南
TEST_CHECKLIST.md 测试清单
SECURITY_FIX_SUMMARY.md 安全修复总结
DEBUG_GUIDE.md 调试指南
add_is_paw_field.sql 数据库字段添加 SQL
test_first_login.sql 测试用 SQL
install_first_login_password.sh Linux/Mac 安装脚本
install_first_login_password.bat Windows 安装脚本

安装步骤

1. 执行数据库脚本

# Windows
install_first_login_password.bat

# Linux/Mac
chmod +x install_first_login_password.sh
./install_first_login_password.sh

2. 设置测试账号

UPDATE la_admin SET is_paw = 0 WHERE id = 1;

3. 测试功能

按照 TEST_CHECKLIST.md 进行测试

常见问题

Q1: 刷新后进入系统了

A: 检查后端 AdminLogic::detail() 方法是否返回 is_paw 字段,前端 getUserInfo() 是否更新状态。

Q2: 可以通过 URL 绕过

A: 检查路由守卫是否正确配置,是否在获取用户信息后检查 isPaw

Q3: 修改密码后无法登录

A: 检查密码加密方式是否使用 create_password() 函数。

安全等级

  • 修复前:⚠️ 低(可绕过)
  • 修复后: 高(无法绕过)

维护建议

  1. 定期审计管理员账号的 is_paw 状态
  2. 为新创建的管理员默认设置 is_paw = 0
  3. 定期要求管理员修改密码
  4. 考虑添加密码强度验证
  5. 考虑添加密码历史记录

完成时间

2024-XX-XX

版本

v1.0 - 初始实现 v1.1 - 修复刷新绕过问题