Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/node.js/36.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Node.js 环回授权用户仅查看其数据_Node.js_Express_Loopbackjs - Fatal编程技术网

Node.js 环回授权用户仅查看其数据

Node.js 环回授权用户仅查看其数据,node.js,express,loopbackjs,Node.js,Express,Loopbackjs,我正在使用Loopback开发一个NodeJS应用程序 我对nodejs和restapi都很陌生,所以如果我在概念上有错误,请纠正我 Loopback自动构建crudrestapi,这是我想要使用的一个特性,以避免自己编写api,但我需要限制用户只能看到他们的数据 例如,假设我的数据库中有3个表,user,book和一个关系表user\u book 例如: table user id | name --------- 1 | user1 2 | user2

我正在使用Loopback开发一个NodeJS应用程序

我对nodejs和restapi都很陌生,所以如果我在概念上有错误,请纠正我

Loopback自动构建crudrestapi,这是我想要使用的一个特性,以避免自己编写api,但我需要限制用户只能看到他们的数据

例如,假设我的数据库中有3个表,
user
book
和一个关系表
user\u book

例如:

table user
    id | name
    ---------
    1 | user1
    2 | user2
    3 | user3

table book
    id | title | author
    -------------------
    1 | title1 | author1
    2 | title2 | author1
    3 | title3 | author2
    4 | title4 | author2
    5 | title5 | author3

table user_book
    id | user_id | book_id
    -------------------
    1 |     1    |    1
    2 |     1    |    4
    3 |     1    |    3
    4 |     2    |    3
    5 |     2    |    2
    6 |     2    |    1
    7 |     3    |    3
当用户
X
通过身份验证时,API
/books
应该只回答X的书,而不是表中的每本书。例如,如果用户
user1
被记录并调用
/books
,那么他应该只获取他的书籍,因此id为
1、3、4的书籍

类似地,
/books?filter[where][book\u author]='author1'
应仅返回作者为'author1'的用户
X
的书籍

我发现loopback在执行远程方法之前和之后提供了附加,并且还提供了所谓的

[…]指定可以作为方法调用引用的常用查询 关于模型[…]

我正在考虑使用2的组合,以便将对表
books
的访问限制为仅运行API调用的用户的行

module.exports = function (book) {

  // before every operation on table book
  book.beforeRemote('**', function (ctx, user, next) {
    [HERE I WOULD PERFORM A QUERY TO FIND THE BOOKS ASSOCIATED WITH THE USER, LET'S CALL ID book_list]

    ctx._ds = book.defaultScope; // save the default scope
    book.defaultScope = function () {
      return {
        'where': {
          id in book_list
        }
      };
    };

    next();
  });

  book.afterRemote('**', function (ctx, user, next) {
    book.defaultScope = ctx._ds; // restore the default scope
    next();
  });
};

这个解决方案有效吗?特别是,我特别关注并发性。如果来自不同用户的
/books
发生多个请求,那么更改默认范围是否是一项关键操作?

我们实现这一点的方法是创建一个mixin。看看github中的环回时间戳混合。我建议您创建一个与您的用户模型的“所有者”关系。简而言之,它的工作原理如下:

  • 使用mixin的每个模型都将在模型和用户之间创建一个关系
  • 每次创建模型的新实例时,用户ID都将与实例一起保存
  • 每次调用find或findById时,查询都会被修改以添加{where:{userId:[当前登录的用户id]}}子句
/common/mixins/owner.js

'use strict';
module.exports = function(Model, options) {
  // get the user model
  var User = Model.getDataSource().models.User;
  // create relation to the User model and call it owner
  Model.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'});

  // each time your model instance is saved, make sure the current user is set as the owner
  // need to do this for upsers too (code not here)
  Model.observe('before save', (ctx, next)=>{
    var instanceOrData = ctx.data ? 'data' : 'instance';
    ctx[instanceOrData].ownerId = ctx.options.accessToken.userId;
  });

  // each time your model is accessed, add a where-clause to filter by the current user
  Model.observe('access', (ctx, next)=>{
    const userId = safeGet(ctx, 'options.accessToken.userId');
    if (!userId) return next();  // no access token, internal or test request;
    var userIdClause = {userId: userId};

    // this part is tricky because you may need to add
    // the userId filter to an existing where-clause

    ctx.query = ctx.query || {};
    if (ctx.query.where) {
      if (ctx.query.where.and) {
        if (!ctx.query.where.and.some((andClause)=>{
          return andClause.hasOwnProperty('userId');
        })) {
          ctx.query.where.and.push(userIdClause);
        }
      } else {
        if (!ctx.query.where.userId) {
          var tmpWhere = ctx.query.where;
          ctx.query.where = {};
          ctx.query.where.and = [tmpWhere, userIdClause];
        }
      }
    } else {
      ctx.query.where = userIdClause;
    }
    next();
  });
};
/common/models/book.json

{
  "mixins": {
    "Owner": true
  }
}
{
  "name": "user_book",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "id": {
      "type": "number",
      "required": true
    },
    "user_id": {
      "type": "number",
      "required": true
    },
    "book_id": {
      "type": "number",
      "required": true
    }
  },
  "validations": [],
  "relations": {
    "user": {
      "type": "belongsTo",
      "model": "user",
      "foreignKey": "user_id"
    },
    "book": {
      "type": "belongsTo",
      "model": "book",
      "foreignKey": "book_id"
    }
  },
  "acls": [{
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW",
      "property": "*"
    }],
  "methods": []
}
{
  "name": "user",
  "base": "User",
  "idInjection": true,
  "properties": {},
  "validations": [],
  "relations": {
    "projects": {
      "type": "hasMany",
      "model": "project",
      "foreignKey": "ownerId"
    },
    "teams": {
      "type": "hasMany",
      "model": "team",
      "foreignKey": "ownerId"
    },
    "books": {
      "type": "hasMany",
      "model": "book",
      "foreignKey": "user_id",
      "through": "user_book"
    }
  },
  "acls": [{
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "ALLOW",
      "property": "listMyBooks"
    }],
  "methods": []
}

