peixue-dev/Archive/[一次性]消息通知权限问题修复说明.md

332 lines
8.8 KiB
Markdown
Raw Permalink Normal View History

2026-02-28 17:26:03 +08:00
# 消息通知权限问题修复说明
## 🚨 严重问题
### 问题描述
**所有用户(陪伴员、管理师、家长等)都能看到相同的消息列表,而不是只看到自己的消息。**
从截图可以看到:
- 显示了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/**", // ⚠️ 已移除:消息通知需要登录才能访问
// ...
);
```
### 修复2getCurrentUserId抛出异常而不是返回默认值
**文件:** `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
**严重程度:** 🚨 高危(安全漏洞)
**测试状态:** ⚠️ 需要重启后端服务并测试