繁体   English   中英

跨域的Node JS身份验证

[英]Node js authentication in cross domain

我正在开发MEAN应用程序,我在项目中使用Angular 4。 为了进行身份验证,我已经实现了Passport js Local-strategy 我正在使用Express-session来保持持久Express-session 到现在为止一切都很好。

问题

在同一个域中的session工作正常,我能够对用户进行身份验证。 但是在跨域中,我无法维护会话。 它将为跨域的每个新请求生成一个新的会话ID。

然后我尝试了Passport-jwt但问题是我无法控制用户会话。 我的意思是,如果用户处于非活动状态,或者即使在服务器重新启动时,我也无法从服务器注销该用户,并且token也不会失效。

简而言之,我正在Node.js(Express js)中寻找一种身份验证解决方案,在其中可以跨域管理身份验证。

我已经看到了一些博客文章和做题像这样 ,但它并不能帮助。

谢谢。

编辑

我是否应该编写自己的代码来实现这一目标? 如果是这样,我有一个计划。

我的基本计划是:

  1. 用户将发送带有登录请求的凭据。
  2. 我将检查数据库中的凭据。 如果凭据有效,则我将生成一个随机令牌并将其保存到用户表中的数据库中,并将向用户提供成功响应时使用的相同令牌。
  3. 现在,对于每个请求,用户将发送令牌,而我将检查数据库中每个请求的令牌。 如果令牌有效,那么我将允许用户访问API,否则我将生成带有401状态代码的错误。
  4. 我正在使用Mongoose(MongoDB),因此可以在每个请求中检查令牌(从性能角度来看)。

我认为这也是一个好主意。 无论我是否朝着正确的方向思考,我都只需要一些建议。

我会得到什么:

  1. 应用程序(活动会话)中已登录的用户数。
  2. 如果用户闲置了一段时间,我可以注销该用户。
  3. 我可以管理同一用户的多个登录会话(通过在数据库中进行输入)。
  4. 我可以允许最终用户清除所有其他登录会话(如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,必须登录用户。
  3. 当用户从localhost:3000登录时,服务器将检查凭据(使用Passport-local)并在响应标头中返回令牌。
  4. 现在,登录后,当用户从localhost:3000任何API时,预定义的Headerreq.isAuthenticated() passport-session ,然后Passport使用req.isAuthenticated()验证用户会话,一切正常。
  5. 当用户从localhost:4000登录并且服务器在响应标头中发送令牌(与localhost:3000相同)。
  6. 成功登录后,用户从localhost:4000访问任何API时,护照js函数req.isAuthenticated()返回false
  7. 之所以发生这种情况,是因为在跨域中, cookie不会进入服务器,我们需要在客户端将withCredentials标头设置为true
  8. 我已经将withCredentials标头设置为true但是仍然在服务器上, req.isAuthenticated()返回false

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

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

您也可以通过expressrequest模块在节点中完成此操作,或者使用类似以下这样的现成中间件:

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

使用此中间件的解决方案非常简单:

var proxy = require('express-http-proxy');
var app = require('express')();

app.use('/api', proxy('localhost:4000'));

如果您想使用会话(即代替jwt等),我认为默认情况下它们只是内存中的,因此当您的应用程序扩展到多个主机时它将不起作用。 不过,将它们配置为持久很容易。

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

您可能曾经尝试过passport-jwt。 它根据登录时的JWT协议生成令牌。 您的要求是在注销时将生成的令牌列入黑名单。 为此,您可以在mongodb中使用名为userid和token的字段创建一个名为“ BlacklistToken”的集合。 用户注销后,可以在集合中插入令牌和用户标识。 然后编写一个中间件来检查令牌是否被列入黑名单。 如果重定向到登录页面。

您是否已经在这里看过:

在这种情况下,可以基于一些考虑将响应发送回去。

如果有问题的资源打算被广泛访问(就像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');

通常,第二台服务器重新创建一个新会话,因为假设您使用Express-session并根据文档

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

这意味着您需要找到一种同步服务器会话数据的方法...假设您找到了一种方法来进行同步,则当您尝试连接时,两台服务器都将检索相同的用户会话数据,而第二台服务器将不必创建一个新的会话...

如果我在这里正确理解了问题,则希望用户会话在服务器上是无状态的。 这样,只要用户登录,就可以在扩展应用程序时重新使用会话,甚至可以在重新启动应用程序时在服务器的任何实例中重用该会话。

为此,您需要使用数据库解决方案配置express-session 您可以使用此软件包https://github.com/jdesboeufs/connect-mongomongo进行此操作。

但是,最佳实践是针对这种用例使用更强大的功能,例如使用此包https://github.com/tj/connect-redis的 redis

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM