角色权限系统:实现动态项目编辑权限校验中间件方案
动态权限授权中间件实现方案
针对你当前的角色权限系统需求(Super Admin全量操作、Site Admin仅能操作指派项目),要实现无硬编码的动态授权,核心是把权限存在性校验和资源范围校验解耦,通过可配置的规则集合来处理不同权限的专属校验逻辑。以下是具体实现步骤和代码:
核心思路
- 定义权限规则接口:为每个需要资源校验的权限单独配置校验函数,替代硬编码的角色判断
- 异步支持:资源校验通常需要查询数据库(比如检查用户是否关联目标项目),中间件需改为异步处理
- 规则与中间件分离:后续新增/修改权限逻辑,只需调整规则配置,无需改动中间件核心代码
实现代码
1. 定义权限规则类型
import { Request } from 'express'; // 权限校验规则类型:每个权限对应一个资源校验函数 type PermissionRule = { name: string; // 校验函数:接收请求和用户信息,返回是否有权限操作该资源 validateResource: (req: Request, user: any) => Promise<boolean>; };
2. 配置权限校验规则
把不同权限的校验逻辑单独维护,方便扩展:
// 示例数据库模型,根据你的实际模型调整 import { ProjectUser, Task } from '../models'; const permissionRules: PermissionRule[] = [ { name: 'EDIT_PROJECT', validateResource: async (req, user) => { // Super Admin直接拥有所有项目的编辑权限 if (user.role.name === 'Super Admin') return true; // Site Admin仅能编辑自己被指派的项目 if (user.role.name === 'Site Admin') { const projectId = req.params.projectId; // 从路由参数获取目标项目ID // 查询用户是否是该项目的Site Admin const userProject = await ProjectUser.findOne({ where: { userId: user.id, projectId, role: 'Site Admin' // 假设项目-用户关联表存储了项目内的角色 } }); return !!userProject; } // 其他角色默认无权限 return false; } }, { name: 'EDIT_TASK', validateResource: async (req, user) => { if (user.role.name === 'Super Admin') return true; if (user.role.name === 'Site Admin') { const taskId = req.params.taskId; // 先查询任务所属项目,再校验用户是否是该项目的Site Admin const task = await Task.findByPk(taskId, { include: ['project'] }); if (!task) return false; const userProject = await ProjectUser.findOne({ where: { userId: user.id, projectId: task.project.id, role: 'Site Admin' } }); return !!userProject; } return false; } } ];
3. 升级后的权限校验中间件
import { Request, Response, NextFunction } from 'express'; import RequestResponseMappings from './RequestResponseMappings'; // 假设你的响应工具类路径 const authorizeRoles = (requiredPermissions: string[]) => { return async (req: Request, res: Response, next: NextFunction) => { try { // 基础校验:用户及角色权限是否存在 if (!req.user || !req.user.role || !req.user.role.permissions) { return RequestResponseMappings.sendErrorMessage(res, {}, "未授权访问", 401); } const userPermissionNames = req.user.role.permissions.map(p => p.name); // 先判断用户是否拥有至少一个所需权限 const hasAnyPermission = requiredPermissions.some(perm => userPermissionNames.includes(perm)); if (!hasAnyPermission) { return RequestResponseMappings.sendErrorMessage(res, {}, "禁止访问:权限不足", 403); } // 对每个用户拥有的所需权限,执行资源校验 for (const perm of requiredPermissions) { if (!userPermissionNames.includes(perm)) continue; const rule = permissionRules.find(r => r.name === perm); if (rule) { const isAuthorized = await rule.validateResource(req, req.user); if (!isAuthorized) { return RequestResponseMappings.sendErrorMessage(res, {}, "禁止访问:无该资源操作权限", 403); } } // 无对应规则的权限(如全局权限)默认通过 } next(); } catch (error: any) { return RequestResponseMappings.sendErrorMessage(res, {}, error.message, 500); } }; }; export default authorizeRoles;
使用说明
- 当需要保护某个路由时,传入所需权限即可:
router.put('/projects/:projectId', authorizeRoles(['EDIT_PROJECT']), updateProject); router.put('/tasks/:taskId', authorizeRoles(['EDIT_TASK']), updateTask); - 后续新增权限(如
DELETE_PROJECT),只需在permissionRules中添加对应规则即可,无需修改中间件 - 资源ID的获取方式可根据实际路由调整(比如从
req.body或req.query获取)
内容的提问来源于stack exchange,提问作者Muhammad Mansha
相关产品推荐
相关产品推荐

