322 lines
7.4 KiB
Markdown
322 lines
7.4 KiB
Markdown
# 首次登录强制修改密码 - 最终实现总结
|
||
|
||
## 功能概述
|
||
|
||
实现了一个安全的首次登录强制修改密码功能,当管理员的 `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 - 修复刷新绕过问题
|