Node.js(Express)中间件做权限判断:实操说明
Node.js(Express)中间件做权限判断:实操说明
在 Express 里,中间件是按顺序执行的函数,形如 (req, res, next) => { ... }。做登录态校验、角色/权限判断时,通常把逻辑抽成可复用的中间件,挂在全局或某条路由上,避免在每个处理函数里重复写 if。
下面约定:已登录用 Authorization: Bearer <token> 传递 JWT(示例用 jsonwebtoken;实际项目密钥与过期策略按安全规范配置)。
1. 先分清:401 与 403
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 401 Unauthorized | 未认证(没带 Token、过期、签名无效) | 需要登录才能访问 |
| 403 Forbidden | 已认证但无权限 | 普通用户访问管理员接口 |
中间件里按情况 res.status(401) 或 res.status(403),并 return,不要再调用 next(),除非你要交给后续错误处理中间件统一格式化。
2. 安装依赖
npm install express jsonwebtoken
3. 签发 Token(登录接口里用,便于本地联调)
const jwt = require("jsonwebtoken");
const JWT_SECRET = process.env.JWT_SECRET || "dev-only-change-me";
const JWT_EXPIRES = "8h";
function signToken(payload) {
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES });
}
// 登录成功后:res.json({ token: signToken({ sub: user.id, role: user.role }) })
生产环境 JWT_SECRET 必须用长随机串且只放在环境变量里。
4. 中间件一:校验登录(解析 JWT)
src/middleware/auth.js
const jwt = require("jsonwebtoken");
const JWT_SECRET = process.env.JWT_SECRET || "dev-only-change-me";
/**
* 要求请求必须带合法 Bearer Token,并把用户信息挂到 req.user
*/
function requireAuth(req, res, next) {
const header = req.headers.authorization || "";
const [, token] = header.split(" ");
if (!token) {
return res.status(401).json({ code: "NO_TOKEN", message: "未提供凭证" });
}
try {
const payload = jwt.verify(token, JWT_SECRET);
req.user = {
id: payload.sub,
role: payload.role,
};
next();
} catch {
return res.status(401).json({ code: "INVALID_TOKEN", message: "凭证无效或已过期" });
}
}
module.exports = { requireAuth };
5. 中间件二:按角色限制(权限)
在 req.user 已存在的前提下(即 requireAuth 之后),再检查 role:
src/middleware/authorize.js
/**
* @param {...string} allowedRoles 允许的角色,如 'admin', 'editor'
*/
function requireRole(...allowedRoles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ code: "NO_USER", message: "未认证" });
}
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ code: "FORBIDDEN", message: "无权限执行此操作" });
}
next();
};
}
module.exports = { requireRole };
6. 挂载到路由:顺序很重要
全局:先 express.json(),再按需 requireAuth。
单条路由:从左到右 先认证、再鉴权、最后业务处理。
src/routes/posts.js
const { Router } = require("express");
const { requireAuth } = require("../middleware/auth");
const { requireRole } = require("../middleware/authorize");
const router = Router();
// 公开
router.get("/public", (req, res) => res.json({ list: [] }));
// 必须登录
router.get("/mine", requireAuth, (req, res) => {
res.json({ userId: req.user.id });
});
// 仅管理员
router.delete("/:id", requireAuth, requireRole("admin"), (req, res) => {
res.json({ deleted: req.params.id });
});
module.exports = router;
src/index.js(片段)
const express = require("express");
const postsRouter = require("./routes/posts");
const app = express();
app.use(express.json());
// 某一整组 API 都要登录时,也可写在 Router 上:
// const api = Router();
// api.use(requireAuth);
// api.use("/posts", postsRouter);
// app.use("/api", api);
app.use("/posts", postsRouter);
7. 可选:登录可有可无的「弱中间件」
部分接口未登录也能访问,登录后返回更多字段时,可用 try/catch 解析 Token,失败则 req.user = null,不直接 401:
function optionalAuth(req, res, next) {
const header = req.headers.authorization || "";
const [, token] = header.split(" ");
req.user = null;
if (!token) return next();
try {
const jwt = require("jsonwebtoken");
const payload = jwt.verify(token, process.env.JWT_SECRET || "dev-only-change-me");
req.user = { id: payload.sub, role: payload.role };
} catch {
/* 忽略无效 token,当作未登录 */
}
next();
}
8. 统一错误与注意事项
- 密钥:
JWT_SECRET与签发端一致;多实例部署时不要每台机器不同。 - HTTPS:生产环境对外只走 HTTPS,避免 Token 被窃听。
- 敏感操作:除角色外,还可校验资源归属(例如只能删自己的文章:
if (post.userId !== req.user.id)),这类逻辑放在具体路由处理函数或下一层中间件里。 - 集中导出:可把
requireAuth、requireRole、optionalAuth放在同一目录,便于测试与替换为 Session、OAuth 等实现。
更完整的 Web API 搭建、数据库与 Docker 部署可参阅同目录下的 Node.js 入门:简介、API、数据库、跨域与部署。
