Nodejs/mongodb - 检查用户是不是具有管理员权限(基于令牌的身份验证)

Posted

技术标签:

【中文标题】Nodejs/mongodb - 检查用户是不是具有管理员权限(基于令牌的身份验证)【英文标题】:Nodejs/mongodb- Checking if a user has Admin privileges (token based auth)Nodejs/mongodb - 检查用户是否具有管理员权限(基于令牌的身份验证) 【发布时间】:2016-07-11 03:32:08 【问题描述】:

在我的 express/mongoose 应用程序中,我定义了 verifyOrdinaryUser 函数来检查用户是否在服务器上进行了身份验证。效果很好,但是我在下面定义了 verifyAdmin 函数来检查用户是否也具有管理员权限(我正在使用 passport-local-mongoose 模块来定义用户模式)。 正如您所看到的,在 verifyOrdinaryUser() 函数中检查了用户的令牌,它将加载一个名为 decoded 的新属性到我试图在 verifyAdmin 中重用的请求对象,这就是我在邮递员中收到以下错误的时候。


  "message": "Cannot read property '_doc' of undefined",
  "error": 

以下是

var User = require('../models/user');
var jwt = require('jsonwebtoken'); 
var config = require('../config.js');

exports.getToken = function (user) 
    return jwt.sign(user, config.secretKey, 
        expiresIn: 3600
    );
;

exports.verifyOrdinaryUser = function (req, res, next) 
    // check header or url parameters or post parameters for token
    var token = req.body.token || req.query.token || req.headers['x-access-token'];

    // decode token
    if (token) 
        // verifies secret and checks exp
        jwt.verify(token, config.secretKey, function (err, decoded) 
            if (err) 
                var err = new Error('You are not authenticated!');
                err.status = 401;
                return next(err);
             else 
                // if everything is good, save to request for use in other routes
                req.decoded = decoded;
                next();
            
        );
     else 
        // if there is no token
        // return an error
        var err = new Error('No token provided!');
        err.status = 403;
        return next(err);
    
;

exports.verifyAdmin = function(req,res,next)
    if(req.decoded._doc.admin !== true)  
        return next(err);
    else 
        return next();
    
;

我确定我在 verifyAdmin 函数中搞砸了。 中间件订单对我来说很合适 欢迎提出建议

谢谢

编辑:中间件来自 app.js

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded( extended: false ));
app.use(cookieParser());

// passport config
var User = require('./models/user');
app.use(passport.initialize());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);
app.use('/dishes',dishRouter);
app.use('/promotions',promoRouter);
app.use('/leadership',leaderRouter);

【问题讨论】:

快速健全性检查:您app.use中间件的顺序是什么? 快递中间件订单 你能把你app.use的行贴出来吗?我知道你说它看起来不错,但我在这里看不出有什么问题。 刚刚编辑了问题。如果您需要更多说明,请告诉我。 嗯...我仍然看不到这个中间件在哪里得到app.useed。 :) 您已经导出了 3 个可用作中间件的函数。在某些时候,您应该先使用app.use(foo.verifyOrdinaryUser);,然后再使用app.use(foo.verifyAdmin);,对吗? 【参考方案1】:
exports.verifyAdmin = ((req,res,next) =>
        const name = req.body.username
        console.log(name)
        User.findOne(username: name,(err,user) =>  
            if(err) 
                next(err)
            
            else if(!user) 
                next(new Error("user not found"))
            
            else 
                if(!user.admin) 
                    next(new Error("you are not an admin"))
                
                else 
                    next()
                
                                     
        );
    );

它在我的情况下有效。您可以在验证用户为普通用户后使用此功能。

【讨论】:

虽然此代码可能会解决问题,including an explanation 关于如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。【参考方案2】:

来自 cmets,我猜你在app.useing 之后遇到了错误 verifyAdmin 是因为它被调用了。所以我们做了app.use(verifyAdmin()) 而不是app.use(verifyAdmin)