每次使用Owner mixing时,该模型将在每次创建或保存新实例时自动添加和填充ownerId属性,并且每次“获取”数据时,结果将自动过滤。

我认为解决方案是使用环回关系。必须设置关系: -用户通过用户手册拥有许多书籍 -书中有许多用户通过用户书

这与回送文档提供的示例类似:

因此,假设用户在使用函数之前应通过身份验证,然后您可以传递user/userId/books以获得特定用户可以访问的图书

如果要限制访问,则应使用ACL。对于这种情况,必须使用自定义角色解析程序,回送提供了相同的示例:

如果应用了此解析器,则用户只能访问属于他们的书籍


希望这有帮助

以下是我解决您问题的方法:

/common/models/user\u book.json

{
  "mixins": {
    "Owner": true
  }
}
{
  "name": "user_book",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "id": {
      "type": "number",
      "required": true
    },
    "user_id": {
      "type": "number",
      "required": true
    },
    "book_id": {
      "type": "number",
      "required": true
    }
  },
  "validations": [],
  "relations": {
    "user": {
      "type": "belongsTo",
      "model": "user",
      "foreignKey": "user_id"
    },
    "book": {
      "type": "belongsTo",
      "model": "book",
      "foreignKey": "book_id"
    }
  },
  "acls": [{
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW",
      "property": "*"
    }],
  "methods": []
}
{
  "name": "user",
  "base": "User",
  "idInjection": true,
  "properties": {},
  "validations": [],
  "relations": {
    "projects": {
      "type": "hasMany",
      "model": "project",
      "foreignKey": "ownerId"
    },
    "teams": {
      "type": "hasMany",
      "model": "team",
      "foreignKey": "ownerId"
    },
    "books": {
      "type": "hasMany",
      "model": "book",
      "foreignKey": "user_id",
      "through": "user_book"
    }
  },
  "acls": [{
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "ALLOW",
      "property": "listMyBooks"
    }],
  "methods": []
}
/common/models/book

{
  "name": "book",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "id": {
      "type": "number",
      "required": true
    },
    "title": {
      "type": "string",
      "required": true
    },
    "author": {
      "type": "string",
      "required": true
    }
  },
  "validations": [],
  "relations": {
      "users": {
        "type": "hasMany",
        "model": "user",
        "foreignKey": "book_id",
        "through": "user_book"
      }
  },
  "acls": [{
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW",
      "property": "*"
    }],
  "methods": []
}
/common/models/user.json

