7.4 KiB
7.4 KiB
首次登录强制修改密码 - 最终实现总结
功能概述
实现了一个安全的首次登录强制修改密码功能,当管理员的 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()
测试验证
必测场景
- ✅ 登录后自动跳转到修改密码页面
- ✅ 直接输入 URL 无法绕过
- ✅ 刷新页面仍然在修改密码页面
- ✅ 修改密码成功后
is_paw更新为 1 - ✅ 使用新密码登录后正常进入系统
- ✅ 多标签页测试
- ✅ 密码验证规则测试
- ✅ 企业微信登录测试
测试工具
- 浏览器开发者工具(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() 函数。
安全等级
- 修复前:⚠️ 低(可绕过)
- 修复后:✅ 高(无法绕过)
维护建议
- 定期审计管理员账号的
is_paw状态 - 为新创建的管理员默认设置
is_paw = 0 - 定期要求管理员修改密码
- 考虑添加密码强度验证
- 考虑添加密码历史记录
完成时间
2024-XX-XX
版本
v1.0 - 初始实现 v1.1 - 修复刷新绕过问题