请注意,通过不带任何参数调用它,req 显然是 undefined

我们想要的是 Express 进行调用。我们只需要把这个函数塞进某个地方。

中间件很有趣。


所以,回顾一下,如果我没记错的话,我认为我们得到的结果如下:

    我们有一个 app.js(或者可能是 index.jsserver.js)来完成所有 app.use 路由器管道

    app.js 已经设置了 Passport,它负责身份验证。

    我们还有一个模块,其中导出了以下中间件:

    verifyOrdinaryUser verifyAdmin

我们称这个模块为foo.js

    我们想在只希望登录用户和管理员用户去的地方使用verifyOrdinaryUserverifyAdmin

我会继续假设我们不希望用户能够在没有登录的情况下到达除登录页面 app.use('/', routes); 之外的任何地方。所以,这一行之后,让我们添加app.use(verifyOrdinaryUser)

app.use('/', routes);

// verifyOrdinaryUser will now be called before any middleware used AFTER this statement
app.use(foo.verifyOrdinaryUser);

app.use('/users', users);
app.use('/dishes',dishRouter);
app.use('/promotions',promoRouter);
app.use('/leadership',leaderRouter);

现在让我们假设只有管理员可以访问/users 部分。为此,我们将 verifyAdmin 中间件放在 users 路由器前面。现在我们的代码如下所示:

app.use('/', routes);

// verifyOrdinaryUser will now be called before any middleware used AFTER this statement
app.use(foo.verifyOrdinaryUser);

// Call the verifyAdmin middleware BEFORE any middleware in the `users` router
app.use('/users', foo.verifyAdmin, users);

app.use('/dishes',dishRouter);
app.use('/promotions',promoRouter);
app.use('/leadership',leaderRouter);

这段代码做了很多假设,但你应该能够适应它。

说了这么多,关于 req.decoded._doc 的东西一开始似乎有点不对劲。 Passport 不应该处理verifyOrdinaryUser 用户部分吗?

【讨论】:

我正在为不同的 http 请求分配不同的权限,这是在 ...Router.js 文件中完成的,这就是我必须调用中间件的地方,如下所示 .get(Verify.verifyOrdinaryUser, Verify. verifyAdmin, function(req,res,next)... 所以在调用 verifyOrdinaryUser 之后它会返回请求中的解码后我可以在调用 verifyAdmin 时使用它。我在调用 verifyAdmin 时没有先检查用户是否经过身份验证,换句话说,首先调用 verifyOrdinaryUser。无论如何,感谢您的努力。 @ForkInSpace,就我而言,您的方式仅适用于管理员用户。在普通用户中,它会给出以下错误: "message": "err is not defined", "error": 【参考方案3】:

查看您的验证管理员代码,这是正确的,只是“req.decoded._doc.admin”不再起作用。新的层次结构是“req.decoded.admin”。要检查并确定真正的层次结构,您可以使用 console.log(req.decoded)。 ??

【讨论】:

【参考方案4】:

如果有人使用以下推荐的签名:

exports.jwtPassport = passport.use(new JwtStrategy(opts,
    (jwt_payload, done) => 
        console.log("JWT payload: ", jwt_payload);
        User.findOne(_id: jwt_payload._id, (err, user) => 
            if (err) 
                return done(err, false);
            
            else if (user) 
                return done(null, user);
            
            else 
                return done(null, false);
            
        );
    ));

exports. verifyOrdinaryUser = passport.authenticate('jwt', session: false);

然后,您可能会注意到策略返回了“用户”对象。因此,通过设置:

exports.verifyAdmin = function(params, err, next) 
    if (params.user.admin)
      return next();
     else 
      var err = new Error('Only administrators are authorized to perform this operation.');
      err.status = 403;
      return next(err);
    
;

之后路由中的以下授权签名应该起作用:

...
.get(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req,res,next)...

不过,正如 Emmanuel P. 所提到的,还有一种更清洁的方法。

【讨论】:

【参考方案5】:

您面临的问题是由于decoded下没有_doc,正确的调用应如下所示,

req.decoded.data.admin

为了确保我在说什么,只需在您的 verifyAdminfunction 中的某处添加 console.log(req.decoded),您应该会在控制台上找到类似于以下内容的输出

MAC:passport username$ npm start
...
Connected correctly to server
 data: 
    _id: '59e011e9209e2613c5492b1d',
     salt: '...',
     hash: '...',
     username: 'username',
     __v: 0,
     admin: false ,
  iat: 1508012930,
  exp: 1508016530 

GET /dishes 401 18.242 ms - 70

因此,您的代码应如下所示,

exports.verifyAdmin = function (req, res, next) 

    if (req.decoded.data.admin == true)  //NOTICE THE CHANGE "_doc --> data"
        next();
     else 
        // if the user is not admin
        // return an error
        var err = new Error('You are not authorized to perform this operation!');
        err.status = 403;
        return next(err);
    
;

玩得开心;)

