NodeJS Express REST服务器中的授权

时间:2017-07-23 12:53:51

标签: node.js express authentication authorization passport.js

我正使用RESTnodejs中构建express服务器。

我想允许某些用户执行某些调用。 即,让管理员可以编辑其他用户并查看报告,用户只能执行简单的操作。

我尝试使用passport.jspassport-ldapauth,我还想对身份验证(检查凭据)和授权执行不同的查询(检查用户是否属于某个组)。< / p>

var fs = require('fs');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');
var LdapStrategy = require('passport-ldapauth');

var app = express();

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));


// Allow self signed certificates
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

app.use('/', index);
app.use('/users', users);
var OPTS = {
  server: {
    url: 'ldaps://...',
    bindDN: 'cn=Manager,dc=com',
    bindCredentials: 'secret',
    searchBase: 'ou=people,dc=com',
    searchFilter: '(uid={{username}})',
    tlsOptions: {
      ca: [fs.readFileSync('/path/to/certificate.crt')]
    }
  },
  handleErrorsAsFailures: true,
  failureErrorCallback: (err) => console.log(err)
};

passport.use(new LdapStrategy(OPTS));
passport.use('test', new LdapStrategy(OPTS));

app.use(passport.initialize());

app.post('/login', function(req, res, next) {
    passport.authenticate('ldapauth', function(err, user, info) {
      if (err) return next(err);
      if (!user) return res.status(401).send(info);

      res.json(user);
      // req.logIn(user, function(err) {
      //   if (err)
      //     console.error(err);
      //   if (err) return next(err);
      //   return res.json(user);
      // })
    })(req, res, next);
  });

1 个答案:

答案 0 :(得分:1)

根据我在阅读文档时所知,passport-ldapauth策略不允许您执行任何其他检查或查询。策略和Passport通常旨在使登录/身份验证过程尽可能无缝且简单。因此,任何其他约束都需要自己处理。

话虽如此,passport-ldapauth利用ldapauth-fork下面的ldapjs依次使用here。您可以尝试使用显示heremiddlewareldapjs,但我认为最简单的解决方案是直接使用ldapauth-fork

我们首先需要设置ldapauth-fork,因此我们将使用以下示例app/ldap/index.js

const LdapAuth = require('ldapauth-fork')

const ldap = new LdapAuth({
  url: 'ldaps://...',
  bindDN: 'cn=Manager,dc=com',
  bindCredentials: 'secret',
  searchBase: 'ou=people,dc=com',
  searchFilter: '(uid={{username}})',
  tlsOptions: {
    ca: [fs.readFileSync('/path/to/certificate.crt')]
})

ldap.on('error', (err) => { throw err })

module.exports = ldap

我们的示例app/controllers/auth.js可能如下所示:

const jwt = require('jsonwebtoken')
const ldap = require('../ldap')
const { User } = require('../database/models') // mongoose model
const Promise = require('bluebird')

exports.login = async (req, res) => {
  const { username, password } = req.body

  if (!username || !password) {
    res.status(400
    res.json({ error: 'Missing username or password.' })
    return
  }

  // ldapauth-fork doesn't support Promises.
  // You can try to promisfy it, but I prefer this.
  // I've named it `profile`, but you can name it whatever you want.
  const profile = await Promise.fromCallback(cb => ldap.authenticate(username, password, cb))

  // Since this is a REST API, we need to send back a token.
  // For this example, we're creating it by hand.
  const token = jwt.sign({ user: profile }, 'secret', {})

  // Use epoch time from the token instead of generating it ourselves.
  const { exp } = jwt.verify(token, 'secret')

  // Finally send the token.
  // By convention, the keys are snake case.
  res.json({
    access_token: token,
    token_type: 'Bearer',
    expires_in: exp,
    user: profile
  })
}

现在我们已经创建了令牌,我们需要一种方法来验证该令牌。要做到这一点,我们需要编写自己的other。例如app/middleware/valid-token.js

const jwt = require('jsonwebtoken')

exports.needsAdminAccess = (req, res, next) => {
  // This token should have already been validated by the `requiresToken` middleware
  let token = req.header('authorization').split(' ')[1]
  token = jwt.verify(token, 'secret')

  // Let's check if they are in the admin group
  // Remember that we set the user/profile value in the controller.
  if (!token.user.dn.includes('ou=ADMIN')) {
    next(new Error('You must be an admin to access this route.'))
    return
  }

  // Any additional checks would go here.
  // ...

  // If everything is fine then call next to let the request continue.
  next()
}

exports.requiresToken = (req, res, next) => {
  // Assuming the token is in the header as Authorization: Bearer token
  let token = req.header('authorization').split(' ')[1]

  // Make sure our secret key matches
  token = jwt.verify(token, 'secret')

  // Additional checks of the token should be done here as well.
  // ...

  // Don't forget to call next if all is good
  next()
}

最后,无论您在何处定义路线,我们都会使用中间件,例如:

const express = require('express')
const app = express()
const { requiresToken, needsAdminAccess } = require('./middleware/valid-token')

// This route needs a valid token, but not admin rights
app.get('/user', requiresToken, (req, res) => { })

// This route needs a valid token AND admin rights
app.get('/admin', requiresToken, needsAdminAccess, (req, res) => { })

我从头开始编写所有内容,希望能够清晰地描绘出一切如何运作。您可以使用{{3}}包为您验证令牌,但我们需要验证具体的事情,以便我们自己编写。