【问题标题】:Express-session not persistent after redirect重定向后快速会话不持久
【发布时间】:2018-09-03 16:33:36
【问题描述】:

昨天,我尝试做一个网站,使用 Node.js 和框架 Express.js。在该网站上,用户需要使用登录到数据库的凭据进行登录。如果凭据正确,我想将用户信息保存在会话中。

为了这个目的,我使用了中间件 Express-Session,但是我有一个问题。当用户键入良好的凭据时,其信息会正确存储在会话中。但是,将用户重定向到主页后,会话被清除,因此存储用户信息的变量现在未定义。

我尝试了很多解决方案,我搜索了很多,但我没有解决这个问题......

有我的代码: app.js:

const createError = require('http-errors');
const express = require('express');
const session = require('express-session');
const path = require('path');
const helmet = require('helmet');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const bodyParser = require('body-parser');

const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const admRouter = require('./routes/adm');

const urlencodedParser = bodyParser.urlencoded({extended : false});

const app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(urlencodedParser);
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({secret: 'secret', resave: false, saveUninitialized: false, cookie: { maxAge : 60000, secure: false }}));

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/adm', admRouter);

// On utilise helmet pour sécuriser l'application.
app.use(helmet());

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    next(createError(404));
});

// 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
    res.status(err.status || 500);
    res.render('error');
});

module.exports = app;

app.listen(80);

还有 index.js:

const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const verif = require('../functions/verif');
const password = require('node-php-password');

const dbServer = require('../database');
// const credentials = require('../functions/dbCredentials');

// const dbServer = mysql.createConnection(credentials);

const urlencodedParser = bodyParser.urlencoded({extended : false});

/* GET home page. */
router.get('/', function(req, res, next) {
    console.log(JSON.stringify(req.session.user));
    res.render('index', {verif, req });
});

router.post('/login', urlencodedParser, (req, res, next) => {
    if(req.body.username !== undefined && req.body.password !== undefined) {
        if(req.body.username !== null && req.body.password !== null) {
            dbServer.query('SELECT * FROM users WHERE username = ?', [req.body.username], function(error, result, fields) {
                if (error) throw error;
                // if(password.verify(req.body.password, result))
                console.log("resultat : " + JSON.stringify(result));
                if(result.length > 0) {
                    const utilisateur = result[0]; // On stocke la ligne concernant l'utilisateur dans une constante locale.
                    console.log("L'utilisateur existe.");
                    // On teste le résultat obtenu, pour savoir si son mot de passe est correct.
                    if(password.verify(req.body.password, utilisateur.password)) {
                        console.log("Mot de passe correct.");
                        req.session.user = utilisateur;
                        console.log(req.session.user);
                    } else {
                        // TODO : Session, pour afficher l'erreur.
                        console.log("Mot de passe incorrect.");
                    }
                }
                else {
                    console.log("L'utilisateur n'existe pas.")
                    // TODO : Session, pour afficher l'erreur.
                }
            });
        }
    }
    res.redirect('/');
});

module.exports = router;

使用此代码,当用户登录时,:

console.log(req.session.user);

显示正确的信息,但行:

console.log(JSON.stringify(req.session.user));

对于路线“/”显示“未定义”。

所以,我在这种情况下有点迷失...... 你有解决这个问题的想法吗? 提前致谢 =)

【问题讨论】:

  • 我不知道这是否是唯一的问题,但您的重定向是在您完成所有会话内容之前发生的。根据您的会话设置,您可能还需要.save() 会话更改。
  • 谢谢,但是即使我删除重定向,问题仍然存在^^我尝试了.save(),但它没有任何效果......
  • 您的症状听起来确实像是您没有成功保存对会话对象的更改。

标签: javascript node.js express express-session


【解决方案1】:

问题是,您将它们重定向得太早了。数据库查询比服务器中运行的大多数代码花费的时间要长得多,这就是使用回调和承诺的原因。我会在您的数据库查询末尾移动res.redirect('/')。在最后一个 else 语句之后。这应该可以修复它,因为它允许在重定向之前运行所有内容。

【讨论】:

【解决方案2】:

您正在使用 express-session 的隐式内存存储。他们建议不要在生产中使用它,但是我的经验是,即使在简单的开发环境中,它也完全不稳定。我尝试在超时setTimeout(() => res.redirect('/'), 1000) 中重定向,从统计上看,如果我直接调用它,它在更少的情况下失败,尽管这远非理想。

在我目前的情况下,我最终实现了自己的哑内存存储,只是为了让事情向前发展,稍后将切换到数据库后端。