【讨论】:

【参考方案6】:

使用console.log() 查看解码后的实际内容。你会发现它是你存储在一个“数据”对象中的文档。

然后你可以使用decoded.data.admin 来检查它。

编辑: 这是因为我的“获取令牌”看起来像这样。

exports.getToken = function (user) 
    return jwt.sign(**data:user**, config.secretKey, 
        expiresIn: 3600
    );
;

这个小改动会让事情变得非常简单。

【讨论】:

【参考方案7】:

尝试这样做:

exports.verifyAdmin = function(req, res, next)
  if(req.decoded._doc.admin)
      return next();
  else
      var err = new Error('Not an Admin =.=');
      err.status = 401;
      return next(err);
  
 ;

由于 verifyOrdinaryUser 会首先被调用,所以 req.decode 将包含解码后的详细信息,可用于验证管理员的状态。

【讨论】:

【参考方案8】:

嗯,我刚刚开始执行任务 - 周末看牙后发烧 - 现在我已经坐了最后一个小时,并试图开始执行任务 - 我正处于与你们一样的阶段- 我认为也许用 robomongo 编辑 mongo db 会重置令牌 - 这就是导致我的验证管理员失败的原因 - 我认为无论如何 - 我在继续之前休息一下

【讨论】:

【参考方案9】:

我也在 coursera 课上。其他答案提供的详尽解释清楚地表明了对这些工具的深刻理解,但我认为它们对于这项任务来说可能是矫枉过正的。引用 Jogesh 教授在 coursera 留言板上对类似问题的回复:

“你确定你在调用 verifyAdmin 之前先调用了 verifyOrdinaryUser 吗?这两者必须一个接一个地链接。

从错误来看,req.decoded 似乎不可用。这意味着没有调用 verifyOrdinaryUser。此函数将解码后的属性添加到 req。"

verify.js 和 app.js 中的代码看起来正确,不需要更改。但是,在路由器中调用 verifyAdmin 函数时,请务必在调用 verifyOrdinaryUser 之后始终包含 verifyAdmin,如下所示:

.get(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req, res, next) 

因为 req.decode 是在 verifyOrdinaryUser 中建立的,所以当您先调用 verifyAdmin 而没有 verifyOrdinaryUser 时,您的解码仍然未定义。您可以像其他答案建议的那样使您的 verifyAdmin 功能更彻底,但同样,此分配没有必要。

【讨论】:

【参考方案10】:

req.decoded._doc.admin 不工作

但是你可以使用这个功能。

jwt.decode(token [, options]);

课程示例,对我有用:

