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

在我的express / mongoose应用程序中,我定义了verifyOrdinaryUser函数来检查用户是否在服务器上进行身份validation。 哪个效果很好,但是我已经在下面定义了verifyAdmin函数来检查用户是否也具有pipe理权限(我使用passport-local-mongoose模块来定义用户模式)。 正如你可以看到在verifyOrdinaryUser()函数中检查用户的令牌,它会加载一个名为解码的新属性的请求对象,我试图在verifyAdmin重用,这就是当我在postman中得到以下错误。

{ "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函数中搞砸了一些东西。 中间件的顺序看起来是正确的我build议,欢迎

谢谢

编辑:中间件从这里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); 

我也陷入了同样的问题。 下面是verifyAdmin()的verify.js代码片段

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

我也在课堂上。 其他答案所提供的全面解释清楚地表明了对这些工具的深刻理解,但是我认为这些工作可能是过分的。 引用Jogesh教授在课程留言板上回答的问题:

“在你调用verifyAdmin之前,你确定你先调用了verifyOrdinaryUser吗?这两个必须被连接在一起。

从错误,它看起来像req.decoded不可用。 这意味着verifyOrdinaryUser没有被调用。 该function将解码的属性添加到请求中。

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

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

因为req.decode是在verifyOrdinaryUser中build立的,所以当你首先调用verifyAdmin而没有verifyOrdinaryUser时,你的解码仍然是未定义的。 您可以像其他答案所build议的那样使您的verifyAdminfunction更加彻底,但同样,这个任务不是必须的。

我正在做同样的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); }); }); 

你的代码看起来很好,只需要编辑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); } } 

在你的情况下,错误是因为variables“错误”没有在代码中定义。

问候。

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


  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的validation输出。 这将返回一个JSON对象,该对象具有来自authentication结果的有用信息。 除了validationencryption的密码jwt.query还检查用户有pipe理标志设置,这里是它返回的结果(在这种情况下发现真实) 在这里输入图像描述

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

现在不需要对数据库进行进一步的查询,在下一个中间件中可以使用这个。 在你的情况下,你已经select发送整个解码对象,这也是没有错,但没有。 下一个中间件是你的function来validation是否从以前的结果pipe理员属性是真实的。

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

您需要在路由器中一起使用这两个function

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

第一次validation会检查login,第二次validation会检查pipe理员。 结果在第一个函数本身中可用,您也可以在一个函数中完成它。

您的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。

req.decoded._doc.admin不工作

但是你可以使用这个function。

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

从评论中,我猜你错误后app.use ing verifyAdmin是因为它被调用。 所以我们做了app.use(verifyAdmin())而不是app.use(verifyAdmin)

请注意,通过调用这个没有任何参数, req显然是不确定的。

我们想要的是Express来打电话。 我们只需要在某个地方填充这个函数。

中间件很有趣。


所以只是回顾一下,如果我正在跟踪这个权利,我认为我们得到了如下内容:

1)我们有一个app.js (或者可能是index.jsserver.js ),它执行所有的app.use路由器pipe道

2) app.js已经有Passport设置,这是照顾authentication。

3)我们还有一个与以下中间件导出的模块:

  • verifyOrdinaryUser
  • verifyAdmin

我们来调用这个模块foo.js

4)我们想在我们只想login用户和pipe理员用户的地方使用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); 

现在让我们假设只有pipe理员可以访问/users部分。 为此,我们将把verifyAdmin中间件放在JUST 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似乎有点关头。 不应该护照处理verifyOrdinaryUser用户部分?

我将不同的特权分配给不同的http请求,这是在… Router.js(promoRouter.js等)文件中完成的,这就是我不得不调用中间件的东西,如下所示

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

所以在调用verifyOrdinaryUser之后,它会返回解码的请求,我可以使用的时候我会调用verifyAdmin。 我正在调用verifyAdmin,而没有首先检查用户是否被authentication,换句话说,首先调用verifyOrdinaryUser。

在相同的课程当然在哪里。

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

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

 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){...} 

在第四周,教授介绍了简化代码的所有(中间件)

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

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

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

那么你可以使用decoded.data.admin来检查它。

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

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

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

你面对的问题是由于没有_doc下的_doc这个事实,正确的调用应该是按照下面的方式,

 req.decoded.data.admin 

为了确定我在说什么,只需在你的verifyAdmin函数的某处添加console.log(req.decoded) ,你应该在你的控制台上find类似于以下的输出

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

玩的开心 ;)

尝试这样做:

 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将包含可用于validationadmin状态的已解码细节。

如果有人正在使用推荐的签名:

 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){...} 

尽pipe如Emmanuel P.提到的那样,有一种更清洁的方式。

看着你的validationpipe理员代码,这是正确的,恰恰是“req.decoded._doc.admin”不再工作。 新的层次结构是“req.decoded.admin”。 要检查,并确定真正的层次结构,你可以console.log(req.decoded)。 👍🏽

那么,我刚刚开始这个任务 – 在牙科治疗后的周末发烧了 – 现在我已经坐了最后一个小时,试图把这个任务交给我 – 我在你们这个阶段 – 我想也许用robomongo编辑mongo db会重置令牌 – 这就是导致我的validationpipe理员失败的原因 – 无论如何,我想 – 我正在rest,然后再继续