后期编辑:我做了一些广泛的测试,在我的情况下,设置会话 cookie 和重定向之间可能存在一些竞争条件,我认为有时浏览器会在设置之前重定向sid的事情。我设法通过在我的操作过程中记录每个步骤的活动会话来发现这一点,并且似乎在没有明显原因的情况下在重定向上创建了新会话。

【讨论】:

  • 完美地帮助我理解出了什么问题。谢谢
【解决方案3】:

我遇到了这个问题,并通过 express-session 自述文件给出的提示解决了它。

如果您查看 readme.md 的 https://github.com/expressjs/session#cookiesecure 部分,您会注意到以下声明:

请注意,secure: true 是推荐的选项。但是,它需要启用 https 的网站,即安全 cookie 需要 HTTPS。 如果设置了安全,并且您通过 HTTP 访问您的网站,则不会设置 cookie。

因此,我尝试了以下操作:

const sess = {
    secret: 'My app',
    store: MongoStore.create({ 
        mongoUrl: 'mongodb://localhost:27017/myBaseApp',
        crypto: {
            secret: 'My database'
        }
    }),
    resave: true,
    saveUninitialized: false,
    cookie: { 
        secure: false,            //setting this false for http connections
        maxAge: 3600000,
        expires: new Date(Date.now() + 3600000) 
    }
}

重定向开始正常工作。

【讨论】:

    【解决方案4】:

    几乎所有具有回调的东西都是异步的,这要求您的脚本在发送信息/继续其他可能创建所谓竞争条件的行为之前等待回复。

    正如其他答案所建议的,这里是一个如何在成功/失败时重定向的示例:

    router.post('/login', urlencodedParser, (req, res) => {
        // only in some cases
        let sendRedirectDirectly = true;
    
        if(req.body.username !== undefined && req.body.password !== undefined) {
            if(req.body.username !== null && req.body.password !== null) {
    
                // here you need to wait for the result of the query 
                sendRedirectDirectly = false; 
    
    
                dbServer.query('SELECT * FROM users WHERE username = ?', [req.body.username], function(error, result, fields) {
                    if (error) throw error;
                    // if(password.verify(req.body.password, result))
                    console.log("resultat : " + JSON.stringify(result));
                    if(result.length > 0) {
                        const utilisateur = result[0]; // On stocke la ligne concernant l'utilisateur dans une constante locale.
                        console.log("L'utilisateur existe.");
                        // On teste le résultat obtenu, pour savoir si son mot de passe est correct.
                        if(password.verify(req.body.password, utilisateur.password)) {
                            console.log("Mot de passe correct.");
                            req.session.user = utilisateur;
                            console.log(req.session.user);
    
    
                            // now we need to call next() or forward the user again
                            res.redirect('/?loginSuccess');
                        } else {
                            // TODO : Session, pour afficher l'erreur.
                            console.log("Mot de passe incorrect.");
    
                            // if password is incorrect
                            res.redirect('/?incorrectPass');
                        }
                    }
                    else {
                        console.log("L'utilisateur n'existe pas.")
                        // TODO : Session, pour afficher l'erreur.
    
                        // if username not found:
                        res.redirect('/?incorrectCredentials');
                    }
                });
            }
        }
    
        // as noted above
        if (sendRedirectDirectly) 
           res.redirect('/');
    });
    

    为了更好地理解,一些包具有更好的async 支持,并允许编写如下示例中的代码。 (以 mssql 包为例)

    async/await 函数/语句会暂停脚本的执行,直到满足 Promiseawait 并抛出可由 try/catch-block 处理的可捕获错误。

    如果一个函数被声明为async,它总是返回一个Promise

    您还可以将 mysql-query 包装到 Promise 中:

    function mysqlQuery(...args) {
       return new Promise((resolve, reject) => {
          dbServer.query(...args, (error, result, fields) => {
             if (error)
               return reject(error);
             resolve([result, fields]);
          })
       })
    }
    

    现在你可以使用了:

    router.post('/login', urlencodedParser, async (req, res) => {
    // add some checks for null/undefined
      try {
        let [ result, fields ] = await mysqlQuery('SELECT * FROM users WHERE username = ?', [req.body.username]);
        if (result.length) {
          // test the password?
        } else {
          // invalid username
        }
      } catch (e) {
        console.error(e);
      }
      // with async behavior you can send it always at the end
      res.redirect('/');
    })
    

    【讨论】:

      猜你喜欢
      • 2012-11-05
      • 1970-01-01
      • 2016-05-06
      • 2019-10-13
      • 2021-05-09
      • 2022-11-27
      • 2016-04-10
      • 2021-08-22
      • 2014-08-17
      相关资源
      最近更新 更多