跨域

时间:2017-11-26 14:41:08

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

我正在开发一个 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),所以我可以检查每个请求中的令牌(性能观点)。
  5. 我认为这也是一个好主意。我只是想要一些建议,无论我是否正在考虑正确的方向。

    我将从中获得:

    1. 应用程序中登录用户的数量(活动会话)。
    2. 如果用户闲置了一段时间,我可以退出用户。
    3. 我可以管理同一用户的多个登录会话(通过在数据库中输入)。
    4. 我可以允许最终用户清除所有其他登录会话(例如Facebook和Gmail优惠)。
    5. 与授权相关的任何自定义。
    6. 编辑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. My Express服务器正在端口3000上运行。
      2. 要从服务器使用任何API,用户必须登录。
      3. 当用户从localhost:3000登录时,服务器会检查凭据(使用Passport-local)并在响应头中返回一个令牌。
      4. 现在登录后,当用户点击localhost:3000中的任何API时,预定义的Header会自带passport-session,然后护照会使用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

5 个答案:

答案 0 :(得分:2)

解决CORS / cookie /同域问题的一种可能解决方案是创建代理服务器,该服务器将镜像从localhost:3000/apilocalhost: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'));

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

答案 3 :(得分:0)

你已经看了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中。 会话数据存储在服务器端

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

答案 4 :(得分:0)

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

要实现这一目标,您需要的是使用数据库解决方案配置express-session。您可以使用此程序包https://github.com/jdesboeufs/connect-mongo使用mongo执行此操作。

但是,最佳做法是针对此类用例使用更强大的功能,例如redis使用此程序包https://github.com/tj/connect-redis