在Express应用程序中验证Socket.IO客户端

时间:2014-03-12 16:29:19

标签: node.js authentication socket.io passport.js

我启动并运行了一个Express应用程序,使用Passport.js(带有MongoDB后端)进行身份验证。

一切都很好,但现在我面临另一个挑战:

我想在我的项目中使用某种“聊天”功能,为此我正在使用Socket.IO(实时传递消息)。

该页面会在加载前验证用户是否已登录,但仍可以绕过该页面。

我希望Socket.IO流也受到保护和授权。

如何将Socket.IO聊天系统集成到基于Passport.js的身份验证?

2 个答案:

答案 0 :(得分:3)

我不确定护照究竟是如何工作的,但假设它在客户端设置了一个cookie,其会话ID就是以下解决方案应该有效。看起来很糟糕,但我还没有看到更优雅的解决方案。

var connect = require('express/node_modules/connect'),
    parseSignedCookie = connect.utils.parseSignedCookie,
    Cookie = require('express/node_modules/cookie'),
    store = YOUR_SESSION_STORE_INSTANCE, // i.e. redis-store, memory, or whatever 
    sessionKey = "YOUR SESSION KEY", // defaults to connect.sid
    sessionSecret = "YOUR SESSION SECRET";

var verifyCookie = function(data, callback){
    try{
        var cookie = Cookie.parse(data.headers.cookie);
        var sessionID = parseSignedCookie(cookie[sessionKey], sessionSecret);
        store.get(sessionID, callback);
    }catch(e){
        callback(e);
    }
};

// set up socket.io to validate cookies on an authorization request
// this assumes you've assigned your socket.io server to the io variable
io.configure(function(){
    io.set("authorization", function(handshake, accept){
        if(handshake.headers.cookie){
            verifyCookie(handshake, function(error, session){
                if(error || !session)
                    accept("Invalid authentication", false);
                else
                    accept(null, true);
            });
        }else{
            accept("Invalid authentication", false);
        }        
    });
});

我不是100%肯定,但如果这不能正确接听会话,您可能需要将上面使用的会话存储更改为护照会话存储而不是默认快递存储或者你正在使用的任何东西。

希望有所帮助。如果这样做或者您找到了正确的解决方案,请发布您的调查结果,因为我们将来可能会转移到护照,并且知道如何修补我们现有的代码会很棒。

编辑:经过一些更多的讨论后,看起来就像你从cookie获得会话数据,如上所示,你必须找到用户ID属性并将该数据传递给护照。他们在http://passportjs.org/guide/configure/显示的示例是:

passport.deserializeUser(function(id, done) {
    User.findById(id, function(err, user) {
        done(err, user);
    });
});

编辑2:另一种选择是完全回避这个问题。在实现上面列出的解决方案之前,我们采用了不同的路由,其中​​包含一次存储在redis中的令牌。从高层次来看,这个过程就像这样:

  1. 客户端首先通过常规HTTP请求从服务器请求令牌(实现为uuid)。这通过express的中间件路由请求,并且可以轻松访问所有会话数据。
  2. 当生成令牌时,它将作为键值对存储在redis中,其中令牌是键,会话数据是值。
  3. 然后将令牌传递回用户,当初始连接"升级"时,socket.io客户端将该URL作为GET参数附加到url。请求。
  4. 当socket.io收到授权请求而不是验证cookie时,它会解析url,将该标记作为GET参数弹出,并尝试查询redis以获取由该标记键入的键值对。如果redis查询失败或返回空数据,则拒绝授权请求。如果redis请求成功,那么您可以完全访问所有会话数据,现在可以删除redis中的键值对。
  5. 这里可能存在的问题是,您需要强制socket.io客户端在每次连接尝试时强制建立新连接。否则它会尝试使用相同的连接,因此使用相同的标记。如果在验证之后删除令牌,则会阻止套接字重新连接。根据您的应用程序,这可能是所需的行为。这是我们的,所以我们按原样离开了它,但它仍然是值得了解的东西。

    此方法还使您的WS身份验证机制独立于您的WS库。出于安全原因,Sockjs无法提供对连接请求的访问权限,因此这种方法使我们更容易从socket.io切换到sockjs。

答案 1 :(得分:0)

如果您正在开发基于Express ver 4.x和Socket.io ver 1.x的应用程序,您可能需要阅读本文: http://mykospark.net/2014/07/authentication-with-socket-io-1-0-and-express-4-0

在我的情况下,身份验证过程的代码如下所示:

io.use(function(socket, next) {
    var handshake = socket.handshake;

    if (handshake.headers.cookie) {
        var req = {
            headers: {
                cookie: handshake.headers.cookie,
            }
        }

        cookieParser(config.session.secret)(req, null, function(err) {
            if (err) {
                return next(err);
            }
            var sessionID = req.signedCookies[config.session.name] ||
                            req.cookies[config.session.name];

            var sessionStore = new MongoStore({ db: global.db});
            sessionStore.get(sessionID, function (err, session) {
                if (err) {
                    return next(err);
                }

                // userData bellow is written once the Express session is created
                if (session && session.userData) { 
                    next();
                } else {
                    return next(new Error('Invalid Session'));
                }
            })
        });
    } else {
        next(new Error('Missing Cookies'));
    }
});