【问题标题】:Check resource ownership on Node.js rest API检查 Node.js REST API 上的资源所有权
【发布时间】:2018-04-16 04:37:52
【问题描述】:

我正在尝试使用 express 和 mongoose 开发 Node.js 后端。

网络上有很多关于如何实现适当的身份验证层的示例,但我找不到任何如何正确实现授权层的示例。

在我的具体情况下,我正在创建一个多用户应用程序的后端,我希望每个用户只能看到自己插入的数据。

我有三个模型:

  1. 用户
  2. 类别
  3. 文档

用户拥有一个或多个类别类别包含零个或多个文档

CRUD 操作在以下端点上实现:

/user/:userid
/user/:userid/category
/user/:userid/category/:categoryid
/user/:userid/category/:categoryid/document
/user/:userid/category/:categoryid/document/:documentid

在身份验证部分,我为每个请求设置了当前登录的用户 ID,因此我可以轻松检查

jsonwebtoken.userId == req.params.userid

否则返回403 错误。

检查类别的所有权非常容易,因为每个类别都包含对创建它们的用户的引用。

var CategorySchema = mongoose.Schema({
    name: {
        type: String,
        required: true,
        trim: true
    },
    user_id: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        index: true
    }
});

然而,在 Document 模型中,我只有一个对其所属类别的引用,但我没有添加对用户的引用。

因此,我想知道如何处理“嵌套”关系。我是否需要在任何深度级别为所有这些添加 user_id 引用?有什么最佳做法吗?

此外,这是做我需要的正确方法还是有任何官方/成熟的库已经这样做了?

【问题讨论】:

  • 这里有一个关系数据的例子。该解决方案在像 mongo 这样的 noSQL 数据库中并不优雅。我只想将“user_id”添加到文档中。这将防止将来出现优化问题。
  • @user1695032 谢谢,我认为最好通过user_id 检查所有权,否则我必须查询类别以查看所有者是否为user_id 并且仅在那个时候允许用户创建一个新文档,这会降低性能。对我来说似乎很奇怪,虽然这样的基本用例没有记录或得到图书馆的官方支持。

标签: node.js rest express mongoose


【解决方案1】:

no-sql 数据库使您能够将子文档(或关系数据库中的等效表)嵌入到单个文档中。因此,您可以考虑将架构重新设计为类似

{
  userId:"",
  categories": [
    {
      "categoryId": "",
      "name": "",
      "documents": [
        {
          "documentId": "",

        },
        {
          "documentId": "",

        },

      ]
    },
    {
      "categoryId": "",
      "name": "",
      "documents": [
        {
          "documentId": "",

        },
        {
          "documentId": "",

        },

      ]
    }
  ]
}

这可能会帮助您优化数据库查询的数量,但这里需要注意的重要一点是,如果每个用户和每个类别的类别和文档的数量分别增长得非常大,那么这种方法就不好了。

永远记住 mongo db 架构设计的 6 条重要经验法则

  1. 支持嵌入,除非有令人信服的理由不这样做

  2. 需要单独访问一个对象是不嵌入它的一个令人信服的理由

  3. 数组不应无限制地增长。如果“多”端有几百个文档,不要嵌入它们;如果“多”端有超过几千个文档,请不要使用 ObjectID 引用数组。高基数数组是不嵌入的一个令人信服的理由。

  4. 不要害怕应用级连接

  5. 在非规范化时考虑写入/读取比率。大部分会被读取但很少更新的字段是非规范化的良好候选者。

  6. 您希望构建数据以匹配应用程序查询和更新数据的方式。

取自here

【讨论】:

  • 虽然这个答案对于决定如何设计模式非常有用,但我认为它并没有真正解决有关如何验证资源“所有权”的问题。它没有提供任何模式/最佳实践/库来实现此目的。
【解决方案2】:

经过一番修改,我最终得到了以下中间件。

它基本上按预期顺序检查路由参数并检查一致的成员资格。

不确定这是否是实现这一目标的最佳方式,但它确实有效:

var Category = require('../category/Category'),
    Document = require('../document/Document'),
    unauthorizedMessage = 'You are not authorized to perform this operation.',
    errorAuthorizationMessage = 'Something went wrong while validating authorizations.',
    notFoundMessage = ' not found.';

var isValidMongoId = function (id) {
    if (id.match(/^[0-9a-fA-F]{24}$/)) {
        return true;
    }
    return false;
}

var verifyPermissions = function (req, res, next) {
    if (req.userId) {
        if (req.params.userid && isValidMongoId(req.params.userid)) {
            if (req.userId != req.params.userid) {
                return res.status(403).send({error: 403, message: unauthorizedMessage});
            }

            if (req.params.categoryid && isValidMongoId(req.params.userid)) {
                Category.findOne({_id: req.params.categoryid, user_id: req.params.userid}, function(err, category){
                    if (err) {
                        return res.status(500).send({error: 500, message: errorAuthorizationMessage})
                    }
                    if (!category) {
                        return res.status(404).send({error: 404, message: 'Category' + notFoundMessage});
                    }

                    if (req.params.documentid && isValidMongoId(req.params.documentid)) {
                        Document.findOne({_id: req.params.documentid, category_id: req.params.categoryid}, function(err, document){
                            if (err) {
                                return res.status(500).send({error: 500, message: errorAuthorizationMessage})
                            }
                            if (!document) {
                                return res.status(404).send({error: 404, message: 'Document' + notFoundMessage});
                            }
                        });
                    }
                });
            }
        }

        next();
    } else {
        return res.status(403).send({error: 403, message: unauthorizedMessage});
    }
};

module.exports = verifyPermissions;

【讨论】:

  • 该类别是否包含除名称和用户 ID 之外的其他信息?
  • @pulankit 是的,实际代码在类别中存储了更多属性(例如“上次打开”、“处于活动状态”)。此外,我正在寻找一种适用于在每个通用深度 N 处嵌套的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多