使express.js中的所有单个用户的会话无效

时间:2014-10-10 15:41:29

标签: node.js session express

出于安全原因,我们希望能够使用户的所有活动会话无效,例如,如果他们更改了密码,或者只是希望能够强制注销其他会话。我们正在使用Node.js,Express,express-sessions和Redis会话存储。在我们的应用程序中,我们有(CoffeeScript):

app.use express.session
    cookie:
        maxAge: 5 * 24 * 60 * 60 * 1000 # 5 days in ms
    store: new RedisStore(client: rclient)
    key: "secret-key"

Redis商店的工作原理是将唯一会话ID映射到您在会话中存储的任何数据。例如:

# In an HTTP request
req.session.user = { _id: "user-id" }
Redis中的

成为:

> get "sess:<session-id>"
'{ "user": { "_id": "user-id" } } '

我们需要的是一种跟踪与每个用户ID相对应的所有会话的方法,这样如果我们想要使用户的会话无效,我们就可以从Redis中删除这些会话。以下警告适用:

  1. 会话在Redis中给出一个等于maxAge的TTL 曲奇饼。每个会话的跟踪机制也应该过期 在此之后,以避免陈旧数据。
  2. 并非所有会话都必须与用户相关联。有些仅用于跟踪匿名会话详细信息。
  3. 在redis中添加另一个反向查找键的简单方法(例如,将user_id映射到用户的一组会话ID)在使用警告(1)时失败。

    这感觉就像使用Express的其他网站必须遇到的问题一样,因为它是一种非常常见的安全模式。有没有人对如何跟踪用户会话有任何建议,然后根据需要使它们失效?

    谢谢!

1 个答案:

答案 0 :(得分:5)

我为类似情况所做的是使用包含密钥名称中的用户ID的自定义会话ID。也许现在有一种更简单的方法,但基本上我必须做的就是设置自定义会话ID:

main.js:

var uid = require('uid'),
    redis = require('redis'),
    session = require('express-session'),
    RedisStore = require('connect-redis')(session),
    Session = session.Session,
    Cookie = session.Cookie;

var utils = require('./utils');

var COOKIE_SECRET = 'somethingrandom',
    COOKIE_KEY = 'mycustomsession';

var redisClient = redis.createClient(),
    redisStore = new RedisStore({
      client: redisClient,
      ttl: 24 * 60 * 60, // 1 day session expiration
      prefix: 'sess:'
    });

// ... then inside the login route after user was successfully authenticated ...
req.sessionStore = redisStore;
req.sessionID = 'sess:' + user.id + ':' + uid(24);
req.session = new Session(req);
req.session.cookie = new Cookie({});
req.session.user = user;
utils.createSession(req, res, COOKIE_KEY, COOKIE_SECRET);

utils.js:

var onHeaders = require('on-headers'),
    signature = require('cookie-signature');
exports.createSession = function(req, res, name, secret) {
  var trustProxy = true;
  // ripped from express-session
  onHeaders(res, function() {
    if (!req.session)
      return;

    var cookie = req.session.cookie
      , proto = (req.headers['x-forwarded-proto'] || '').split(',')[0].toLowerCase().trim()
      , tls = req.connection.encrypted || (trustProxy && 'https' == proto);

    // only send secure cookies via https
    if (cookie.secure && !tls)
      return;

    var val = 's:' + signature.sign(req.sessionID, secret);
    res.cookie(name, val, cookie.data);
  });

  // proxy end() to commit the session
  var end = res.end;
  res.end = function(data, encoding) {
    res.end = end;
    if (!req.session) return res.end(data, encoding);
    req.session.resetMaxAge();
    req.session.save(function(err) {
      if (err) console.error(err.stack);
      res.end(data, encoding);
    });
  };
};