我正在 node.js 中实现一个身份验证系统,该系统由用户的 redis 数据库和用于持久、可扩展 session 存储的 connect-redis 支持。
这是我的应用程序的核心,服务器:
// Module Dependencies
var express = require('express');
var redis = require('redis');
var client = redis.createClient();
var RedisStore = require('connect-redis')(express);
var crypto = require('crypto');
var app = module.exports = express.createServer();
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({ secret: 'obqc487yusyfcbjgahkwfet73asdlkfyuga9r3a4', store: new RedisStore }));
app.use(require('stylus').middleware({ src: __dirname + '/public' }));
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
// Message Helper
app.dynamicHelpers({
// Index Alerts
indexMessage: function(req){
var msg = req.sessionStore.indexMessage;
if (msg) return '<p class="message">' + msg + '</p>';
},
// Login Alerts
loginMessage: function(req){
var err = req.sessionStore.loginError;
var msg = req.sessionStore.loginSuccess;
delete req.sessionStore.loginError;
delete req.sessionStore.loginSuccess;
if (err) return '<p class="error">' + err + '</p>';
if (msg) return '<p class="success">' + msg + '</p>';
},
// Register Alerts
registerMessage: function(req){
var err = req.sessionStore.registerError;
var msg = req.sessionStore.registerSuccess;
delete req.sessionStore.registerError;
delete req.sessionStore.registerSuccess;
if (err) return '<p class="error">' + err + '</p>';
if (msg) return '<p class="success">' + msg + '</p>';
},
// Session Access
sessionStore: function(req, res){
return req.sessionStore;
}
});
// Salt Generator
function generateSalt(){
var text = "";
var possible= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*"
for(var i = 0; i < 40; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
// Generate Hash
function hash(msg, key){
return crypto.createHmac('sha256', key).update(msg).digest('hex');
}
// Authenticate
function authenticate(username, pass, fn){
client.get('username:' + username + ':uid', function(err, uid){
if (uid !== null){
client.hgetall('uid:' + uid, function(err, user){
if (user.pass == hash(pass, user.salt)){
return fn(null, user);
}
else{
fn(new Error('invalid password'));
}
});
}
else{
return fn(new Error('cannot find user'));
}
});
}
function restrict(req, res, next){
if (req.sessionStore.user) {
next();
} else {
req.sessionStore.loginError = 'Access denied!';
res.redirect('/login');
}
}
function accessLogger(req, res, next) {
console.log('/restricted accessed by %s', req.sessionStore.user.username);
next();
}
// Routes
app.get('/', function(req, res){
res.render('index', {
title: 'TileTabs'
});
});
app.get('/restricted', restrict, accessLogger, function(req, res){
res.render('restricted', {
title: 'Restricted Section'
});
});
app.get('/logout', function(req, res){
req.sessionStore.destroy(function(err){
if (err){
console.log('Error destroying session...');
}
else{
console.log(req.sessionStore.user.username + ' has logged out.');
res.redirect('home');
}
});
});
app.get('/login', function(req, res){
res.render('login', {
title: 'TileTabs Login'
});
});
app.post('/login', function(req, res){
var usernameLength = req.body.username.length;
var passwordLength = req.body.password.length;
if (usernameLength == 0 && passwordLength == 0){
req.sessionStore.loginError = 'Authentication failed, please enter a username and password!';
res.redirect('back');
}
else{
authenticate(req.body.username, req.body.password, function(err, user){
if (user) {
req.session.regenerate(function(){
req.sessionStore.user = user;
req.sessionStore.indexMessage = 'Authenticated as ' + req.sessionStore.user.name + '. Click to <a href="/logout">logout</a>. ' + ' You may now access <a href="/restricted">the restricted section</a>.';
console.log(req.sessionStore.user.username + ' logged in!');
res.redirect('home');
});
} else {
req.sessionStore.loginError = 'Authentication failed, please check your username and password.';
res.redirect('back');
}
});
}
});
app.get('/register', function(req, res){
res.render('register', {
title: 'TileTabs Register'
});
});
app.post('/register', function(req, res){
var name = req.body.name;
var username = req.body.username;
var password = req.body.password;
var salt = generateSalt();
if (name.length == 0 && username.length == 0 && password.length == 0){
req.sessionStore.registerError = 'Registration failed, please enter a name, username and password!';
res.redirect('back');
}
else{
client.get('username:' + username + ':uid', function(err, uid){
if (uid !== null){
req.sessionStore.registerError = 'Registration failed, ' + username + ' already taken.';
res.redirect('back');
}
else{
client.incr('global:nextUserId', function(err, uid){
client.set('username:' + username + ':uid', uid);
client.hmset('uid:' + uid, {
name: name,
username: username,
salt: salt,
pass: hash(password, salt)
}, function(){
req.sessionStore.loginSuccess = 'Thanks for registering! Try logging in!';
console.log(username + ' has registered!');
res.redirect('/login');
});
});
}
});
}
});
// Only listen on $ node app.js
if (!module.parent) {
app.listen(80);
console.log("Express server listening on port %d", app.address().port);
}
注册和登录身份验证工作得很好,但由于某种原因,当连接的用户尝试注销时,我遇到了问题。
正如您从我的 /logout
route 看到的,
app.get('/logout', function(req, res){
req.sessionStore.destroy(function(err){
if (err){
console.log('Error destroying session...');
}
else{
console.log(req.sessionStore.user.username + ' has logged out.');
res.redirect('home');
}
});
});
我有两个 console.log
来尝试确定卡住发生的位置。有趣的是,没有记录任何内容。
因此,无论出于何种原因,destroy()
都没有被正确调用。
我不确定我是否只是在语法上犯了错误,或者什么,但根据 connect documentation 看来我的设置是正确的。
最佳答案
您必须使用 req.session 而不是 req.sessionStore。 req.sessionStore 是单个 RedisStore 实例,不会通过 connect 动态设置。这意味着您的代码仅适用于一个用户。您的用户将通过这种方式共享相同的 session 数据。
req.sessionStore 也有一个 destroy 方法,这就是为什么你没有收到任何错误。您的回调未被调用,因为在此方法中回调是第二个参数。
只需在所有代码中将 req.sessionStore 替换为 req.session 即可。例如:
req.session.destroy(function(err) { ... });
关于session - connect-redis session 销毁时卡住?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6921231/