Node.js中如何实现用户角色与权限的访问控制功能
Node.js+MongoDB 实现多角色权限控制方案
你要实现的是标准RBAC(基于角色的访问控制)模型,不需要依赖复杂的第三方权限包,按下面步骤实现即可,和你现有JWT鉴权逻辑完全兼容。
1. 调整数据模型
你现有User模型缺少角色关联字段,需要新增Role集合存储角色和对应权限,再给User加角色关联字段。
新建Role模型
新建models/Role.js,每个角色对应一组操作权限:
const mongoose = require('mongoose'); const roleSchema = new mongoose.Schema({ name: { type: String, required: true, unique: true }, // 权限用统一格式的字符串标识即可,比如 bus:create、order:delete permissions: [{ type: String, required: true }], description: String }, { timestamps: true }); module.exports = mongoose.model('Role', roleSchema);
修改现有User Schema
在你原有userSchema的tokens字段下方,新增角色关联字段:
// 关联用户绑定的角色,存储Role文档的ObjectId roles: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Role' }]
初始化默认角色
服务启动、数据库连接成功后执行一次初始化,自动创建你需要的三个默认角色,避免手动添加:
const Role = require('./models/Role'); const initSystemRoles = async () => { const roleCount = await Role.estimatedDocumentCount(); if (roleCount > 0) return; // 普通用户 await Role.create({ name: 'user', permissions: ['bus:list', 'order:create', 'order:own:list'], description: '普通注册用户' }); // 主管 await Role.create({ name: 'supervisor', permissions: ['bus:list', 'bus:update', 'order:list', 'order:process'], description: '运营主管' }); // 管理员,* 代表拥有所有权限,不用单独枚举 await Role.create({ name: 'admin', permissions: ['*'], description: '系统管理员' }); console.log('系统默认角色初始化完成'); }
新用户注册时默认绑定普通用户角色,在注册逻辑里加一行即可:
const defaultRole = await Role.findOne({ name: 'user' }); const user = new User({ // 原有firstname、lastname、email等字段 roles: [defaultRole._id] })
2. 编写权限校验中间件
你现有JWT鉴权逻辑只需要做少量改造,再加一个权限判断中间件即可。
在你存放鉴权逻辑的文件(比如middlewares/auth.js)里写以下代码:
const jwt = require('jsonwebtoken'); const User = require('../models/User'); // 1. 登录鉴权中间件:校验JWT,同时聚合用户的所有权限 const auth = async (req, res, next) => { try { // 保留你原有JWT提取、校验逻辑 const token = req.header('Authorization').replace('Bearer ', ''); const decoded = jwt.verify(token, process.env.JWT_SECRET); // 查询用户时通过populate把关联的角色信息一起查出来 const user = await User.findOne({ _id: decoded._id, 'tokens.token': token }).populate('roles'); if (!user) throw new Error('用户不存在'); // 合并所有角色的权限,去重 const permissionSet = new Set(); user.roles.forEach(role => { role.permissions.forEach(perm => permissionSet.add(perm)); }); // 把用户信息、权限列表挂到req对象上,供后续接口使用 req.user = user; req.token = token; req.permissions = Array.from(permissionSet); next(); } catch (e) { res.status(401).send({ error: '请先登录' }); } } // 2. 权限校验中间件:调用接口时传入需要的权限标识,判断用户是否有权限 const requirePermission = (requiredPerm) => { return (req, res, next) => { // 管理员通配权限直接放行 if (req.permissions.includes('*')) return next(); if (!req.permissions.includes(requiredPerm)) { return res.status(403).send({ error: '无操作权限' }); } next(); } } module.exports = { auth, requirePermission };
3. 给路由绑定权限
在路由定义处,按接口需要的权限加中间件即可,示例:
const express = require('express'); const router = express.Router(); const { auth, requirePermission } = require('../middlewares/auth'); // 所有登录用户都可查看班次列表 router.get('/buses', auth, requirePermission('bus:list'), async (req, res) => { // 原有班次查询逻辑 }); // 仅主管/管理员可更新班次信息 router.patch('/buses/:id', auth, requirePermission('bus:update'), async (req, res) => { // 原有班次更新逻辑 }); // 仅管理员可给用户分配角色 router.post('/users/:id/roles', auth, requirePermission('user:role:edit'), async (req, res) => { const { roleIds } = req.body; await User.findByIdAndUpdate(req.params.id, { roles: roleIds }); res.send({ message: '角色分配成功' }); });
4. 调试注意事项
- 用Postman调试时,先通过管理员账号登录拿到token,给测试账号分配不同角色后,再用对应账号的token调用接口,验证403/200返回是否符合预期
- 权限标识建议统一用
资源名:操作名的格式命名,避免混乱,后续加新权限直接在对应角色的permissions数组里加字符串即可 - 不要把角色、权限信息存在JWT payload里,否则修改用户角色后旧token的权限不会更新,每次请求实时查库聚合权限,在你当前业务量级下完全没有性能问题
- 如果需要给用户多角色,直接在用户的roles数组里加多个Role的ObjectId即可,权限会自动合并
内容的提问来源于stack exchange,提问作者user19180062
相关产品推荐
相关产品推荐

