节点js跨域验证

时间:2021-10-28 16:34:00

I am working on a MEAN application, I am using Angular 4 for my project. For authentication, I have implemented the Passport js Local-strategy. And I am maintaining persistent session using Express-session. Things are working fine till here.

我正在研究MEAN应用程序,我正在为我的项目使用Angular 4。对于身份验证,我已经实现了Passport js Local-strategy。我正在使用Express-session维护持久会话。事情一直很好,直到这里。

The Problem

In the same domain session works fine and I am able to authenticate the user. But in cross-domain, I am not able to maintain the session. It generates a new session id for each new request in cross-domain.

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

I then tried Passport-jwt but the problem with it is I don't have the control over user session. I mean I can't logout the user from the server if he is inactive or even on server re-start also the token don't get invalid.

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

So in simple words, I am looking for an authentication solution in Node js (Express js) in which I can manage authentication in cross-domain.

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

I have already seen some blog post and SO questions like this, but it doesn't help.

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

Thank you.

EDIT

Should I write my own code to achieve this? If so I have a plan.

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

My basic plan is:

我的基本计划是:

  1. The user will send credentials with the login request.
  2. 用户将使用登录请求发送凭据。

  3. I will check for the credentials in the database. If credentials are valid, I will generate a random token and save it to the database, in the user table and the same token I will provide to the user with success response.
  4. 我将检查数据库中的凭据。如果凭据有效,我将生成一个随机令牌并将其保存到用户表中的数据库中,并且我将通过成功响应向用户提供相同的令牌。

  5. Now, with each request user will send the token and I will check the token for each request in the database. If the token is valid then I will allow the user to access the API otherwise I will generate an error with 401 status code.
  6. 现在,每个请求用户都将发送令牌,我将检查数据库中每个请求的令牌。如果令牌有效,那么我将允许用户访问API,否则我将生成401状态代码的错误。

  7. I am using Mongoose (MongoDB) so I will be ok to check the token in each request (performance point of view).
  8. 我正在使用Mongoose(MongoDB),所以我可以检查每个请求中的令牌(性能观点)。

I think this is also a good idea. I just want some suggestions, whether I am thinking in right direction or not.

我认为这也是一个好主意。我只是想要一些建议,无论我是否正在考虑正确的方向。

What I will get with this:

我会得到这个:

  1. The number of logged in user in the application (active sessions).
  2. 应用程序中登录用户的数量(活动会话)。

  3. I can logout a user if he is idle for a certain interval of time.
  4. 如果用户空闲了一段时间,我可以注销用户。

  5. I can manage multiple login session of the same user (by doing an entry in the database).
  6. 我可以管理同一用户的多个登录会话(通过在数据库中进行输入)。

  7. I can allow the end user to clear all other login sessions (like Facebook and Gmail offers).
  8. 我可以允许最终用户清除所有其他登录会话(如Facebook和Gmail优惠)。

  9. Any customization related to authorization.
  10. 任何与授权相关的自定义。

EDIT 2

Here I am shareing my app.js code

在这里,我正在分享我的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;

EDIT 3

For those who don't understand my problem. Explaining the problem in simple words:

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

  1. My Express server is running on port 3000.
  2. My Express服务器在端口3000上运行。

  3. In order to consume any API from the server, a user must be logged in.
  4. 要从服务器使用任何API,用户必须登录。

  5. When a user gets logged in from localhost:3000, the server checks the credentials(using Passport-local) and returns a token in the response header.
  6. 当用户从localhost:3000登录时,服务器会检查凭据(使用Passport-local)并在响应头中返回一个令牌。

  7. Now after login, when a user hits any API from localhost:3000, a predefined Header comes with passport-session and then passport verifies the user session using req.isAuthenticated() and all the things works as expected.
  8. 现在登录后,当用户从localhost:3000访问任何API时,预定义的Header会带有护照会话,然后护照会使用req.isAuthenticated()验证用户会话,并且所有内容都按预期工作。

  9. When a user gets logged in from localhost:4000 and the server send a token in response header (same as localhost:3000).
  10. 当用户从localhost:4000登录时,服务器在响应头中发送令牌(与localhost:3000相同)。

  11. When after successful login, the user hits any API from localhost:4000 the passport js function req.isAuthenticated() returns false.
  12. 成功登录后,用户从localhost:4000命中任何API,护照js函数req.isAuthenticated()返回false。

  13. This was happening because in cross-domain the cookie doesn't go to the server we need to set withCredentials header to true at the client side.
  14. 发生这种情况是因为在跨域中,cookie不会发送到服务器,我们需要在客户端将withCredentials标头设置为true。

  15. I have set withCredentials header to true but still at the server the req.isAuthenticated() is returning false.
  16. 我已将withCredentials标头设置为true,但仍然在服务器上req.isAuthenticated()返回false。

