使用共享会话数据进行socket.io身份验证,io.use()的工作原理

18
如何在Socket.IO 1.x和Express 4.x之间共享会话?启发,我以某种"纯净"的方式实现了套接字身份验证,在这种方式中不需要使用cookie-parser并从标头读取Cookie,但仍有一些项不清楚。例如使用最新版本的稳定的Socket.IO版本1.3.6。
var express      = require('express'),
    session      = require('express-session'),
    RedisStore   = require('connect-redis')(session),
    sessionStore = new RedisStore(),
    io           = require('socket.io').listen(server);

var sessionMiddleware = session({
    store   : sessionStore,
    secret  : "blabla",
    cookie  : { ... }
});

function socketAuthentication(socket, next) {
  var sessionID = socket.request.sessionID;

  sessionStore.get(sessionID, function(err, session) {
      if(err) { return next(err); }

      if(typeof session === "undefined") {
          return next( new Error('Session cannot be found') );
      }
      console.log('Socket authenticated successfully');
      next();
  });
}

io.of('/abc').use(socketAuthentication).on('connection', function(socket) { 
  // setup events and stuff
});

io.use(function(socket, next) {
  sessionMiddleware(socket.request, socket.request.res, next);
});

app.use(sessionMiddleware);
app.get('/', function(req, res) { res.render('index'); });
server.listen(8080);

index.html

<body>
    ...
    <script src="socket.io/socket.io.js"></script>
    <script>
       var socket = io('http://localhost:8080/abc');
    </script>
</body>

所以,客户端的io('http://localhost:8080/abc');将发送初始HTTP握手请求到服务器,服务器可以通过它获取cookie和许多其他请求信息。因此,服务器可以通过socket.request访问该初始请求。

我的第一个问题是为什么握手请求不在express-session中间件的范围内?(更普遍地说,在app.use中间件的范围内?)某种程度上,我期望这个app.use(sessionMiddleware);会在初始请求之前触发,然后轻松访问socket.request.session

其次,定义了io.use()中间件将触发哪些场景?仅用于初始的HTTP握手请求吗?看起来像io.use()用于与socket相关的东西(问题是:什么东西),而app.use用于标准请求。

我不太确定为什么在上面的例子中io.use()会在io.of('/abc').use()之前触发。有意我将io.of('/abc').use()放在前面来测试是否可以正常工作,结果它确实可以正常工作。应该写反了。

最后,像一些人在链接的问题中指出的那样,socket.request.res有时会是undefined,导致应用程序崩溃,可以通过提供空对象而不是socket.request.res来解决这个问题,比如:sessionMiddleware(socket.request, {}, next);,这对我来说看起来像一个肮脏的技巧。为什么socket.request.res会产生undefined的原因是什么?

2个回答

5
尽管@oLeduc有一定的正确性,但还有更多需要解释的事情。
为什么握手请求不在express-session中间件范围内?
最大的原因是express中间件旨在处理请求特定任务。并非所有处理程序都使用标准的req、res、next语法,但其中大部分都是如此。而套接字是“无请求”的,如果我这么说的话。你有socket.request的事实是由于握手的方式以及它使用HTTP进行握手。因此,socket.io的人们将第一个请求入侵到你的socket类中,以便你可以使用它。它并非由express团队设计用于套接字和TCP。
定义为io.use()的中间件将在哪些情况下触发?

io.use 是 express use 中间件的近似表示。在 express 中,中间件会在每个请求上执行,对吧?但是 socket 没有请求,如果在每次 socket 推送时使用中间件,就会很尴尬,因此它们被设计成在每个连接上执行。但是,与 express 中间件一样,Socket.IO 在连接上使用中间件甚至在实际握手之前用于处理(和响应)请求。您可以使用这种类型的中间件拦截握手,非常方便(以保护服务器免受垃圾邮件攻击)。更多信息可以在 passport-socketio 的代码中找到。

为什么 io.use() 在 io.of('/abc').use() 之前触发?

这个真正的解释可以在 这里 找到,代码如下:

Server.prototype.of = function(name, fn){
    if (String(name)[0] !== '/') name = '/' + name;

    if (!this.nsps[name]) {
        debug('initializing namespace %s', name);
        var nsp = new Namespace(this, name);
        this.nsps[name] = nsp;
    }
    if (fn) this.nsps[name].on('connect', fn);
    return this.nsps[name];
};

在代码开头,有这行代码:
this.sockets = this.of('/');

所以,一开始就创建了一个默认命名空间。在那里,你可以看到它立即附加了一个“connect”侦听器。后来,每个命名空间都会得到完全相同的“connect”侦听器,但因为“Namespace”是“EventEmitter”,所以侦听器一个接一个地添加,因此它们一个接一个地触发。换句话说,默认命名空间的侦听器位于第一位,因此它首先触发。
我不认为这是有意设计的,但它恰好是这样的:)
为什么socket.request.res未定义?
老实说,我对此并不确定。这是由于engine.io的实现方式 - 你可以在这里读到更多信息。它连接到常规服务器,并发送请求以进行握手。我只能想象有时头文件与响应分离,这就是为什么你不会得到任何响应的原因。无论如何,这只是猜测。
希望这些信息有所帮助。

3

为什么握手请求不在express-session中间件的范围内?

因为socket.io会依附于express下面的http.Server层。这在socket.io源代码的评论中有提到。

原因是第一个请求是普通的HTTP请求,用于将无状态的HTTP连接升级为有状态的websocket连接。因此,让它通过适用于常规HTTP请求的所有逻辑并没有太多意义。

使用io.use()定义的中间件会在哪些场景下触发?

每当创建新的socket连接时。

因此,每次客户端连接时都会调用使用io.use()注册的中间件。但一旦客户端连接成功,当从客户端接收到数据包时就不会再调用它了。无论连接是在自定义命名空间上还是在主要命名空间上初始化,它都会被调用。

为什么io.use()会在io.of('/abc').use()之前触发?

命名空间是socket.io实现的细节,实际上,WebSocket始终会先到达主要命名空间。

为了说明这种情况,请看一下这段代码以及它产生的输出:

var customeNamespace = io.of('/abc');

customeNamespace.use(function(socket, next){
  console.log('Use -> /abc');

  return next();
});

io.of('/abc').on('connection', function (socket) {
  console.log('Connected to namespace!')
});

io.use(function(socket, next){
  console.log('Use -> /');

  return next();
});

io.on('connection', function (socket) {
  console.log('Connected to namespace!')
});

输出:

Use -> /
Main namespace
Use -> /abc
Connected to namespace!

请看socket.io团队在文档中添加的警告:

重要提示:命名空间是Socket.IO协议的实现细节,与底层传输的实际URL无关,默认为/socket.io/…。

为什么socket.request.res未定义?

据我所知,它不应该是未定义的。这可能与您特定的实现有关。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接