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

多租户Express应用授权层实现方案技术问询

多租户Express API授权层实现方案

针对你的多租户Express API授权需求,结合你给出的项目结构,我整理了一套可落地的实现方案,从核心思路到代码实现一步步拆解:

核心设计思路

我们基于**角色+租户(Company)**的双重校验逻辑来构建授权层:

  • superadmin:拥有最高权限,可跨公司执行所有操作,跳过租户校验
  • admin:仅能操作所属公司内的所有资源(用户、公司信息等)
  • user:仅能操作自身相关的个人资源

1. 构建授权中间件(对应services/authorisation.js)

这是授权层的核心,我们封装不同粒度的权限校验中间件,方便路由层调用:

// services/authorisation.js
const User = require('../models/User');

// 校验用户是否有权限访问目标公司(superadmin直接放行,其他角色校验所属公司)
exports.checkCompanyAccess = (req, res, next) => {
  const { companyId, userId } = req.params;
  const currentUser = req.user; // 由Passport认证后挂载到请求对象

  // superadmin直接跳过校验
  if (currentUser.role === 'superadmin') {
    return next();
  }

  // 校验当前用户是否属于目标公司
  if (currentUser.companyId.toString() !== companyId.toString()) {
    return res.status(403).json({ message: '无权限访问该公司资源' });
  }

  // 如果是操作特定用户,额外校验该用户是否属于当前公司(防止admin跨公司操作)
  if (userId) {
    User.findById(userId)
      .then(targetUser => {
        if (!targetUser || targetUser.companyId.toString() !== currentUser.companyId.toString()) {
          return res.status(403).json({ message: '无权限操作该用户' });
        }
        next();
      })
      .catch(err => res.status(500).json({ message: '权限校验失败', error: err }));
  } else {
    next();
  }
};

// 校验用户是否为superadmin
exports.checkSuperadmin = (req, res, next) => {
  if (req.user.role !== 'superadmin') {
    return res.status(403).json({ message: '仅超级管理员可执行此操作' });
  }
  next();
};

// 校验用户是否为admin或superadmin
exports.checkAdminOrSuperadmin = (req, res, next) => {
  if (!['admin', 'superadmin'].includes(req.user.role)) {
    return res.status(403).json({ message: '仅管理员或超级管理员可执行此操作' });
  }
  next();
};

// 校验用户是否为自身(用于普通用户修改个人信息场景)
exports.checkUserOwnership = (req, res, next) => {
  const { userId } = req.params;
  const currentUser = req.user;

  if (currentUser.role === 'superadmin' || currentUser._id.toString() === userId.toString()) {
    return next();
  }
  return res.status(403).json({ message: '无权限操作该用户资源' });
};

2. Passport集成(对应services/passport.js)

确保认证成功后,把包含role和companyId的完整用户信息挂载到req.user,为授权校验提供数据支撑:

// services/passport.js
const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/User');

const opts = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET, // 从环境变量读取密钥
};

passport.use(new JwtStrategy(opts, (jwtPayload, done) => {
  // 查询完整用户信息,包含role和companyId字段
  User.findById(jwtPayload.id)
    .then(user => {
      if (user) {
        return done(null, user); // user对象会被挂载到req.user
      }
      return done(null, false);
    })
    .catch(err => done(err, false));
}));

module.exports = passport;

3. 路由层集成授权中间件

在路由中组合认证和授权中间件,实现接口的权限控制:

公司路由(routers/companies.js)

const express = require('express');
const router = express.Router();
const companiesController = require('../controllers/companies');
const { checkCompanyAccess, checkSuperadmin, checkAdminOrSuperadmin } = require('../services/authorisation');
const passport = require('../services/passport');

// 所有公司接口需先通过JWT认证
router.use(passport.authenticate('jwt', { session: false }));

// superadmin可查看所有公司列表
router.get('/', checkSuperadmin, companiesController.getAllCompanies);

// 普通admin仅能查看自身公司信息
router.get('/:companyId', checkCompanyAccess, companiesController.getCompanyById);

// 仅superadmin可创建公司
router.post('/', checkSuperadmin, companiesController.createCompany);

// superadmin或目标公司admin可更新公司信息
router.put('/:companyId', checkAdminOrSuperadmin, checkCompanyAccess, companiesController.updateCompany);

// 仅superadmin可删除公司
router.delete('/:companyId', checkSuperadmin, companiesController.deleteCompany);

module.exports = router;

用户路由(routers/users.js)

const express = require('express');
const router = express.Router();
const usersController = require('../controllers/users');
const { checkCompanyAccess, checkAdminOrSuperadmin, checkUserOwnership } = require('../services/authorisation');
const passport = require('../services/passport');
const User = require('../models/User');

router.use(passport.authenticate('jwt', { session: false }));

// superadmin或目标公司admin可查看公司内所有用户
router.get('/company/:companyId', checkAdminOrSuperadmin, checkCompanyAccess, usersController.getUsersByCompany);

// superadmin或目标公司admin可创建用户
router.post('/', checkAdminOrSuperadmin, (req, res, next) => {
  // 将请求体中的companyId传入权限校验中间件
  req.params.companyId = req.body.companyId;
  checkCompanyAccess(req, res, next);
}, usersController.createUser);

// superadmin或用户所属公司admin可更新用户信息
router.put('/:userId', checkAdminOrSuperadmin, (req, res, next) => {
  // 先查询目标用户所属公司,再执行权限校验
  User.findById(req.params.userId)
    .then(targetUser => {
      req.params.companyId = targetUser.companyId;
      checkCompanyAccess(req, res, next);
    })
    .catch(err => res.status(500).json({ message: '权限校验失败', error: err }));
}, usersController.updateUser);

// 普通用户仅能修改自身信息
router.put('/profile/:userId', checkUserOwnership, usersController.updateUserProfile);

// 仅superadmin可删除用户
router.delete('/:userId', checkSuperadmin, usersController.deleteUser);

module.exports = router;

4. 模型层辅助优化(可选)

在User模型中添加静态方法,简化权限校验逻辑:

// models/User.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  role: { type: String, enum: ['superadmin', 'admin', 'user'], default: 'user' },
  companyId: { type: mongoose.Schema.Types.ObjectId, ref: 'Company', required: true },
});

// 静态方法:校验用户是否有权限访问目标公司
userSchema.statics.hasCompanyAccess = function(userId, targetCompanyId) {
  return this.findById(userId)
    .then(user => {
      if (!user) return false;
      if (user.role === 'superadmin') return true;
      return user.companyId.toString() === targetCompanyId.toString();
    });
};

module.exports = mongoose.model('User', userSchema);

之后你可以在授权中间件中调用User.hasCompanyAccess(req.user._id, companyId)来替代重复的校验逻辑。

5. 边界场景处理

  • 资源不存在校验:在权限校验前先校验资源(如Company、User)是否存在,避免泄露资源存在性信息
  • 跨公司操作拦截:确保admin角色无法访问其他公司的任何资源
  • 权限粒度细化:可根据业务需求添加更多角色或权限规则(如readonly admin)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:13:35