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)),这类逻辑放在具体路由处理函数下一层中间件里。
  • 集中导出:可把 requireAuthrequireRoleoptionalAuth 放在同一目录,便于测试与替换为 Session、OAuth 等实现。

更完整的 Web API 搭建、数据库与 Docker 部署可参阅同目录下的 Node.js 入门:简介、API、数据库、跨域与部署

Last Updated 4/9/2026, 6:16:02 AM