{
  "mixins": {
    "Owner": true
  }
}
{
  "name": "user_book",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "id": {
      "type": "number",
      "required": true
    },
    "user_id": {
      "type": "number",
      "required": true
    },
    "book_id": {
      "type": "number",
      "required": true
    }
  },
  "validations": [],
  "relations": {
    "user": {
      "type": "belongsTo",
      "model": "user",
      "foreignKey": "user_id"
    },
    "book": {
      "type": "belongsTo",
      "model": "book",
      "foreignKey": "book_id"
    }
  },
  "acls": [{
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW",
      "property": "*"
    }],
  "methods": []
}
{
  "name": "user",
  "base": "User",
  "idInjection": true,
  "properties": {},
  "validations": [],
  "relations": {
    "projects": {
      "type": "hasMany",
      "model": "project",
      "foreignKey": "ownerId"
    },
    "teams": {
      "type": "hasMany",
      "model": "team",
      "foreignKey": "ownerId"
    },
    "books": {
      "type": "hasMany",
      "model": "book",
      "foreignKey": "user_id",
      "through": "user_book"
    }
  },
  "acls": [{
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "ALLOW",
      "property": "listMyBooks"
    }],
  "methods": []
}
然后在用户模型js文件中,您需要使用HTTP动词“GET”创建一个定制的远程方法,并具有route“/books”。在其处理函数中,您应该获取经过身份验证的用户实例(带有访问令牌信息),并只返回user.books(通过对直通关系的环回实现),以获取用户\ book模型指定的相关书籍。下面是代码示例:

/common/models/user.js

module.exports = function(User) {
  User.listMyBooks = function(accessToken,cb) {
    User.findOne({where:{id:accessToken.userId}},function(err,user) {
      user.books(function (err,books){
          if (err) return cb(err);
          return cb(null,books);
      });
    });
  };
  User.remoteMethod('listMyBooks', {
    accepts: [{arg: 'accessToken', type: 'object', http: function(req){return req.res.req.accessToken}}],
    returns: {arg: 'books', type: 'array'},
    http: {path:'/books', verb: 'get'}
  });
};
还请确保远程方法公开供公众访问:

/server/model config.json:

  ...
  "user": {
    "dataSource": "db",
    "public": true
  },
  "book": {
    "dataSource": "db",
    "public": true
  },
  "user_book": {
    "dataSource": "db",
    "public": true
  }
  ...
有了这些,您应该能够调用
GET/users/books?access\u token=[authenticated token government from POST/users/login]
获取属于已验证用户的书籍列表。 有关环回中has多直通关系的使用,请参阅参考资料:

祝你好运!:)


使用此mixim而不是@YeeHaw1234 answer。所有其他步骤都是相同的。

我想补充一下YeeHaw1234的答案。我计划按照他描述的方式使用mixin,但我需要更多的字段,而不仅仅是用户ID来过滤数据。我还有另外3个字段要添加到访问令牌中,以便在尽可能低的级别强制执行数据规则

我想在会话中添加一些字段,但不知道如何在环回中添加。我查看了express session和cookie express,但问题是我不想重写环回登录,登录似乎是应该设置会话字段的地方

我的解决方案是创建一个自定义用户和自定义访问令牌,并添加我需要的字段。然后,在写入新的访问令牌之前,我使用操作挂钩(保存之前)插入新字段

现在每次有人登录,我都会得到额外的字段。如果有更简单的方法向会话添加字段,请随时通知我。我计划添加一个更新访问令牌,这样,如果用户的权限在登录时发生更改,他们将在会话中看到这些更改

下面是一些代码

/通用/models/mr-access-token.js

var app = require('../../server/server');

module.exports = function(MrAccessToken) {

  MrAccessToken.observe('before save', function addUserData(ctx, next) {
    const MrUser = app.models.MrUser;
    if (ctx.instance) {
      MrUser.findById(ctx.instance.userId)
        .then(result => {
        ctx.instance.setAttribute("role");
        ctx.instance.setAttribute("teamId");
        ctx.instance.setAttribute("leagueId");
        ctx.instance.setAttribute("schoolId");
        ctx.instance.role = result.role;
        ctx.instance.teamId = result.teamId;
        ctx.instance.leagueId = result.leagueId;
        ctx.instance.schoolId = result.schoolId;
        next();
      })
      .catch(err => {
        console.log('Yikes!');
      })
    } else {
      MrUser.findById(ctx.instance.userId)
        .then(result => {
        ctx.data.setAttribute("role");
        ctx.data.setAttribute("teamId");
        ctx.data.setAttribute("leagueId");
        ctx.data.setAttribute("schoolId");
        ctx.data.role = result.role;
        ctx.data.teamId = result.teamId;
        ctx.data.leagueId = result.leagueId;
        ctx.data.schoolId = result.schoolId;
        next();
      })
      .catch(err => {
        console.log('Yikes!');
      })
    }
  })


};
这花了我很长时间调试。这是我遇到的一些障碍。我最初认为它需要在/server/boot中,但我没有看到在保存时触发的代码。当我将它移动到/common/models时,它开始启动。文档中没有试图找出如何从观察者内部引用第二个模型。
var-app=…
在另一个SO答案中。最后一个大问题是我有
next()
ou