8.8 KiB
8.8 KiB
消息通知权限问题修复说明
🚨 严重问题
问题描述
所有用户(陪伴员、管理师、家长等)都能看到相同的消息列表,而不是只看到自己的消息。
从截图可以看到:
- 显示了8335条未读消息
- 所有消息都是"服务提醒"
- 不同用户登录后看到的消息列表完全相同
问题根源
1. JWT拦截器配置错误
文件: peidu/backend/src/main/java/com/peidu/config/WebMvcConfig.java
问题代码(第47行):
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
// ... 其他排除路径
"/api/notification/**", // ⚠️ 消息通知接口被排除在JWT拦截器之外
// ...
);
后果:
- 消息通知接口不需要登录就能访问(安全漏洞)
- JWT拦截器不会解析token
- request中没有userId属性
- 所有用户都无法获取到自己的userId
2. 默认返回userId=1
文件: peidu/backend/src/main/java/com/peidu/controller/NotificationController.java
问题代码(第33-40行):
private Long getCurrentUserId(HttpServletRequest request) {
Object userId = request.getAttribute("userId");
if (userId != null) {
return Long.valueOf(userId.toString());
}
// 默认返回测试用户ID
return 1L; // ⚠️ 所有用户都返回userId=1
}
后果:
- 当无法获取userId时,默认返回1L
- 所有用户查询的都是userId=1的消息
- 导致所有用户看到相同的消息列表
数据流程分析
用户登录 → 获取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
修改前:
.excludePathPatterns(
// ...
"/api/notification/**", // ⚠️ 错误:排除了消息通知接口
// ...
);
修改后:
.excludePathPatterns(
// ...
// "/api/notification/**", // ⚠️ 已移除:消息通知需要登录才能访问
// ...
);
修复2:getCurrentUserId抛出异常而不是返回默认值
文件: peidu/backend/src/main/java/com/peidu/controller/NotificationController.java
修改前:
private Long getCurrentUserId(HttpServletRequest request) {
Object userId = request.getAttribute("userId");
if (userId != null) {
return Long.valueOf(userId.toString());
}
// 默认返回测试用户ID
return 1L; // ⚠️ 错误:返回默认值
}
修改后:
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:清除前端缓存
cd peidu/uniapp
npm run dev:mp-weixin
步骤3:测试不同用户
测试陪伴员A
- 使用陪伴员A的账号登录
- 进入"消息通知"页面
- 查看消息列表
- 记录消息数量和内容
测试陪伴员B
- 使用陪伴员B的账号登录
- 进入"消息通知"页面
- 查看消息列表
- 记录消息数量和内容
测试管理师
- 使用管理师账号登录
- 进入"消息通知"页面
- 查看消息列表
- 记录消息数量和内容
步骤4:验证结果
预期结果:
- ✅ 不同用户看到的消息列表不同
- ✅ 每个用户只看到发送给自己的消息
- ✅ 未读数量显示正确
- ✅ 消息内容显示正确
如果出现"未登录"错误:
- 检查前端是否正确发送了token
- 检查token是否过期
- 重新登录获取新token
🔍 数据验证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()方法,需要检查是否有相同的问题:
CheckInRecordController- 签到记录ManagerApplicationController- 管理师申请StudentController- 学生管理TeacherController- 陪伴员管理WithdrawController- 提现管理CalendarController- 日历管理
检查要点:
- 这些接口是否被排除在JWT拦截器之外?
- getCurrentUserId()是否返回默认值?
- 是否存在类似的权限问题?
🎯 安全建议
1. 统一的用户身份获取方式
建议创建一个基础控制器类:
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:
log.info("用户{}查询消息列表,页码:{},类型:{}", userId, page, type);
4. 添加单元测试
测试不同用户只能看到自己的消息:
@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
严重程度: 🚨 高危(安全漏洞)
测试状态: ⚠️ 需要重启后端服务并测试