4. 权限中间件实现:请求拦截、权限校验、无权限响应处理

中间件这个词,听起来挺唬人的。说白了,它就像一道安检门。

用户发起的每个请求,在到达真正的业务逻辑之前,都得先过我这道门。我检查你的身份,核对你的权限,没问题放行,有问题直接拦下。这就是中间件的核心价值——请求拦截。

核心思路: 将权限校验逻辑从业务代码中剥离,统一在中间件层处理。这样业务代码只关心「能不能做」,不关心「该不该做」。

4.1 请求拦截:在入口处设卡

我个人习惯,在路由注册阶段就把中间件挂上去。比如在 Express 或 Koa 框架里,我会这样写:

// 路由注册时挂载权限中间件
router.put('/api/recipe/:id', 
  authMiddleware,       // 第一步:身份认证
  permissionMiddleware, // 第二步:权限校验
  recipeController.update // 第三步:业务处理
);

你看,请求进来后,先过 authMiddleware 确认你是谁,再过 permissionMiddleware 确认你有没有权限,最后才到真正的更新逻辑。每一层各司其职,互不干扰。

我在项目中遇到过一种情况:有人把权限校验写在业务代码里,结果每个接口都要重复写一遍 if-else。后来改中间件方案,代码量直接砍掉 60%。

小技巧: 中间件的执行顺序很重要。身份认证一定要在权限校验之前,因为权限校验依赖用户身份信息。顺序搞反了,你连用户是谁都不知道,怎么判断他有没有权限?

4.2 权限校验:核心逻辑怎么设计

权限校验的逻辑,其实就三步:

  1. 提取用户身份:从请求头或 token 中解析出用户 ID 和角色。
  2. 获取资源归属:查出要修改的配方属于哪个用户。
  3. 比对权限规则:当前用户是否有权操作该资源。

我一般会把校验逻辑封装成一个函数,方便复用:

// 权限校验中间件核心逻辑
async function permissionMiddleware(req, res, next) {
  try {
    // 1. 从认证中间件获取用户信息
    const userId = req.user.id;
    const userRole = req.user.role;

    // 2. 获取目标资源(配方)的归属信息
    const recipeId = req.params.id;
    const recipe = await Recipe.findById(recipeId);
    
    if (!recipe) {
      return res.status(404).json({ message: '配方不存在' });
    }

    // 3. 权限规则判断
    const hasPermission = checkPermission(userRole, userId, recipe.ownerId);
    
    if (!hasPermission) {
      // 无权限处理
      return res.status(403).json({ 
        message: '您没有权限修改此配方',
        code: 'FORBIDDEN'
      });
    }

    // 4. 放行
    next();
  } catch (error) {
    next(error);
  }
}

这里有个关键点:checkPermission 函数。它封装了具体的权限规则。我建议把规则做成可配置的,而不是硬编码在代码里。

角色 操作自己配方 操作他人配方 操作所有配方
普通用户 ✅ 允许 ❌ 禁止 ❌ 禁止
高级用户 ✅ 允许 ✅ 允许(需审核) ❌ 禁止
管理员 ✅ 允许 ✅ 允许 ✅ 允许

注意: 我曾经踩过一个坑——在权限校验时直接查询数据库,导致每个请求都多了一次查询。后来我改用 Redis 缓存用户权限信息,性能提升很明显。权限信息变化不频繁,很适合缓存。

4.3 无权限响应处理:优雅地拒绝

用户没权限,你不能直接甩个 500 错误就完事了。响应要友好,信息要明确。

我一般会统一返回这样的结构:

{
  "success": false,
  "code": "FORBIDDEN",
  "message": "您没有权限修改此配方",
  "detail": "只有配方的创建者或管理员才能修改",
  "timestamp": 1698765432100
}

为什么这么设计?

  • code:给前端程序看的,方便做统一处理。
  • message:给用户看的,要通俗易懂。
  • detail:给开发者看的,方便调试。
  • timestamp:记录时间,方便排查问题。

嗯,这里要注意:不要暴露敏感信息。比如「你没有管理员权限」可以,但「你的角色是普通用户,需要升级到高级用户」这种话就别说了,容易被人利用。

4.4 整体流程:一张图看懂

说了这么多,咱们用一张图把整个流程串起来:

权限中间件完整请求流程 客户端请求 ① 身份认证中间件 ② 权限校验中间件 权限校验结果 ③ 业务处理(放行) ④ 无权限响应(拒绝) 关键点: • 中间件按顺序执行 • 任一中间件可中断流程 • 无权限直接返回,不执行业务 • 响应信息要友好且安全 响应返回客户端

4.5 避坑指南:我踩过的几个坑

做中间件开发,有几个地方特别容易出问题。我把自己踩过的坑分享给你:

  • 异步错误没捕获:中间件里的异步操作,一定要用 try-catch 包起来,否则错误会直接抛到 Express 的默认错误处理器里,返回 500 给用户。
  • 权限规则写死:我刚开始把权限规则硬编码在中间件里,后来需求一变,改得我头皮发麻。建议把规则抽成配置,甚至做成可动态加载的。
  • 忽略资源不存在的情况:用户请求修改一个不存在的配方,你返回「无权限」还是「资源不存在」?我建议先查资源是否存在,不存在就返回 404,存在再校验权限。
  • 性能问题:每个请求都查数据库,高并发下扛不住。用缓存,或者把权限信息放在 token 里,减少数据库查询。

我的习惯: 我会在中间件里加一个简单的日志,记录每次权限校验的结果。这样出了问题,翻日志就能定位是哪个环节的问题。日志格式:[权限校验] 用户ID:xxx 操作:修改配方 结果:通过/拒绝

4.6 小结

权限中间件,说白了就是一道安检门。请求进来,先过身份认证,再过权限校验,最后才到业务逻辑。没权限的,直接优雅地拒绝。

记住三个要点:拦截要早、校验要准、响应要友好。把这三点做好了,你的权限系统就稳了。