在跨域中节点js身份validation

我正在处理一个MEAN应用程序,我正在使用Angular 4作为我的项目。 对于身份validation,我已经实施了Passport js Local-strategy 。 而且我正在使用Express-session维护持久Express-session 。 事情一切正常,直到这里。

问题

在相同的域session正常工作,我能够validation用户。 但在跨域,我无法保持会话。 它为跨域中的每个新请求生成一个新的会话ID。

然后我尝试了Passport-jwt但问题是我没有对用户会话的控制。 我的意思是我不能从服务器注销用户,如果他是不活动的,甚至在服务器上重新启动也token不会失效。

所以简单地说,我正在寻找Node js(Express js)中的身份validation解决scheme,我可以在其中pipe理跨域身份validation。

我已经看到了一些博客文章和SO这样的问题,但它没有帮助。

谢谢。

编辑

我应该写我自己的代码来达到这个目的吗? 如果是的话,我有一个计划。

我的基本计划是:

  1. 用户将通过login请求发送凭证。
  2. 我将检查数据库中的凭据。 如果凭证是有效的,我将生成一个随机令牌并将其保存到数据库,在用户表和我将提供给用户成功响应的相同标记。
  3. 现在,每个请求用户都将发送令牌,我将检查数据库中每个请求的令牌。 如果令牌是有效的,那么我将允许用户访问API,否则我将产生401状态代码的错误。
  4. 我正在使用Mongoose(MongoDB),所以我可以检查每个请求中的令牌(性能angular度)。

我认为这也是一个好主意。 我只是想要一些build议,不pipe我是否朝着正确的方向思考。

我会得到什么:

  1. 应用程序中login的用户数(活动会话)。
  2. 如果用户闲置一段时间,我可以注销用户。
  3. 我可以pipe理同一用户的多个login会话(通过在数据库中进行input)。
  4. 我可以允许最终用户清除所有其他login会话(如Facebook和Gmail优惠)。
  5. 任何与授权相关的定制。

编辑2

