You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node+Express+JWT:实现受保护路由的多角色权限控制

问题描述

我想用用户角色保护特定路由(部分路由允许多个角色访问),但当前在路由处理程序中配置允许的角色时,仅首个列出的角色能获得访问权限,需要让所有列出的角色都可访问。

现有代码

认证中间件(auth.js)

import jwt from "jsonwebtoken";

const auth = (req, res, next) => {
  const token = req.header("x-auth-token");
  if (!token)
    return res
      .status(401)
      .send({ message: "Access denied. No token provided!" });

  try {
    const decoded = jwt.verify(token, "SomethingPrivate");
    req.user = decoded;
    next();
  } catch (ex) {
    res.status(400).send({ message: "Invalid Token." });
  }
};

export const superAdmin = (req, res, next) => {
  const token = req.header("x-auth-token");
  if (!auth && !token)
    return res.status(401).send({ message: "Access denied." });

  const decoded = jwt.verify(token, "SomethingPrivate");
  if (auth && decoded.role === "superAdmin") {
    res.status(200);
    next();
  } else {
    res.status(400).send({ message: "Access denied!" });
  }
};

export const admin = (req, res, next) => {
  const token = req.header("x-auth-token");
  if (!auth && !token)
    return res.status(401).send({ message: "Access denied." });

  const decoded = jwt.verify(token, "SomethingPrivate");
  if (auth && decoded.role === "admin") {
    res.status(200);
    next();
  } else {
    res.status(400).send({ message: "Access denied!" });
  }
};

export const teacher = (req, res, next) => {
  const token = req.header("x-auth-token");
  if (!auth || !token)
    return res.status(401).send({ message: "Access denied." });

  const decoded = jwt.verify(token, "SomethingPrivate");
  if (auth && decoded.role === "teacher") {
    res.status(200);
    next();
  } else {
    res.status(400).send({ message: "Access denied!" });
  }
};

export const student = (req, res, next) => {
  const token = req.header("x-auth-token");
  if (!auth || !token)
    return res.status(401).send({ message: "Access denied." });

  const decoded = jwt.verify(token, "SomethingPrivate");
  if (auth && decoded.role === "student") {
    res.status(200);
    next();
  } else {
    res.status(400).send({ message: "Access denied!" });
  }
};

export const parent = (req, res, next) => {
  const token = req.header("x-auth-token");
  if (!auth) return res.status(401).send({ message: "Access denied." });

  const decoded = jwt.verify(token, "SomethingPrivate");
  if (auth && decoded.role === "parent") {
    res.status(200);
    next();
  } else {
    res.status(400).send({ message: "Access denied!" });
  }
};

路由文件(userRoute.js)

import express from "express";
import {
  superAdmin,
  admin,
  teacher,
  student,
  parent,
} from "../middleware/auth.js";
const router = express.Router();
import {
  view,
  find,
  me,
  create,
  edit,
  update,
  viewUser,
} from "../controllers/userController.js";

// Routes
router.get("/", [superAdmin, admin], view); // 问题路由
router.post("/", find);
router.get("/me", me);
router.post("/create", superAdmin, create);
router.get("/edituser/:userID", edit);
router.post("/edituser/:userID", [], update);
router.get("/viewuser/:userID", viewUser);


export { router as user };

JWT负载数据

const token = jwt.sign(
              {
                userID: result[0].userID,
                firstName: result[0].firstName,
                lastName: result[0].lastName,
                email: result[0].email,
                role: result[0].role,
              },

问题原因

当前写法的核心问题:

  • 每个角色中间件重复执行JWT验证,冗余且易出错
  • 当路由使用[superAdmin, admin]这类中间件数组时,第一个中间件会先执行,若用户角色不是superAdmin,该中间件直接返回400拒绝访问,后续admin中间件无执行机会,导致只有第一个角色能访问

解决方案

重构认证中间件,分离JWT验证与角色授权,实现通用角色检查中间件:

修改后的auth.js

import jwt from "jsonwebtoken";

// 通用JWT验证中间件:仅负责验证token有效性并解码用户信息
export const auth = (req, res, next) => {
  const token = req.header("x-auth-token");
  if (!token)
    return res.status(401).send({ message: "Access denied. No token provided!" });

  try {
    const decoded = jwt.verify(token, "SomethingPrivate");
    req.user = decoded; // 将解码后的用户信息挂载到req对象
    next();
  } catch (ex) {
    res.status(400).send({ message: "Invalid Token." });
  }
};

// 通用角色授权中间件:接收允许的角色数组,检查用户角色是否符合要求
export const authorizeRoles = (...allowedRoles) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).send({ message: "Access denied. No authenticated user found!" });
    }
    if (allowedRoles.includes(req.user.role)) {
      next();
    } else {
      res.status(403).send({ message: "Access denied. Insufficient permissions!" });
    }
  };
};

修改后的userRoute.js

import express from "express";
import { auth, authorizeRoles } from "../middleware/auth.js"; // 导入重构后的中间件
const router = express.Router();
import {
  view,
  find,
  me,
  create,
  edit,
  update,
  viewUser,
} from "../controllers/userController.js";

// Routes
// 先执行auth验证token,再用authorizeRoles指定允许的角色
router.get("/", auth, authorizeRoles("superAdmin", "admin"), view);
router.post("/", find);
router.get("/me", auth, me); // 个人信息路由添加auth验证
router.post("/create", auth, authorizeRoles("superAdmin"), create);
router.get("/edituser/:userID", edit);
router.post("/edituser/:userID", auth, update); // 按需添加角色验证
router.get("/viewuser/:userID", auth, viewUser); // 按需添加角色验证

export { router as user };

说明

  • auth中间件统一处理JWT验证,解码后将用户信息存入req.user
  • authorizeRoles中间件支持传入任意数量的角色参数,检查当前用户角色是否在允许列表中
  • 路由只需按顺序使用auth + authorizeRoles(角色1, 角色2...),即可实现多角色访问控制
  • 该写法简洁易维护,新增角色无需编写新中间件,只需在使用时添加角色参数

内容的提问来源于stack exchange,提问作者cameronErasmus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:06:26