【问题标题】:Node js authentication in cross domain跨域节点js认证
【发布时间】:2018-05-09 21:16:57
【问题描述】:

我正在开发一个 MEAN 应用程序,我正在为我的项目使用 Angular 4。对于身份验证,我实现了 Passport js Local-strategy。我正在使用Express-session 维护持久会话。到目前为止一切正常。

问题

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

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

简单来说,我正在寻找一个Node js(Express js)中的身份验证解决方案,我可以在其中管理跨域的身份验证。

我已经看过一些博客文章和类似this 这样的问题,但这并没有帮助。

谢谢。

编辑

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

我的基本计划是:

  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 时,预定义的Header 带有passport-session,然后passport 使用req.isAuthenticated() 验证用户会话,所有事情都按预期工作。
  5. 当用户从localhost:4000 登录并且服务器在响应标头中发送一个令牌(与localhost:3000 相同)。
  6. 当用户登录成功后,从localhost:4000点击任意API,passport js函数req.isAuthenticated()返回false
  7. 之所以发生这种情况,是因为在跨域中,cookie 没有转到服务器,我们需要在客户端将 withCredentials 标头设置为 true
  8. 我已将withCredentials 标头设置为true,但在服务器上req.isAuthenticated() 仍在返回false

【问题讨论】:

  • 可能最简单的解决方案是使用 cookie,它们是“跨域本地存储”......有点...... github.com/zendesk/cross-storage 在另一个问题上,我想已经有了一个解决方案,不过我从来没有找过,如果你找到了,分享一下,谢谢。
  • @Akxe_,我原来的问题不在客户端。问题出在Express-session,如果我们在跨域中,服务器会在每个请求上生成新会话。
  • 哦,值得一提...然后:stackoverflow.com/questions/9071969/…
  • @Akxe,这也不能解决我的问题。
  • 域是彼此的子域吗?像 example.com 和 blog.example.com 一样,还是像 example.com 和 example.org 这样不相关?

标签: javascript node.js express authentication mean-stack


【解决方案1】:

解决 CORS/cookie/same-domain 问题的一种可能解决方案是创建代理服务器,该服务器将镜像从 localhost:3000/apilocalhost:4000 的所有请求,然后使用 localhost:3000/api 而不是 @987654325 访问 API @。

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

您也可以通过expressrequest 模块在node 中执行此操作,或者使用一些现成的中间件,例如:

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        你看过了吗here:

        在这种情况下,可以根据一些考虑将响应发回。

        如果有问题的资源打算被广泛访问(就像任何通过 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,并且根据the documentation

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

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

        【讨论】:

        • 是的,我知道。
        • 我们不能使用“*”,因为我们接受跨域cookie,所以我们需要使用req.headers.origin
        • 您能否仔细检查一下 req.headers.origin 确实是预期的 IP... 就像跨域无法正常工作,因为您仅限制访问一个 IP(其他域IP),但是如果您希望客户端可以在两个域上请求,则需要允许所有客户端的公共IP,所以“*”......您是否尝试将“Access-Control-Allow-Origin”添加到“访问控制允许标题”列表?
        • @A STEFANI,我更新了问题,请看我的EDIT 3,你会明白我想要什么。
        • @ArpitMeena,我刚刚更新了我的答案,请看一下
        【解决方案5】:

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

        为此,您需要为express-session 配置数据库解决方案。你可以通过mongo 使用这个包https://github.com/jdesboeufs/connect-mongo 来做到这一点。

        但是,最佳做法是为这种用例使用更强大的东西,例如 redis 使用此包 https://github.com/tj/connect-redis

        【讨论】:

          猜你喜欢
          • 2021-01-09
          • 2015-04-17
          • 2017-01-13
          • 1970-01-01
          • 2010-11-06
          • 1970-01-01
          相关资源
          最近更新 更多