在这里我分享我的app.js代码

 var express = require('express'); var helmet = require('helmet'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var dotenv = require('dotenv'); var env = dotenv.load(); var mongoose = require('mongoose'); var passport = require('passport'); var flash = require('connect-flash'); var session = require('express-session'); var cors = require('cors'); var databaseUrl = require('./config/database.js')[process.env.NODE_ENV || 'development']; // configuration mongoose.connect(databaseUrl); // connect to our database var app = express(); // app.use(helmet()); // required for passport app.use(function(req, res, next) { res.header('Access-Control-Allow-Credentials', true); res.header('Access-Control-Allow-Origin', req.headers.origin); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept'); if ('OPTIONS' == req.method) { res.send(200); } else { next(); } }); app.use(cookieParser()); app.use(session({ secret: 'ilovescotchscotchyscotchscotch', // session secret resave: true, saveUninitialized: true, name: 'Session-Id', cookie: { secure: false, httpOnly: false } })); require('./config/passport')(passport); // pass passport for configuration var index = require('./routes/index'); var users = require('./routes/user.route'); var seeders = require('./routes/seeder.route'); var branches = require('./routes/branch.route'); var companies = require('./routes/company.route'); var dashboard = require('./routes/dashboard.route'); var navigation = require('./routes/navigation.route'); var roles = require('./routes/role.route'); var services = require('./routes/services.route'); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); // uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); // app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use(passport.initialize()); app.use(passport.session()); // persistent login sessions app.use(flash()); // use connect-flash for flash messages stored in session require('./routes/auth.route')(app, passport); app.use('/', index); app.use('/users', users); app.use('/seed', seeders); app.use('/branches', branches); app.use('/companies', companies); app.use('/dashboard', dashboard); app.use('/navigation', navigation); app.use('/roles', roles); app.use('/services', services); // catch 404 and forward to error handler app.use(function(req, res, next) { res.status(404).send({ status: 'NOT_FOUND', message: 'This resource is not available.'}); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page let errorObj = { status: 'INTERNAL_SERVER_ERROR', message: 'Something went wrong.', error: err.message }; res.status(err.status || 500).send(errorObj); }); module.exports = app; 

编辑3

对于那些不了解我的问题的人。 用简单的语言解释问题:

  1. 我的Express服务器在端口3000上运行。
  2. 为了从服务器使用任何API,用户必须login。
  3. 当用户从localhost:3000login时,服务器检查凭证(使用Passport-local),并在响应头中返回一个令牌。
  4. 现在,login后,当用户从localhost:3000任何API时,预设的Header会附带passport-session ,然后passport使用req.isAuthenticated()validation用户会话,所有的事情都按预期工作。
  5. 当用户从localhost:4000login时,服务器发送响应头中的令牌(与localhost:3000相同)。
  6. 当成功login后,用户从localhost:4000任何API localhost:4000护照js函数req.isAuthenticated()返回false
  7. 发生这种情况是因为在跨域中, cookie不会到达服务器,我们需要在客户端将withCredentials标头设置为true
  8. 我已经设置withCredentials头为true但仍然在服务器req.isAuthenticated()返回false

解决CORS / cookie /相同域问题的一个可能的解决scheme是创build代理服务器,将所有请求从localhost:3000/api镜像到localhost:4000 ,然后使用localhost:3000/api访问API而不是localhost:4000

生产部署的最佳方法是在您的Web服务器(nginx / apache)上执行。

你也可以通过expressrequest模块在节点上做,或者使用一些现成的中间件,比如:

https://github.com/villadora/express-http-proxy

这个中间件的解决scheme非常简单:

 var proxy = require('express-http-proxy'); var app = require('express')(); app.use('/api', proxy('localhost:4000')); 

如果你想使用会话(即。而不是jwt等),我想默认情况下,他们只是在内存中,所以它不会工作,因为你的应用程序扩展到多个主机。 虽然configuration它们很容易,

请参阅https://github.com/expressjs/session#compatible-session-stores

你可能已经用passport-jwt尝试过了。 它在login时根据JWT协议生成令牌。 您的要求是在注销时将生成的令牌黑名单。 为了达到这个目的,你可以在名为“BlacklistToken”的mongodb中用字段userid和token创build一个集合。 当用户注销时,可以在集合中插入令牌和用户标识。 然后编写一个中间件来检查令牌是否被列入黑名单。 如果是redirect到login页面。

你已经看过这里了 :

在这种情况下,可以根据一些考虑将回复发送回去。

如果有问题的资源被广泛访问(就像GET访问的任何HTTP资源一样), 那么发送Access-Control-Allow-Origin:*头就足够了

你可以试试这个(允许任何公共IP):

 app.use(function(req, res, next) { res.header('Access-Control-Allow-Credentials', true); res.header('Access-Control-Allow-Origin', '*'); // add this line // res.header('Access-Control-Allow-Origin', req.headers.origin); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); 

第二台服务器重新创build一个新的会话是正常的,因为假设你使用Express-session,并根据文档 :

会话数据不会保存在cookie本身,只是会话ID。 会话数据存储在服务器端

这意味着你需要find一种方法来同步服务器会话数据…假设你find一个方法来做到这一点,当你尝试连接,两台服务器将检索相同的用户会话数据,第二个将不必创build一个新的会话…

如果我在这里正确地理解了这个问题,你希望用户的会话在服务器上是无状态的。 因此,无论用户何时login,在扩展应用程序时,或者即使只是重新启动应用程序,都可以在服务器的任何实例中重新使用该会话。

为了达到这个目的,你需要用数据库解决schemeconfigurationexpress-session 。 你可以用mongo做这个包https://github.com/jdesboeufs/connect-mongo

但是,最好的做法是使用这种用例的一些更健壮的东西,如redis使用这个包https://github.com/tj/connect-redis