Node.js express-session + redis单实例问题

node.js express-session + redis single instance issue

本文关键字:实例 问题 单实例 js express-session redis Node      更新时间:2023-09-26

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

允许一个用户有多个会话。我做错了什么?请在下面找到一些代码。

 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 provider 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;

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