【问题标题】:Prevent Expressjs from creating a session when requests contain an authorization header?当请求包含授权标头时,防止 Expressjs 创建会话?
【发布时间】:2014-02-11 10:44:14
【问题描述】:

我有一个 API,可以使用浏览器调用,其中请求是事务性的并且有会话,或者直接调用,例如。使用 curl,其中请求是原子的。浏览器请求必须首先进行身份验证,然后使用快速会话 (connect.sid) 进行后续授权,直接 API 调用使用标头:Authorization: "SOMETOKEN",每个请求都必须发送该标头。

我遇到的问题是,因为我使用同一个 Web 服务器来提供原子流量和事务流量,所以 Express 不必要地为每个 API 调用提供了一个会话。每个响应都包含一个 Set-Cookie,所有这些会话都填满了我的会话存储。因此:当请求包含 Authorization 标头时,如何防止 Express 在内存存储 (Redis) 中输入新的 sess key?

注意。我知道一个更经典的方法是拥有一个单独的 API 服务器和一个单独的 WEB 服务器,但为什么不在一台机器上同时运行呢?对我来说,区别在于 API 服务于数据,而 WEB 服务于视图,但除此之外,它们都是同一个应用程序的一部分。我只是碰巧还允许用户直接访问他们的数据,而不是强迫他们使用我的界面。

快速配置

module.exports = function(app, exp, sessionStore, cookieParser, passport, flash) {

    app.configure(function(){
        // Templates
        app.set('views', ERNEST.root + '/server/views');
        app.set('view engine', 'jade');
        app.set('view options', { doctype : 'html', pretty : true });

        // Allow large files to be uploaded (default limit is 100mb)
        app.use(exp.limit('1000mb'));

        // Faux putting and deleting
        app.use(exp.methodOverride());

        // Static content
        app.use(exp.static(ERNEST.root + '/server'));
        app.use(exp.static(ERNEST.root + '/public'));

        // Handle favicon
        app.use(exp.favicon());

        // For uploads
        app.use(exp.bodyParser({keepExtensions: true}));

        // Configure cookie parsing
        if ( cookieParser ) app.use(cookieParser);
        else app.use(exp.cookieParser());

        // Where to store the session
        var session_options = { 'secret': "and she put them on the mantlepiece" };
        if ( sessionStore ) session_options.store = sessionStore;
        app.use(exp.session( session_options ));

        // Rememberance
        app.use( function (req, res, next) {
            if ( req.method == 'POST' && req.url == '/authenticate' ) {
                if ( req.body.rememberme === 'on' ) {
                    req.session.cookie.maxAge = 2592000000; // 30*24*60*60*1000 Rememeber 'me' for 30 days
                } else {
                    req.session.cookie.expires = false;
                }
            }
            next();
        });

        // PassportJS
        if ( passport ){
            app.use(flash());
            app.use(passport.initialize());
            app.use(passport.session());
        }
    });

};

示例路线

app.get('/status/past_week', MID.ensureAuthenticated, MID.markStart, function(req, res) {
    WEB.getStatus('week', function(err, statuses){
        if ( err ) res.send(500, err);
        else res.send(200, statuses);
    });
});

授权中间件

MID.ensureAuthenticated = function(req, res, next) {
  if ( req.isAuthenticated() ) return next();
  else {
        isAuthorised(req, function(err, authorised){
            if ( err ) return res.redirect('/');
            else if ( authorised ) return next();
            else return res.redirect('/');
        });
    }

    function isAuthorised(req, callback){
        var authHeader = req.headers.authorization;
        if ( authHeader ) {
            // Has header, verify it
            var unencoded = new Buffer(authHeader, 'base64').toString();
            var formatted = unencoded.toString().trim();
            ACCOUNT.verifyAuth(formatted, callback); // verifyAuth callbacks next() when successful
        } else callback(null, false); // No Authorised header
    }
};

【问题讨论】:

    标签: node.js express connect passport.js


    【解决方案1】:

    您总是可以捕获响应标头事件并删除 'set-cookie' 标头:

    app.use(function(req, res, next) {
      res.on('header', function () {
        if (req.headers.authorization) {
          delete res._headers['set-cookie'];
        }
      });
      next();
    });
    

    从技术上讲,您可以将它放在中间件链中的任何位置。

    【讨论】:

    • 公平地说,您已经直接回答了这个问题,但这不起作用。它确实成功删除了 set-cookie 标头,但这不是我真正的问题。我真正的问题是在我的内存存储(Redis)中创建的所有 sess 键,每个请求一个 - 这很快就会消耗内存。我会更新问题以反映这一点。
    • 使用这种检查每个请求的方法,一个选项是使用req.session.destroy(); 在 Express 设置会话后立即删除它。不过,我宁愿阻止 Express 首先设置它。
    【解决方案2】:

    看来您需要编写自己的会话中间件。 Here's an example。如果您可以创建一个单独的子域,例如,www.example.com 用于浏览器会话,而 app.example.com 用于直接访问它,那么您应该能够几乎完全使用链接方法,只是不要启动会话对于 app.example.com 请求。这可能是最直接的方法,调用指示它打算通过哪种方法进行身份验证,任何偏离该方法的方法都是错误的。

    否则,您必须在中间件中检测身份验证令牌,并且在找到时不启动会话。

    【讨论】:

    • 谢谢,您使用单独子域的建议很有意义;我怀疑我们最终会这样做,然后您的链接将很有用。
    【解决方案3】:

    试试这个:

    var sessionMiddleware = exp.session( session_options );
    
    app.use(function(req, res, next) {
      if (req.headers.authorization) {
        return next();
      }
      return sessionMiddleware(req, res, next);
    });
    

    【讨论】:

    • 谢谢。这正是我想要的。
    【解决方案4】:

    减少会话存储中存储的会话数量的另一种方法是将默认 maxAge 设置为较低的值。然后,当您确实需要存储更长时间的会话时,例如用户登录后,您可以设置req.session.cookie.expires = null;。另外不要忘记在用户注销时将会话过期设置为较低的值。

    这是一个例子:

    // set default to something low
    app.use(session({
      resave: true,
      saveUninitialized: true,
      cookie: {
        maxAge: 5 * 60 * 1000 // 5 minutes
      },
      secret: secrets.sessionSecret,
      store: new MongoStore({
        url: yourUrl,
        auto_reconnect: true
      })
    }));
    
    // on successful login, 
    // set expiration to null or something longer than default
    var time = 14 * 24 * 3600000; //2 weeks
    req.session.cookie.maxAge = time;
    req.session.cookie.expires = new Date(Date.now() + time);
    req.session.touch();
    
    // on logout, reset expiration to something low  
    var time = 5 * 60 * 1000; // 5 minutes
    req.session.cookie.maxAge = time; //2 weeks
    req.session.cookie.expires = new Date(Date.now() + time);
    req.session.touch();
    

    这在远程监控您的应用时特别有用,因为如果监控足够频繁,会话将很快填满。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-28
      • 2021-08-28
      • 2012-08-12
      • 1970-01-01
      • 2015-10-16
      • 2017-04-18
      • 2012-09-02
      • 2019-07-15
      相关资源
      最近更新 更多