javascript - Node.js Express-Session + Redis 单实例问题

标签 javascript node.js session authentication promise

我正在使用express-session模块来处理我的node.js用户 session 。 默认情况下,它允许每个用户多个 session 。我需要限制每个用户一次 session 。我找到了以下解决方案:将 user_id:session_id 对存储在 redis 中,当用户登录时检查该 user_id 的 session 是否存在并将其删除,然后创建一个新 session 并将其保存到 redis。一切都工作得很好,直到我尝试使用 siege 对我的服务器进行压力测试。我模拟了同时 1000 次登录尝试,我发现一些 session 没有被清除,仍然在 Redis 存储中。

这允许一个用户拥有多个 session 。我究竟做错了什么? 请在下面找到一些代码。

 var FileStreamRotator = require('file-stream-rotator'),
    app = require('express')(),
    fs = require("fs"),
    bodyParser = require('body-parser'),
    config = require("./providers/config"),
    morgan = require('morgan'), //HTTP request logger middleware for node.js
    cookieParser = require('cookie-parser'),
    redis = require('redis'),
    session = require('express-session'),
    redisStore = require('connect-redis')(session),
    publicRouter = require('./routes/public.js')();

var port = process.env.PORT || config.port; 
var client = redis.createClient();

app.disable('x-powered-by'); 

app.use(cookieParser(config.session.secret));
app.use(session(
    {
        secret: config.session.secret,
        store: new redisStore({host: config.redis.host, port: config.redis.port, client: client}),
        saveUninitialized: false, // don't create session until something stored,
        resave: false // don't save session if unmodified
    }
));

app.use(morgan('combined', {stream: accessLogStream}));
app.use(bodyParser.urlencoded({extended: true})); 
app.use(bodyParser.json());


*****
app.all('/api/*', [require('./middlewares/validateRequest')]);

******
app.use('/api/public', publicRouter); 
******

app.listen(port, function (err) {
    if (!err) console.log('Find me on port ' + port + ' and say "Hello"');
    else console.log(err);
});

auth.js

var User = require('./../models/user.js');
var Promise = require('bluebird');
var redis = require("./../providers/redis.js");
var util = require('util');

var auth = {
    login: function (req, res) {
        var login = req.body.login || '';
        var password = req.body.password || '';

        if (login === '') {
            res.status(401);
            res.json({
                "status": 401,
                "message": "login required"
            });
            return;
        }
        if (password === '') {
            res.status(401);
            res.json({
                "status": 401,
                "message": "password required"
            });
            return;
        }
        User.login(login, password)
            .then(function (user) {
                if (!user) {
                    res.status(401);
                    res.json({
                        "status": 401,
                        "message": "Incorrect login data."
                    });
                }

                return redis.get(util.format("usersess:%s", user.id))
                    .then(function (currentSession) {
                        if (currentSession === null) {
                            redis.set(util.format("usersess:%s", user.id), req.session.id)
                                .then(function () {
                                    delete user.password;
                                    req.session.user = user;
                                    res.json({
                                        "status": 200,
                                        "message": "User successfully logged in."
                                    });
                                });
                        } else {
                            if (currentSession !== req.session.id) {
                                return redis.del(util.format("sess:%s", currentSession))
                                    .then(function () {
                                        return redis.set(util.format("usersess:%s", user.id), req.session.id);
                                    })
                                    .then(function () {
                                        delete user.password;
                                        req.session.user = user;
                                            res.json({
                                                "status": 200,
                                                "message": "User successfully logged in."
                                            });
                                    })
                            } else {
                                res.json({
                                    "status": 200,
                                    "message": "User successfully logged in."
                                });
                            }
                        }
                    })
            })
            .catch(function (err) {
                console.log(err);
                res.status(500);
                res.json({
                    error: true,
                    data: {
                        message: err.message
                    }
                });
            });
    },


    logout: function (req, res) {
        req.session.destroy(function (err) {
            if (err) {
                console.log("Can't destroy the session. See details below");
                console.log(err);
                res.status(500);
                res.json({
                    "status": 500,
                    "message": err.message
                })
            } else {
                res.status(200);
                res.json({
                    "status": 200,
                    "message": "User successfully logged out."
                })
            }
        });
    }
};

module.exports = auth;

用户模型user.js

 var Promise = require('bluebird'),
    bcrypt = Promise.promisifyAll(require('bcrypt')),
    db = require("./../providers/db.js");


var User = {
    tableName: 'users',

    login: function (login, password) {
        if (!login || !password) throw new Error('login and password are both required');
        return db.execStoredProcedure("user_get_by_login", [login.trim()])
            .then(
            function (rows) {
                var user = rows[0][0];
                return bcrypt.compareAsync(password, user.password)
                    .then(function (res) {
                        if (!res) user = null;
                        return user;
                    });
            }
        );
    }
};

module.exports = User;

redis 提供者 redis.js

var config = require('./../providers/config');
var Promise = require("bluebird"),
    redis = require('promise-redis')(function(resolver) {
        return new Promise(resolver);
    }),
    redisClient = redis.createClient(config.redis.port, config.redis.host),
    util = require('util');

redisClient.on('connect', function () {
    console.log(util.format('redis connected on %s:%s', config.redis.host, config.redis.port));
});

module.exports = redisClient;

最佳答案

我无法找到某些 session 未被删除的确切原因,但经过大量调试和日志调查后,我认为这是由于 Node 异步性质所致。虽然 mySQL 获取操作需要一些时间,但某些登录操作可以并行运行并为当前用户 session_id 获取相同的值。 为了解决这个问题,我创建了中间件来检查当前用户 session ID 是否在 Redis 存储中,如果不在,它只会销毁 session 并注销用户,要求重新登录。这可能不是一个好的解决方案,但它完全解决了原来的问题。

关于javascript - Node.js Express-Session + Redis 单实例问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31635744/

相关文章:

JavaScript 无法引用该对象

java - 预登录 session ID?

node.js - 如何高效分离路由代码

java - 如何正确地使 JSP session 失效?

python - 无法在 Flask 单元测试中设置 session 变量

javascript - 为什么 fetch 返回我的 React 应用程序的 index.html 而不是我提供的 URL?

javascript - 在 jQuery 中,使用 $.extend(),这是多余的吗?

javascript - 在下面的链接中,为什么他们在javascript步骤中的slides[slideIndex-1].style.display = "-1"处添加 "block"?

javascript - async/await : asynchronous array transformation using Array.prototype.map(有效)与Array.prototype.filter(无效)

node.js - 咕噜声错误 : Cannot find module 'time-grunt'