exports.verifyAdmin = function(req, res, next) 
    // check header or url parameters or post parameters for token
    var token = req.body.token || req.query.token || req.headers['x-access-token'];

    // get the decoded payload and header "req.decoded._doc.admin" NOT WORKING
    var decAdmin = jwt.decode(token,  complete: true );

    // decode token
    if (token) 
        // verifies secret and checks exp
        jwt.verify(token, config.secretKey, function(err, decoded) 
            if (err || !decAdmin.payload._doc.admin) 
                var err = new Error('You are not authorized to perform this operation!');
                err.status = 403;
                return next(err);
             else 
                // if everything is good, save to request for use in other routes
                req.decoded = decoded;
                next();
            
        );
     else 
        // if there is no token
        // return an error
        var err = new Error('No token provided!');
        err.status = 403;
        return next(err);
    
;

【讨论】:

我试过了,这对我有用。除了 if (err || !decAdmin.payload._doc.admin) 行之外,verifyOrdinaryUser 代码几乎相同。但是好点。谢谢。【参考方案11】:

我正在学习与您相同的 Coursera 课程。我刚刚完成了这个任务!你应该做这个: - 在您的 verify.js 文件中添加:

exports.verifyAdmin = function (req, res, next) 

    if (req.decoded._doc.admin == true) 
        next();
     else 
        // if the user is not admin
        // return an error
        var err = new Error('You are not authorized to perform this operation!');
        err.status = 403;
        return next(err);
    

;

然后在你的路由器文件(如 discRouter.js)中,你应该使用这个中间件注入:

dishRouter.route('/')
.get(Verify.verifyOrdinaryUser, function(req,res,next)
    //res.end('Will send all the dishes to you!');
    Dishes.find(, function (err, dish) 
      if (err) throw err;
      res.json(dish);
    );
)

.post(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req, res, next)
    //res.end('Will add the dish: ' + req.body.name + ' with details: ' + req.body.description);
    Dishes.create(req.body, function (err, dish) 
      if (err) throw err;
      console.log('Dish created!');
      var id = dish._id;

      res.writeHead(200, 
          'Content-Type': 'text/plain'
      );
      res.end('Added the dish with id: ' + id);
    );
)

.delete(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req, res, next)
    //res.end('Deleting all dishes');
    Dishes.remove(, function (err, resp) 
      if (err) throw err;
      res.json(resp);
    );
);

【讨论】:

我遵循了所有这些代码,但我仍然收到错误消息。以下是我在 discRouter 和 users 中的一些代码: .delete(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function (req, res, next) 和 if(req.decoded._doc.admin === true)【参考方案12】:

我也遇到了同样的问题。这是 verifyAdmin() 的 verify.js 的 sn-p

exports.verifyAdmin = function(req, res, next)
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];

// verifies secret and checks exp
jwt.verify(token, config.secretKey, function (err, decoded) 
if (err) 
  var err = new Error('You are not authenticated!');
  err.status = 401;
  return next(err);
 else 
  // They are an admin
  if (decoded._doc.admin)
    return next();
   else 
    // They are not an admin
    var err = new Error('You are not authorized to perform this operation!');
    err.status = 403;
    return next(err);
  

);
;

【讨论】:

【参考方案13】:

