我使用快速会话模块来处理我的node.js用户会话。 默认情况下,它允许每个用户多个会话。我需要限制每个用户一个会话。我来到以下解决方案:在redis中存储user_id:session_id对,当用户登录时检查该user_id的会话是否存在并删除它然后创建一个新的并将其保存到redis。一切都很好,直到我试图用围攻来测试我的服务器。我同时模拟了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;
答案 0 :(得分:0)
我无法找到某些会话未被删除的确切原因,但在经过大量调试和日志调查后,我认为这是由于节点异步性质。虽然mySQL获取操作需要一些时间,但某些登录操作可以并行运行并为当前用户session_id获取相同的值。 为了解决这个问题,我创建了中间件来检查当前用户会话ID是否在redis存储中,如果不是 - 它只是破坏会话并注销用户要求新的登录尝试。这可能不是一个好的解决方案,但它完全解决了原始问题。