更新
This commit is contained in:
@@ -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 - 修复刷新绕过问题
|
||||
Reference in New Issue
Block a user