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.userauthorizeRoles中间件支持传入任意数量的角色参数,检查当前用户角色是否在允许列表中- 路由只需按顺序使用
auth + authorizeRoles(角色1, 角色2...),即可实现多角色访问控制 - 该写法简洁易维护,新增角色无需编写新中间件,只需在使用时添加角色参数
内容的提问来源于stack exchange,提问作者cameronErasmus
相关产品推荐
相关产品推荐

