This commit is contained in:
Your Name
2026-03-27 18:06:12 +08:00
parent 9160c36735
commit 099bc1dd22
645 changed files with 276473 additions and 957 deletions
+321
View File
@@ -0,0 +1,321 @@
# 首次登录强制修改密码 - 最终实现总结
## 功能概述
实现了一个安全的首次登录强制修改密码功能,当管理员的 `is_paw` 字段为 0 时,登录后必须修改密码才能使用系统。
## 核心修改
### 后端修改(3 个文件)
#### 1. `server/app/adminapi/logic/LoginLogic.php`
**修改内容:**
- 登录接口返回 `is_paw` 字段
- 添加 `changeFirstPassword()` 方法处理密码修改
**关键代码:**
```php
// 登录时返回 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` 白名单
**关键代码:**
```php
public array $notNeedLogin = ['account', 'workWechatConfig', 'workWechatLogin', 'checkDbColumn', 'changeFirstPassword'];
public function changeFirstPassword() {
// 验证参数
// 调用 LoginLogic::changeFirstPassword()
}
```
#### 3. `server/app/adminapi/logic/auth/AdminLogic.php`
**修改内容:**
-`detail()` 方法的字段列表中添加 `is_paw`
**关键代码:**
```php
$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`(解决刷新问题)
**关键代码:**
```typescript
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`
- 强制跳转到修改密码页面
**关键代码:**
```typescript
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,跳转到修改密码页面
**关键代码:**
```typescript
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`
**修改内容:**
- 添加修改密码路由
**关键代码:**
```typescript
{
path: '/change-password',
component: () => import('@/views/account/change-password.vue')
}
```
### 数据库修改
#### `add_is_paw_field.sql`
```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. 执行数据库脚本
```bash
# Windows
install_first_login_password.bat
# Linux/Mac
chmod +x install_first_login_password.sh
./install_first_login_password.sh
```
### 2. 设置测试账号
```sql
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 - 修复刷新绕过问题