358 lines
11 KiB
Markdown
358 lines
11 KiB
Markdown
|
|
# 🔐 问卷权限问题分析报告
|
|||
|
|
|
|||
|
|
## 📋 问题描述
|
|||
|
|
|
|||
|
|
**用户反馈**:登录系统后能看到所有问卷,明明没有给用户分配问卷权限。
|
|||
|
|
|
|||
|
|
**影响范围**:所有普通用户都能看到系统中的所有问卷,存在严重的权限控制漏洞。
|
|||
|
|
|
|||
|
|
## 🔍 问题根源
|
|||
|
|
|
|||
|
|
### 1. 问卷列表接口缺少权限控制
|
|||
|
|
|
|||
|
|
**文件**:`ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQuestionnaireController.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 获取问卷列表(答题用户可访问)
|
|||
|
|
*/
|
|||
|
|
@GetMapping("/list")
|
|||
|
|
public TableDataInfo list(PsyQuestionnaire questionnaire)
|
|||
|
|
{
|
|||
|
|
startPage();
|
|||
|
|
List<PsyQuestionnaire> list = questionnaireService.selectQuestionnaireList(questionnaire);
|
|||
|
|
return getDataTable(list);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**问题点**:
|
|||
|
|
- ❌ **没有 `@PreAuthorize` 权限注解**
|
|||
|
|
- ❌ **没有权限过滤逻辑**
|
|||
|
|
- ❌ **直接返回所有问卷数据**
|
|||
|
|
- ❌ **任何登录用户都可以访问**
|
|||
|
|
|
|||
|
|
### 2. 对比:量表接口有完整的权限控制
|
|||
|
|
|
|||
|
|
**文件**:`ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScaleController.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 获取量表列表(包含问卷)
|
|||
|
|
* 允许管理员和学员访问
|
|||
|
|
*/
|
|||
|
|
@PreAuthorize("@ss.hasPermi('psychology:scale:list') or @ss.hasAnyRoles('student')")
|
|||
|
|
@GetMapping("/list")
|
|||
|
|
public TableDataInfo list(PsyScale scale, @RequestParam(required = false, defaultValue = "true") Boolean includeQuestionnaire)
|
|||
|
|
{
|
|||
|
|
Set<Long> allowedScaleIds = resolveAllowedScaleIdsForCurrentUser();
|
|||
|
|
Set<Long> restrictedScaleIds = resolveRestrictedScaleIds();
|
|||
|
|
boolean needPermissionFilter = allowedScaleIds != null;
|
|||
|
|
|
|||
|
|
// ... 权限过滤逻辑 ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- ✅ 有 `@PreAuthorize` 权限注解
|
|||
|
|
- ✅ 有完整的权限过滤逻辑
|
|||
|
|
- ✅ 根据用户权限返回数据
|
|||
|
|
- ✅ 支持管理员和普通用户的不同权限
|
|||
|
|
|
|||
|
|
### 3. 权限过滤逻辑说明
|
|||
|
|
|
|||
|
|
在 `PsyScaleController` 中,问卷的处理逻辑是:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
if ("questionnaire".equalsIgnoreCase(sourceType))
|
|||
|
|
{
|
|||
|
|
boolean restricted = restrictedScaleIds != null && restrictedScaleIds.contains(scaleId);
|
|||
|
|
if (!restricted)
|
|||
|
|
{
|
|||
|
|
filtered.add(scale); // 只要不在限制列表中,就添加
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**这段代码的含义**:
|
|||
|
|
- **问卷采用黑名单机制**:默认所有人可见,除非该问卷配置了权限
|
|||
|
|
- **量表采用白名单机制**:只有分配了权限的用户才能看到
|
|||
|
|
|
|||
|
|
**问题**:
|
|||
|
|
- 即使量表接口有权限过滤,但前端可以直接调用问卷接口 `/psychology/questionnaire/list`,完全绕过权限控制
|
|||
|
|
|
|||
|
|
## 📊 权限控制对比
|
|||
|
|
|
|||
|
|
| 项目 | 问卷接口 | 量表接口 |
|
|||
|
|
|------|---------|---------|
|
|||
|
|
| **接口路径** | `/psychology/questionnaire/list` | `/psychology/scale/list` |
|
|||
|
|
| **权限注解** | ❌ 无 | ✅ `@PreAuthorize` |
|
|||
|
|
| **权限过滤** | ❌ 无 | ✅ 完整逻辑 |
|
|||
|
|
| **访问控制** | ❌ 所有人可见 | ✅ 根据权限过滤 |
|
|||
|
|
| **安全性** | ❌ 严重漏洞 | ✅ 安全 |
|
|||
|
|
|
|||
|
|
## 🔧 修复方案
|
|||
|
|
|
|||
|
|
### 方案一:为问卷接口添加权限控制(推荐)
|
|||
|
|
|
|||
|
|
修改 `PsyQuestionnaireController.java`,添加完整的权限控制逻辑。
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- ✅ 问卷和量表使用统一的权限机制
|
|||
|
|
- ✅ 安全性高,符合最小权限原则
|
|||
|
|
- ✅ 可以精确控制每个用户看到的问卷
|
|||
|
|
|
|||
|
|
**实现步骤**:
|
|||
|
|
1. 在 `PsyQuestionnaireController` 中注入 `IPsyScalePermissionService`
|
|||
|
|
2. 添加 `@PreAuthorize` 权限注解
|
|||
|
|
3. 实现权限过滤逻辑(参考 `PsyScaleController`)
|
|||
|
|
4. 修改 `/list` 接口,根据用户权限返回问卷列表
|
|||
|
|
|
|||
|
|
### 方案二:问卷永久对所有人开放(不推荐)
|
|||
|
|
|
|||
|
|
如果业务上问卷确实需要对所有人开放(不需要权限控制),需要:
|
|||
|
|
|
|||
|
|
1. 明确文档说明:问卷对所有登录用户开放
|
|||
|
|
2. 确保量表接口的权限过滤逻辑不影响问卷显示
|
|||
|
|
3. 前端统一使用量表接口(`/psychology/scale/list?includeQuestionnaire=true`)
|
|||
|
|
|
|||
|
|
**问题**:
|
|||
|
|
- ⚠️ 无法精确控制用户可见的问卷
|
|||
|
|
- ⚠️ 可能暴露敏感内容
|
|||
|
|
|
|||
|
|
## ✅ 推荐修复代码
|
|||
|
|
|
|||
|
|
### 修改 `PsyQuestionnaireController.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.ddnai.web.controller.psychology;
|
|||
|
|
|
|||
|
|
import java.util.ArrayList;
|
|||
|
|
import java.util.HashSet;
|
|||
|
|
import java.util.List;
|
|||
|
|
import java.util.Set;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.security.access.prepost.PreAuthorize;
|
|||
|
|
import org.springframework.validation.annotation.Validated;
|
|||
|
|
import org.springframework.web.bind.annotation.*;
|
|||
|
|
import com.ddnai.common.annotation.Log;
|
|||
|
|
import com.ddnai.common.core.controller.BaseController;
|
|||
|
|
import com.ddnai.common.core.domain.AjaxResult;
|
|||
|
|
import com.ddnai.common.core.page.TableDataInfo;
|
|||
|
|
import com.ddnai.common.enums.BusinessType;
|
|||
|
|
import com.ddnai.common.utils.SecurityUtils;
|
|||
|
|
import com.ddnai.system.domain.psychology.PsyQuestionnaire;
|
|||
|
|
import com.ddnai.system.service.psychology.IPsyQuestionnaireService;
|
|||
|
|
import com.ddnai.system.service.psychology.IPsyScalePermissionService;
|
|||
|
|
|
|||
|
|
@RestController
|
|||
|
|
@RequestMapping("/psychology/questionnaire")
|
|||
|
|
public class PsyQuestionnaireController extends BaseController
|
|||
|
|
{
|
|||
|
|
@Autowired
|
|||
|
|
private IPsyQuestionnaireService questionnaireService;
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private IPsyScalePermissionService scalePermissionService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取问卷列表(带权限控制)
|
|||
|
|
*/
|
|||
|
|
@PreAuthorize("@ss.hasPermi('psychology:questionnaire:list') or @ss.hasAnyRoles('student')")
|
|||
|
|
@GetMapping("/list")
|
|||
|
|
public TableDataInfo list(PsyQuestionnaire questionnaire)
|
|||
|
|
{
|
|||
|
|
// 获取当前用户权限
|
|||
|
|
Set<Long> allowedScaleIds = resolveAllowedScaleIdsForCurrentUser();
|
|||
|
|
Set<Long> restrictedScaleIds = resolveRestrictedScaleIds();
|
|||
|
|
|
|||
|
|
// 查询所有问卷
|
|||
|
|
startPage();
|
|||
|
|
List<PsyQuestionnaire> list = questionnaireService.selectQuestionnaireList(questionnaire);
|
|||
|
|
|
|||
|
|
// 权限过滤
|
|||
|
|
list = filterQuestionnaireListByPermission(list, allowedScaleIds, restrictedScaleIds);
|
|||
|
|
|
|||
|
|
return getDataTable(list);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据用户权限过滤问卷列表
|
|||
|
|
*/
|
|||
|
|
private List<PsyQuestionnaire> filterQuestionnaireListByPermission(
|
|||
|
|
List<PsyQuestionnaire> list,
|
|||
|
|
Set<Long> allowedScaleIds,
|
|||
|
|
Set<Long> restrictedScaleIds)
|
|||
|
|
{
|
|||
|
|
// 管理员或无需权限过滤
|
|||
|
|
if (allowedScaleIds == null || list == null)
|
|||
|
|
{
|
|||
|
|
return list;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
List<PsyQuestionnaire> filtered = new ArrayList<>();
|
|||
|
|
for (PsyQuestionnaire questionnaire : list)
|
|||
|
|
{
|
|||
|
|
if (questionnaire == null)
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用负数ID标识问卷(与量表接口保持一致)
|
|||
|
|
Long scaleId = -questionnaire.getQuestionnaireId();
|
|||
|
|
|
|||
|
|
// 如果该问卷未配置权限,对所有人开放
|
|||
|
|
boolean restricted = restrictedScaleIds != null && restrictedScaleIds.contains(scaleId);
|
|||
|
|
if (!restricted)
|
|||
|
|
{
|
|||
|
|
filtered.add(questionnaire);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果配置了权限,检查用户是否有权限
|
|||
|
|
if (allowedScaleIds.contains(scaleId))
|
|||
|
|
{
|
|||
|
|
filtered.add(questionnaire);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return filtered;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析当前用户可访问的量表/问卷ID集合
|
|||
|
|
* @return null 表示无需权限过滤(管理员);非null 表示必须过滤
|
|||
|
|
*/
|
|||
|
|
private Set<Long> resolveAllowedScaleIdsForCurrentUser()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
Long currentUserId = SecurityUtils.getUserId();
|
|||
|
|
|
|||
|
|
// 管理员拥有所有权限
|
|||
|
|
if (currentUserId == null || currentUserId.equals(1L))
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否有管理权限
|
|||
|
|
boolean hasManagePerm = false;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
hasManagePerm = SecurityUtils.hasPermi("psychology:questionnaire:list");
|
|||
|
|
}
|
|||
|
|
catch (Exception ignore)
|
|||
|
|
{
|
|||
|
|
// 忽略权限判断异常
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (hasManagePerm)
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取用户有权限访问的量表/问卷ID
|
|||
|
|
List<Long> scaleIds = scalePermissionService.selectScaleIdsByUserId(currentUserId);
|
|||
|
|
return new HashSet<>(scaleIds != null ? scaleIds : new ArrayList<>());
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
logger.warn("获取用户问卷权限失败: {}", e.getMessage());
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有已配置权限的量表/问卷ID
|
|||
|
|
*/
|
|||
|
|
private Set<Long> resolveRestrictedScaleIds()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
List<Long> ids = scalePermissionService.selectAllScaleIdsWithPermission();
|
|||
|
|
if (ids == null || ids.isEmpty())
|
|||
|
|
{
|
|||
|
|
return java.util.Collections.emptySet();
|
|||
|
|
}
|
|||
|
|
return new HashSet<>(ids);
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
logger.warn("解析问卷权限限制列表失败: {}", e.getMessage());
|
|||
|
|
return java.util.Collections.emptySet();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ... 其他方法保持不变 ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🧪 测试验证
|
|||
|
|
|
|||
|
|
### 测试场景 1:普通用户(未分配权限)
|
|||
|
|
|
|||
|
|
**预期**:
|
|||
|
|
- ✅ 只能看到未配置权限的问卷(公开问卷)
|
|||
|
|
- ❌ 不能看到已配置权限但未分配给该用户的问卷
|
|||
|
|
|
|||
|
|
### 测试场景 2:普通用户(已分配权限)
|
|||
|
|
|
|||
|
|
**预期**:
|
|||
|
|
- ✅ 能看到未配置权限的问卷
|
|||
|
|
- ✅ 能看到已分配权限的问卷
|
|||
|
|
- ❌ 不能看到未分配权限的其他问卷
|
|||
|
|
|
|||
|
|
### 测试场景 3:管理员
|
|||
|
|
|
|||
|
|
**预期**:
|
|||
|
|
- ✅ 能看到所有问卷(不受权限限制)
|
|||
|
|
|
|||
|
|
### 测试步骤
|
|||
|
|
|
|||
|
|
1. **准备数据**:
|
|||
|
|
- 创建3个问卷:A(未配置权限)、B(已配置权限)、C(已配置权限)
|
|||
|
|
- 创建普通用户 user1,只分配问卷B的权限
|
|||
|
|
|
|||
|
|
2. **测试 user1 登录**:
|
|||
|
|
```bash
|
|||
|
|
# 预期结果:只能看到问卷A和B,不能看到C
|
|||
|
|
GET /psychology/questionnaire/list
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **测试管理员登录**:
|
|||
|
|
```bash
|
|||
|
|
# 预期结果:能看到所有问卷A、B、C
|
|||
|
|
GET /psychology/questionnaire/list
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📝 修改文件清单
|
|||
|
|
|
|||
|
|
- ✅ `ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQuestionnaireController.java`
|
|||
|
|
|
|||
|
|
## ⚠️ 注意事项
|
|||
|
|
|
|||
|
|
1. **权限分配兼容性**:
|
|||
|
|
- 问卷使用负数ID(-questionnaireId)存储在权限表中
|
|||
|
|
- 与量表接口保持一致
|
|||
|
|
|
|||
|
|
2. **默认行为**:
|
|||
|
|
- 未配置权限的问卷对所有人开放(向后兼容)
|
|||
|
|
- 已配置权限的问卷只对有权限的用户开放
|
|||
|
|
|
|||
|
|
3. **前端调用**:
|
|||
|
|
- 建议统一使用量表接口:`/psychology/scale/list?includeQuestionnaire=true`
|
|||
|
|
- 或使用修复后的问卷接口:`/psychology/questionnaire/list`
|
|||
|
|
|
|||
|
|
## 🎯 修复后的效果
|
|||
|
|
|
|||
|
|
| 用户类型 | 修复前 | 修复后 |
|
|||
|
|
|---------|-------|-------|
|
|||
|
|
| **管理员** | 看到所有问卷 | 看到所有问卷 ✅ |
|
|||
|
|
| **普通用户(无权限)** | 看到所有问卷 ❌ | 只看到公开问卷 ✅ |
|
|||
|
|
| **普通用户(有权限)** | 看到所有问卷 ❌ | 看到公开+授权问卷 ✅ |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**问题发现时间**:2024年12月2日
|
|||
|
|
**严重程度**:⚠️ 高危(权限控制漏洞)
|
|||
|
|
**修复优先级**:🔥 紧急
|