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

7
在我的Express/Mongoose应用程序中,我正在定义verifyOrdinaryUser函数来检查用户在服务器上是否已经通过身份验证。这很有效,但是我也定义了verifyAdmin函数来检查用户是否具有管理员权限(我使用passport-local-mongoose模块来定义用户模式)。 正如您所看到的,在verifyOrdinaryUser()函数中检查了用户的令牌,它将加载一个名为decoded的新属性到请求对象中,我正在尝试在verifyAdmin中重复使用该属性,这就是当我在Postman中遇到以下错误时的情况。
{
  "message": "Cannot read property '_doc' of undefined",
  "error": {}
}

以下是相关的IT技术内容:
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 的那几行代码吗?我知道你说看起来没问题,但我这里看不出有什么错。 - rtf
刚刚编辑了这个问题。如果您需要更多的澄清,请告诉我。 - forkinspace
嗯...我仍然看不到这个中间件在哪里被使用了 app.use。:) 你已经导出了3个可以用作中间件的函数。在某个时候,你应该会执行 app.use(foo.verifyOrdinaryUser); 然后是 app.use(foo.verifyAdmin);,对吧? - rtf
让我在应用程序中使用身份验证模块,就像你说的那样,我猜这值得一试。我会回复你的。 - forkinspace
尝试使用中间件app.use,但是Node甚至无法连接到服务器。 给我以下错误- var token = req.body.token || req.query.token || req.headers['x-access-token']; ^TypeError:无法读取未定义的属性“body”。 - forkinspace
显示剩余3条评论
17个回答

8
我也参加了coursera课程。其他答案提供的详细解释清楚地显示出他们对这些工具有深刻的理解,但我认为它们可能过于复杂了。引用Jogesh教授在coursera留言板上对类似问题的帖子的回复:“你是否确保在调用verifyAdmin之前首先调用了verifyOrdinaryUser?两者必须连续链式调用。”从错误来看,似乎req.decoded不可用。这意味着verifyOrdinaryUser没有被调用。此函数将decoded属性添加到req中。您在verify.js和app.js中的代码看起来正确,不需要更改。然而,在路由器中调用verifyAdmin函数时,请务必在调用verifyOrdinaryUser之后始终包括verifyAdmin,如下所示:
.get(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req, res, next) {

因为req.decode是在verifyOrdinaryUser中建立的,所以当你在没有先验证普通用户的情况下调用verifyAdmin时,你的decode仍然未定义。你可以像其他答案建议的那样使你的verifyAdmin函数更加全面,但对于这个任务来说并不是必要的。


5
我也遇到了同样的问题。以下是verifyAdmin()函数的代码片段:
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);
  }
}
});
};

4
我是一位有用的助手,以下是您需要翻译的内容:

我正在参加与你相同的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);
    }

};

然后在你的路由文件(如dishRouter.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);
    });
});

我按照所有这些代码操作,但仍然出现错误。以下是我的dishRouter和users中的一些代码: .delete(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function (req, res, next) 以及 if(req.decoded._doc.admin === true){ - tcoulson

3
您的代码看起来很好,只需将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”造成的。

祝好。


2
如果有人使用推荐的签名:
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.所提到的,有一种更简洁的方法。

2

为了那些可能不理解你的代码在做什么的人,我将提供一个详细的解释。对于你特定的错误,请滚动到本帖子底部。


 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还检查用户是否具有管理员标志,并返回以下结果(在本例中找到true): enter image description here 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();
            }
        });

现在不需要进一步查询数据库,下一个中间件可以利用这个结果。在您的情况下,您选择发送整个解码对象,这也不是错误的,但是不必要的。下一个中间件是验证前面结果中是否为true的管理员属性的函数。

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)
    }
  };

你需要在路由器中同时使用这两个函数。
.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。很可能在调用verifyOridinaryUser之前调用了verifyAdmin。


2

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);
    }
};


我尝试了一下,这对我有用。它几乎与verifyOrdinaryUser代码相同,除了(err || !decAdmin.payload._doc.admin)行。但是很好的点子。谢谢。 - mehmet riza oz

1
使用console.log()查看解码后的内容。你会发现它是存储在"data"对象中的文档。
然后,你可以使用decoded.data.admin来检查它。
编辑:
这是因为我的“获取令牌”是这样的。
exports.getToken = function (user) {
    return jwt.sign({**data:user**}, config.secretKey, {
        expiresIn: 3600
    });
};

这个小改变将使事情变得非常容易。

1

我们在同一个coursera课程中。

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

在第三周,我们没有使用.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){...}

在第四周,教授介绍了.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){...}

但是,如果像我一样,没有复制/粘贴课程提供的代码,而是手动输入,那么你可能会错过这个问题。需要更新整个代码才能使其正常工作。


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()
                }
            }                         
        });
    });

在我的情况下它起作用了。您可以在验证用户为普通用户后使用此函数。


虽然这段代码可能解决了问题,但是包括解释它如何以及为什么解决了问题将有助于提高您的帖子质量,并可能导致更多的赞。请记住,您正在回答未来读者的问题,而不仅仅是现在提问的人。请[编辑]您的答案以添加解释并指出适用的限制和假设。 - double-beep

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接