332 lines
8.8 KiB
Markdown
332 lines
8.8 KiB
Markdown
# 消息通知权限问题修复说明
|
||
|
||
## 🚨 严重问题
|
||
|
||
### 问题描述
|
||
**所有用户(陪伴员、管理师、家长等)都能看到相同的消息列表,而不是只看到自己的消息。**
|
||
|
||
从截图可以看到:
|
||
- 显示了8335条未读消息
|
||
- 所有消息都是"服务提醒"
|
||
- 不同用户登录后看到的消息列表完全相同
|
||
|
||
### 问题根源
|
||
|
||
#### 1. JWT拦截器配置错误
|
||
|
||
**文件:** `peidu/backend/src/main/java/com/peidu/config/WebMvcConfig.java`
|
||
|
||
**问题代码(第47行):**
|
||
```java
|
||
registry.addInterceptor(jwtInterceptor)
|
||
.addPathPatterns("/api/**")
|
||
.excludePathPatterns(
|
||
// ... 其他排除路径
|
||
"/api/notification/**", // ⚠️ 消息通知接口被排除在JWT拦截器之外
|
||
// ...
|
||
);
|
||
```
|
||
|
||
**后果:**
|
||
1. 消息通知接口不需要登录就能访问(安全漏洞)
|
||
2. JWT拦截器不会解析token
|
||
3. request中没有userId属性
|
||
4. 所有用户都无法获取到自己的userId
|
||
|
||
#### 2. 默认返回userId=1
|
||
|
||
**文件:** `peidu/backend/src/main/java/com/peidu/controller/NotificationController.java`
|
||
|
||
**问题代码(第33-40行):**
|
||
```java
|
||
private Long getCurrentUserId(HttpServletRequest request) {
|
||
Object userId = request.getAttribute("userId");
|
||
if (userId != null) {
|
||
return Long.valueOf(userId.toString());
|
||
}
|
||
// 默认返回测试用户ID
|
||
return 1L; // ⚠️ 所有用户都返回userId=1
|
||
}
|
||
```
|
||
|
||
**后果:**
|
||
1. 当无法获取userId时,默认返回1L
|
||
2. 所有用户查询的都是userId=1的消息
|
||
3. 导致所有用户看到相同的消息列表
|
||
|
||
### 数据流程分析
|
||
|
||
```
|
||
用户登录 → 获取token → 前端调用 /api/notification/list
|
||
↓
|
||
❌ JWT拦截器被跳过(因为路径被排除)
|
||
↓
|
||
NotificationController.getList()
|
||
↓
|
||
getCurrentUserId() → request.getAttribute("userId") = null
|
||
↓
|
||
❌ 返回默认值 userId = 1L
|
||
↓
|
||
查询 notification 表 WHERE user_id = 1
|
||
↓
|
||
❌ 所有用户都看到userId=1的消息
|
||
```
|
||
|
||
## ✅ 修复方案
|
||
|
||
### 修复1:移除notification接口的JWT拦截器排除
|
||
|
||
**文件:** `peidu/backend/src/main/java/com/peidu/config/WebMvcConfig.java`
|
||
|
||
**修改前:**
|
||
```java
|
||
.excludePathPatterns(
|
||
// ...
|
||
"/api/notification/**", // ⚠️ 错误:排除了消息通知接口
|
||
// ...
|
||
);
|
||
```
|
||
|
||
**修改后:**
|
||
```java
|
||
.excludePathPatterns(
|
||
// ...
|
||
// "/api/notification/**", // ⚠️ 已移除:消息通知需要登录才能访问
|
||
// ...
|
||
);
|
||
```
|
||
|
||
### 修复2:getCurrentUserId抛出异常而不是返回默认值
|
||
|
||
**文件:** `peidu/backend/src/main/java/com/peidu/controller/NotificationController.java`
|
||
|
||
**修改前:**
|
||
```java
|
||
private Long getCurrentUserId(HttpServletRequest request) {
|
||
Object userId = request.getAttribute("userId");
|
||
if (userId != null) {
|
||
return Long.valueOf(userId.toString());
|
||
}
|
||
// 默认返回测试用户ID
|
||
return 1L; // ⚠️ 错误:返回默认值
|
||
}
|
||
```
|
||
|
||
**修改后:**
|
||
```java
|
||
private Long getCurrentUserId(HttpServletRequest request) {
|
||
Object userId = request.getAttribute("userId");
|
||
if (userId != null) {
|
||
return Long.valueOf(userId.toString());
|
||
}
|
||
// ⚠️ 不应该返回默认值,应该抛出异常
|
||
throw new RuntimeException("无法获取当前用户ID,请先登录");
|
||
}
|
||
```
|
||
|
||
## 🔄 修复后的数据流程
|
||
|
||
```
|
||
用户登录 → 获取token → 前端调用 /api/notification/list
|
||
↓
|
||
✅ JWT拦截器执行
|
||
↓
|
||
解析token → 获取userId → request.setAttribute("userId", userId)
|
||
↓
|
||
NotificationController.getList()
|
||
↓
|
||
getCurrentUserId() → request.getAttribute("userId") = 实际的userId
|
||
↓
|
||
✅ 返回当前登录用户的userId
|
||
↓
|
||
查询 notification 表 WHERE user_id = 当前用户的userId
|
||
↓
|
||
✅ 每个用户只看到自己的消息
|
||
```
|
||
|
||
## 📝 测试步骤
|
||
|
||
### 步骤1:重启后端服务
|
||
|
||
修改了拦截器配置,需要重启后端服务才能生效。
|
||
|
||
### 步骤2:清除前端缓存
|
||
|
||
```bash
|
||
cd peidu/uniapp
|
||
npm run dev:mp-weixin
|
||
```
|
||
|
||
### 步骤3:测试不同用户
|
||
|
||
#### 测试陪伴员A
|
||
1. 使用陪伴员A的账号登录
|
||
2. 进入"消息通知"页面
|
||
3. 查看消息列表
|
||
4. 记录消息数量和内容
|
||
|
||
#### 测试陪伴员B
|
||
1. 使用陪伴员B的账号登录
|
||
2. 进入"消息通知"页面
|
||
3. 查看消息列表
|
||
4. 记录消息数量和内容
|
||
|
||
#### 测试管理师
|
||
1. 使用管理师账号登录
|
||
2. 进入"消息通知"页面
|
||
3. 查看消息列表
|
||
4. 记录消息数量和内容
|
||
|
||
### 步骤4:验证结果
|
||
|
||
**预期结果:**
|
||
- ✅ 不同用户看到的消息列表不同
|
||
- ✅ 每个用户只看到发送给自己的消息
|
||
- ✅ 未读数量显示正确
|
||
- ✅ 消息内容显示正确
|
||
|
||
**如果出现"未登录"错误:**
|
||
- 检查前端是否正确发送了token
|
||
- 检查token是否过期
|
||
- 重新登录获取新token
|
||
|
||
## 🔍 数据验证SQL
|
||
|
||
运行以下SQL检查数据库中的消息分布:
|
||
|
||
```sql
|
||
-- 1. 查看各用户的消息数量分布
|
||
SELECT
|
||
user_id,
|
||
COUNT(*) as total_count,
|
||
SUM(CASE WHEN is_read = 0 THEN 1 ELSE 0 END) as unread_count,
|
||
MAX(create_time) as last_notification_time
|
||
FROM notification
|
||
WHERE deleted = 0
|
||
GROUP BY user_id
|
||
ORDER BY user_id;
|
||
|
||
-- 2. 查看userId=1的消息(之前所有用户都看到的)
|
||
SELECT
|
||
id,
|
||
user_id,
|
||
title,
|
||
content,
|
||
type,
|
||
is_read,
|
||
create_time
|
||
FROM notification
|
||
WHERE user_id = 1 AND deleted = 0
|
||
ORDER BY create_time DESC
|
||
LIMIT 20;
|
||
|
||
-- 3. 查看最近创建的消息
|
||
SELECT
|
||
id,
|
||
user_id,
|
||
title,
|
||
content,
|
||
type,
|
||
is_read,
|
||
create_time
|
||
FROM notification
|
||
WHERE deleted = 0
|
||
ORDER BY create_time DESC
|
||
LIMIT 50;
|
||
|
||
-- 4. 统计各类型消息的数量
|
||
SELECT
|
||
type,
|
||
COUNT(*) as count,
|
||
SUM(CASE WHEN is_read = 0 THEN 1 ELSE 0 END) as unread_count
|
||
FROM notification
|
||
WHERE deleted = 0
|
||
GROUP BY type
|
||
ORDER BY count DESC;
|
||
```
|
||
|
||
## ⚠️ 其他需要检查的控制器
|
||
|
||
以下控制器也使用了类似的`getCurrentUserId()`方法,需要检查是否有相同的问题:
|
||
|
||
1. `CheckInRecordController` - 签到记录
|
||
2. `ManagerApplicationController` - 管理师申请
|
||
3. `StudentController` - 学生管理
|
||
4. `TeacherController` - 陪伴员管理
|
||
5. `WithdrawController` - 提现管理
|
||
6. `CalendarController` - 日历管理
|
||
|
||
**检查要点:**
|
||
- 这些接口是否被排除在JWT拦截器之外?
|
||
- getCurrentUserId()是否返回默认值?
|
||
- 是否存在类似的权限问题?
|
||
|
||
## 🎯 安全建议
|
||
|
||
### 1. 统一的用户身份获取方式
|
||
|
||
建议创建一个基础控制器类:
|
||
|
||
```java
|
||
public abstract class BaseController {
|
||
|
||
protected Long getCurrentUserId(HttpServletRequest request) {
|
||
Object userId = request.getAttribute("userId");
|
||
if (userId == null) {
|
||
throw new BusinessException(401, "未登录或登录已过期");
|
||
}
|
||
return Long.valueOf(userId.toString());
|
||
}
|
||
|
||
protected Long getCurrentUserIdOrNull(HttpServletRequest request) {
|
||
Object userId = request.getAttribute("userId");
|
||
return userId != null ? Long.valueOf(userId.toString()) : null;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 最小权限原则
|
||
|
||
- 默认所有接口都需要登录
|
||
- 只有明确的公开接口才排除JWT拦截器
|
||
- 定期审查excludePathPatterns配置
|
||
|
||
### 3. 添加日志记录
|
||
|
||
在关键操作中记录userId:
|
||
|
||
```java
|
||
log.info("用户{}查询消息列表,页码:{},类型:{}", userId, page, type);
|
||
```
|
||
|
||
### 4. 添加单元测试
|
||
|
||
测试不同用户只能看到自己的消息:
|
||
|
||
```java
|
||
@Test
|
||
public void testUserCanOnlySeeOwnNotifications() {
|
||
// 测试用户A只能看到自己的消息
|
||
// 测试用户B只能看到自己的消息
|
||
// 测试用户A看不到用户B的消息
|
||
}
|
||
```
|
||
|
||
## 📊 修改文件清单
|
||
|
||
| 文件 | 修改内容 | 状态 |
|
||
|------|---------|------|
|
||
| `peidu/backend/src/main/java/com/peidu/config/WebMvcConfig.java` | 移除notification接口的JWT拦截器排除 | ✅ 已完成 |
|
||
| `peidu/backend/src/main/java/com/peidu/controller/NotificationController.java` | getCurrentUserId抛出异常而不是返回默认值 | ✅ 已完成 |
|
||
| `Archive/[一次性]消息通知权限问题修复说明.md` | 创建修复说明文档 | ✅ 已完成 |
|
||
|
||
---
|
||
|
||
**修复完成时间:** 2026-01-26
|
||
|
||
**修复人员:** Kiro AI
|
||
|
||
**严重程度:** 🚨 高危(安全漏洞)
|
||
|
||
**测试状态:** ⚠️ 需要重启后端服务并测试
|