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

基于CASL实现Mongoose嵌套文档的权限校验问题

用CASL + Mongoose实现嵌套关联的Document权限校验

我来帮你搞定这个嵌套数据的权限校验问题,结合CASL和Mongoose其实不难,咱们一步步拆解:

1. 先明确CASL的权限规则

首先得在CASL里定义清楚权限逻辑:用户能访问某个Document,当且仅当该Document关联的Vehicle的users列表里包含当前用户ID。规则代码可以这么写:

const { AbilityBuilder, createMongoAbility } = require('@casl/ability');

function defineAbilitiesFor(user) {
  const { can, build } = new AbilityBuilder(createMongoAbility);

  // 核心规则:用户可读取关联自己Vehicle的Document
  can('read', 'Document', {
    'vehicle.users': { $in: [user._id] }
  });

  // 顺带保留用户对自己Vehicle的基础权限(如果业务需要的话)
  can('read', 'Vehicle', { users: { $in: [user._id] } });

  return build();
}

这里利用CASL支持嵌套字段查询的特性,直接指向vehicle.users,后续能无缝和Mongoose的查询逻辑结合。

2. 实现Document的权限查询

有两种常用的实现方式,你可以根据业务场景选:

方式一:分步查询(直观易维护)

这种方式先拿到用户拥有的所有Vehicle ID,再用这些ID筛选Document,逻辑清晰,适合数据量不大的场景:

const mongoose = require('mongoose');
const Vehicle = mongoose.model('Vehicle');
const Document = mongoose.model('Document');

async function getUserDocuments(userId) {
  // 第一步:获取用户有权访问的所有Vehicle ID
  const userVehicles = await Vehicle.find({ users: { $in: [userId] } }, '_id');
  const vehicleIds = userVehicles.map(vehicle => vehicle._id);

  // 第二步:用CASL权限规则过滤Document
  const ability = defineAbilitiesFor({ _id: userId });
  const accessibleDocs = await Document.find(
    ability.can('read', 'Document') ? {} : ability.relevantQuery('read', 'Document')
  ).where('vehicle').in(vehicleIds);

  return accessibleDocs;
}

方式二:聚合查询(一次数据库请求完成)

如果想减少数据库请求次数,可以用MongoDB的聚合管道,直接关联Vehicle集合并完成权限筛选:

async function getUserDocumentsWithAggregation(userId) {
  const ability = defineAbilitiesFor({ _id: userId });
  const baseQuery = ability.relevantQuery('read', 'Document');

  const documents = await Document.aggregate([
    // 关联Vehicle集合,拿到对应的users字段
    {
      $lookup: {
        from: 'vehicles', // 注意这里填集合名,不是模型名
        localField: 'vehicle',
        foreignField: '_id',
        as: 'vehicleInfo'
      }
    },
    // 过滤掉未关联到Vehicle的无效Document
    { $match: { vehicleInfo: { $ne: [] } } },
    // 展开关联的Vehicle数据,方便后续筛选
    { $unwind: '$vehicleInfo' },
    // 同时应用CASL规则和用户ID筛选
    { $match: { ...baseQuery, 'vehicleInfo.users': { $in: [userId] } } },
    // 可选:移除临时关联的Vehicle数据,返回原始Document结构
    { $project: { vehicleInfo: 0 } }
  ]);

  return documents;
}

3. 更优雅的集成:CASL Mongoose插件(可选)

如果你想让代码更简洁,可以用@casl/mongoose插件,给模型直接添加accessibleBy方法:

const { accessibleRecordsPlugin } = require('@casl/mongoose');

// 给Document Schema注册插件
DocumentSchema.plugin(accessibleRecordsPlugin);

// 之后查询代码可以简化成这样:
async function getUserDocumentsWithPlugin(userId) {
  const ability = defineAbilitiesFor({ _id: userId });
  // 还可以顺便populate关联的Vehicle数据
  return await Document.accessibleBy(ability, 'read').populate('vehicle');
}

插件会自动把CASL规则转换成MongoDB能识别的查询条件,省去手动拼接的麻烦。

注意事项

  • 确保userId是ObjectId类型,如果是字符串要先转换:mongoose.Types.ObjectId(userId)
  • 如果需要支持创建、更新Document的权限,只要调整CASL规则即可,比如:can('create', 'Document', { vehicle: { users: { $in: [user._id] } } })

内容的提问来源于stack exchange,提问作者Adriano Foschi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:42:56