5 个解决方案

#1


1  

One possible solution to get around CORS/cookie/same-domain problems is to create proxy server that will mirror all requests from localhost:3000/api to localhost:4000, and then use localhost:3000/api to access the API instead of localhost:4000.

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

Best way for production deployment is to do it on your web server (nginx/apache).

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

You can also do it in node via express and request modules, or use some ready made middleware like this one:

您也可以通过快速和请求模块在节点中执行此操作,或者使用一些现成的中间件,如下所示:

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

Solution with this middleware is pretty straightforward:

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

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

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

#2


0  

If you want to use sessions (ie. instead of jwt, etc) I think by default they are just in-memory so it will not work as your application scales to multiple hosts. It is easy to configure them to persist though.

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

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

#3


0  

You might have tried with passport-jwt. It generates tokens as per the JWT protocol on login. Your requirement is to blacklist the generated token when you logout. To achieve that, you can create a collection in mongodb named "BlacklistToken" with fields userid and token. When the user logs out, you can insert the token and userid in the collection. Then write a middleware to check whether the token is blacklisted or not. if it is redirect to login page.

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

#4


0  

did you already take a look here:

你有没有看过这里:

In this case, responses can be sent back based on some considerations.

在这种情况下,可以基于某些考虑发回响应。

If the resource in question is meant to be widely accessed (just like any HTTP resource accessed by GET), then sending back the Access-Control-Allow-Origin: * header will be sufficient,[...]

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

You may try this (allow any public IP) :

你可以尝试这个(允许任何公共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');

It is normal that the second server re-create a new session, because assuming that you use Express-session, and according to the documentation:

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

Session data is not saved in the cookie itself, just the session ID. Session data is stored server-side.

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

Which mean that you need to find a way to synchronize servers session data ... Assuming that you find a method to do that, when you will try to connect, both server will retrieve the same user session data and the second will not have to create a new session...

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

#5


0  

If I understand the problem correctly here, you want the user's session to be stateless on the server. So that whenever the user logs in, the session can be re-used in any instance of the server when you scale your application, or even if you were to just reboot your application.

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

To achieve this, what you need is to configure the express-session with a database solution. You can do this with mongo using this package https://github.com/jdesboeufs/connect-mongo.

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

However, best practice is to use something a bit more robust for this sort of use-case, like redis using this package https://github.com/tj/connect-redis.

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

#1


1  

One possible solution to get around CORS/cookie/same-domain problems is to create proxy server that will mirror all requests from localhost:3000/api to localhost:4000, and then use localhost:3000/api to access the API instead of localhost:4000.

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

Best way for production deployment is to do it on your web server (nginx/apache).

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

You can also do it in node via express and request modules, or use some ready made middleware like this one:

您也可以通过快速和请求模块在节点中执行此操作,或者使用一些现成的中间件,如下所示:

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

Solution with this middleware is pretty straightforward:

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

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

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

#2


0  

If you want to use sessions (ie. instead of jwt, etc) I think by default they are just in-memory so it will not work as your application scales to multiple hosts. It is easy to configure them to persist though.

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

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

#3


0  

You might have tried with passport-jwt. It generates tokens as per the JWT protocol on login. Your requirement is to blacklist the generated token when you logout. To achieve that, you can create a collection in mongodb named "BlacklistToken" with fields userid and token. When the user logs out, you can insert the token and userid in the collection. Then write a middleware to check whether the token is blacklisted or not. if it is redirect to login page.

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

#4


0  

did you already take a look here:

你有没有看过这里:

In this case, responses can be sent back based on some considerations.

在这种情况下,可以基于某些考虑发回响应。

If the resource in question is meant to be widely accessed (just like any HTTP resource accessed by GET), then sending back the Access-Control-Allow-Origin: * header will be sufficient,[...]

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

You may try this (allow any public IP) :

你可以尝试这个(允许任何公共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');

It is normal that the second server re-create a new session, because assuming that you use Express-session, and according to the documentation:

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

Session data is not saved in the cookie itself, just the session ID. Session data is stored server-side.

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

Which mean that you need to find a way to synchronize servers session data ... Assuming that you find a method to do that, when you will try to connect, both server will retrieve the same user session data and the second will not have to create a new session...

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

#5


0  

If I understand the problem correctly here, you want the user's session to be stateless on the server. So that whenever the user logs in, the session can be re-used in any instance of the server when you scale your application, or even if you were to just reboot your application.

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

To achieve this, what you need is to configure the express-session with a database solution. You can do this with mongo using this package https://github.com/jdesboeufs/connect-mongo.

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

However, best practice is to use something a bit more robust for this sort of use-case, like redis using this package https://github.com/tj/connect-redis.

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