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

时间:2016-03-23 17:34:47

标签: node.js mongodb authentication express jwt

在我的express / mongoose应用程序中,我正在定义verifyOrdinaryUser函数来检查用户是否在服务器上进行了身份验证。哪个效果很好,但是我在下面定义了verifyAdmin函数以检查用户是否也具有管理员权限(我使用passport-local-mongoose模块来定义用户模式)。 正如您所看到的,在verifyOrdinaryUser()函数中检查了用户的令牌,它会将一个名为decoding的新属性加载到我正在尝试在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);

16 个答案:

答案 0 :(得分:7)

我也在课堂上。其他答案提供的详尽解释清楚地表明了对这些工具的深刻理解,但我认为它们对于这项任务可能有点过分。将Jogesh教授在课程留言板上的回复引用到具有类似问题的主题:

"您是否确定在调用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功能更加彻底,但同样,此分配也不是必需的。

答案 1 :(得分:5)

我也遇到了同样的问题。以下是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);
  }
}
});
};

答案 2 :(得分:3)

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

答案 3 :(得分: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);
    }
};

答案 4 :(得分:2)

您的代码看起来很好,只需编辑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”。

问候。

答案 5 :(得分: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加密密码还检查用户是否设置了admin标志,这里是它返回的结果(在这种情况下发现为true) enter image description here

我们感兴趣的是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();
            }
        });

现在无需对数据库进行进一步查询,在下一个中间件中您可以使用它。在你的情况下,你已经选择发送整个解码的对象,这也没有错,但不必要。下一个中间件是您的函数,用于验证以前的结果中admin属性是否为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。

答案 6 :(得分:1)

从评论中,我猜你在app.use之后得到的错误 verifyAdmin是因为它被调用了。我们做了app.use(verifyAdmin())而不是app.use(verifyAdmin)

请注意,通过不带任何参数调用此内容,req显然会undefined

我们想要的是 Express 来进行调用。我们只需要在某个地方填充这个功能。

中间件很有趣。

所以回顾一下,如果我追踪到这一点,我认为我们得到的是以下内容:

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

  2. app.js已设置Passport,负责身份验证。

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

    • verifyOrdinaryUser
    • verifyAdmin
  4. 我们将此模块称为foo.js

    1. 我们希望在我们只想登录用户和管理员用户的地方使用verifyOrdinaryUserverifyAdmin
    2. 我会继续并假设我们不希望用户能够在登录页面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用户部分吗?

答案 7 :(得分:1)

我正在为不同的http请求分配不同的权限,这是在... Router.js(promoRouter.js等)文件中完成的,我必须将中间件称为以下内容

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

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

答案 8 :(得分:1)

同一课程在哪里。

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

在第4周,教授介绍了 .all(中间件),简化了代码。

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

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

答案 9 :(得分:1)

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

然后您可以使用decoded.data.admin进行检查。

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

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

这一小改动将使事情变得非常简单。

答案 10 :(得分:1)

您遇到的问题是由于_doc下没有decoded,正确的呼叫应按照以下方式进行,

req.decoded.data.admin

要确定我在说什么,只需在console.log(req.decoded)函数中的某处添加verifyAdmin,您就应该在控制台上找到类似于以下内容的输出

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

玩得开心;)

答案 11 :(得分: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()
                }
            }                         
        });
    });

在我的情况下有效。将用户确认为普通用户后即可使用此功能。

答案 12 :(得分:0)

尝试这样做:

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将包含已解码的详细信息,可用于验证admin的状态。

答案 13 :(得分:0)

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

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

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

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.所说,但有一种更清洁的方式。

答案 14 :(得分:0)

查看您的验证管理员代码,它是正确的恰好是" req.decoded._doc.admin"不再工作了。新的层次结构是" req.decoded.admin"。要检查并确定真正的层次结构,您可以使用console.log(req.decoded)。

答案 15 :(得分:-1)

好吧,我刚刚开始任务 - 在获得牙科治疗后的周末发烧了 - 现在我已经坐了最后一个小时并试图将任务调整到位 - 我在舞台上我们你们都是 - 我想也许用robomongo编辑mongo db会重置令牌 - 导致我的验证管理员失败的原因 - 无论如何我想 - 我在继续之前休息一下