为了那些可能不明白你的代码在做什么的人的利益,我会给出一个很长的解释,对于你的具体错误,请滚动到这篇文章的底部


 if (token) 
        // verifies secret and checks exp
        jwt.verify(token, config.secretKey, function (err, decoded) 
            if (err) 
                var err = new Error('You are not authenticated!');
                err.status = 401;
                return next(err);
             else 
                // if everything is good, save to request for use in other routes
                req.decoded = decoded;
                next();
            

将 req.decoded 属性设置为 jwt.query 的验证输出。这将返回一个 JSON 对象,该对象具有来自身份验证结果的有用信息。 jwt.query 除了验证加密密码外,还检查用户是否设置了管理员标志,这是它返回的结果(在这种情况下发现为真)

decoded._doc.admin 字段是我们感兴趣的。所以你可以使用类似

jwt.verify(token, config.secretKey, function (err, decoded) 
            if (err) 
                var err = new Error('You are not authenticated!');
                err.status = 401;
                return next(err);
             else 
                // if everything is good, save to request for use in other routes
                  if (decoded._doc.admin) 
                  req.admin = true;
                
                next();
            
        );

现在不需要进一步查询数据库,在下一个中间件中您可以使用它。在您的情况下,您选择发送整个解码对象,这也没有错,但不必要。下一个中间件是您的函数,用于验证 admin 属性是否从之前的结果中为真。

verifyAdmin = function(req,res,next)
    if (req.admin) 
      console.log("Admin active");
    next();
  
    else 
      var error  = new Error('You do not have admin privileges!');
      error.status = 401;
      return next(error)
    
  ;

需要在Router中同时使用这两个功能

.post(verifyOrdinaryUser, verifyAdmin, function(req, res, next)

第一个验证将检查登录,第二个验证将检查管理员。结果在第一个函数本身中可用,您也可以在一个函数中完成它。

您的 verifyAdmin 代码是

exports.verifyAdmin = function(req,res,next)
    if(req.decoded._doc.admin !== true)  
        return next(err);
    else 
        return next();
    
;

可能会遭受 - 错误不可用,但您的错误消息


  "message": "Cannot read property '_doc' of undefined",
  "error": 

表示您首先没有设置 req.decoded 。您很可能在调用 verifyOrdinaryUser 之前调用了 verifyAdmin。

【讨论】:

【参考方案14】:

您的代码看起来不错,只需将 verifyAdmin 函数编辑为:

exports.verifyAdmin = function(req, res, next)
    var isAdmin = req.decoded._doc.admin
    if (isAdmin) 
        return next();
    
    else 
        // if user is not admin
        // return an error
        var err =  new Error ('You are not autorized to perform this   operation!');
        err.status =  403;
        return next(err);

    

在您的情况下,错误是因为代码中未定义变量“error”。

问候。

【讨论】:

【参考方案15】:

在同一个 coursera 课程中。

正如 ForkInSpace 所说,如果没有定义,那是因为您的代码没有通过第一个中间件

在第 3 周,我们没有使用 .all(middleware) 语法,而是明确定义了每个操作的所有中间件

route('/')
.get(Verify.verifyOrdinaryUser, function(req,res,next)...
.put(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req,res,next)...
.post(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req,res,next)...

第4周,教授介绍了.all(middleware),简化了代码。

route('/')
.all(Verify.verifyOrdinaryUser)
.get(function(req,res,next)...
.put(Verify.verifyAdmin, function(req,res,next)...
.post( Verify.verifyAdmin, function(req,res,next)...

但是,如果您像我一样没有复制/粘贴课程中提供的代码,而是键入它,您可能会错过这一点。并且需要更新你的整个代码才能工作

【讨论】:

【参考方案16】:

我正在为不同的 http 请求分配不同的权限,这是在 ...Router.js(promoRouter.js 等)文件中完成的,这就是我必须调用中间件的地方,如下所示

.get(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req,res,next)...

所以在调用 verifyOrdinaryUser 后,它会返回请求中的解码后,我可以在调用 verifyAdmin 时使用它。我在没有首先检查用户是否经过身份验证的情况下调用了 verifyAdmin,换句话说,首先调用了 verifyOrdinaryUser。

【讨论】:

以上是关于Nodejs/mongodb - 检查用户是不是具有管理员权限(基于令牌的身份验证)的主要内容,如果未能解决你的问题,请参考以下文章

基于nodejs+mongodb实现的JWT

NodeJS MongoDB 添加到子文档数组

喜欢按钮,在数据库上加减。 nodejs, mongodb, 猫鼬, jquery

nodejs mongodb对象ID到字符串

nodejs mongodb对象ID到字符串

NodeJS MongoDb updateMany() 有条件?