检查 Node.js REST API 上的资源所有权

Posted

技术标签:

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

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

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

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

我有三个模型:

    用户 类别 文档

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

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 并且仅在那个时候允许用户创建一个新文档,这会降低性能。对我来说似乎很奇怪,虽然这样的基本用例没有记录或得到图书馆的官方支持。 【参考方案1】:

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


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

        ,
        
          "documentId": "",

        ,

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

        ,
        
          "documentId": "",

        ,

      ]
    
  ]

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

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

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

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

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

    不要害怕应用级连接

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

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

取自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 处嵌套的解决方案。

以上是关于检查 Node.js REST API 上的资源所有权的主要内容,如果未能解决你的问题,请参考以下文章

如何在 MobileFirst Foundation 8 中使用 REST API 从 Node.js 发送推送通知?

nodejs使用Node.js实现REST Client调用REST API

Node.js RESTful API

Node.js 通过 REST API 发送图像

Node.js REST API 从 MongoDB 获取数据

无状态 REST 服务器上的 Google API 回调