Comment partager des sessions avec Socket.IO 1.x et Express 4.x?

88

Comment puis-je partager une session avec Socket.io 1.0 et Express 4.x? J'utilise un magasin Redis, mais je pense que cela ne devrait pas avoir d'importance. Je sais que je dois utiliser un middleware pour regarder les cookies et récupérer la session, mais je ne sais pas comment. J'ai cherché mais je n'ai trouvé aucun travail

    var RedisStore = connectRedis(expressSession);
    var session = expressSession({
        store: new RedisStore({
            client: redisClient
        }),
        secret: mysecret,
        saveUninitialized: true,
        resave: true
    });
    app.use(session);

    io.use(function(socket, next) {
        var handshake = socket.handshake;
        if (handshake.headers.cookie) {
            var str = handshake.headers.cookie;
            next();
        } else {
            next(new Error('Missing Cookies'));
        }
    });
Mustafa
la source

Réponses:

214

La solution est étonnamment simple. Ce n'est tout simplement pas très bien documenté. Il est également possible d'utiliser le middleware de session express comme middleware Socket.IO avec un petit adaptateur comme celui-ci:

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

Voici un exemple complet avec express 4.x, Socket.IO 1.x et Redis:

var express = require("express");
var Server = require("http").Server;
var session = require("express-session");
var RedisStore = require("connect-redis")(session);

var app = express();
var server = Server(app);
var sio = require("socket.io")(server);

var sessionMiddleware = session({
    store: new RedisStore({}), // XXX redis server config
    secret: "keyboard cat",
});

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

app.use(sessionMiddleware);

app.get("/", function(req, res){
    req.session // Session object in a normal request
});

sio.sockets.on("connection", function(socket) {
  socket.request.session // Now it's available from Socket.IO sockets too! Win!
});


server.listen(8080);
Epeli
la source
16
Pouvez-vous m'aider avec votre solution? Je ne reçois que ces données {cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true, secure: true } }Mais si j'imprime la session dans mes routes, j'obtiens toutes les variables de session que j'ai configurées (nom d'utilisateur, id, etc.)
Bobby Shark
7
Cela devrait totalement être ajouté à leurs documents. La documentation d'authentification est très légère comme elle l'est actuellement.
Bret
4
cela "fonctionne" pour moi, mais mon ID de session Express n'est pas le même que mon ID de session socket.io ... peut-être que je ne veux pas vraiment qu'ils soient les mêmes de toute façon?
Alexander Mills
4
Cette solution a très bien fonctionné! ... jusqu'à ce que j'aie besoin de sauvegarder des données dans la session à partir d'un socket.on () à ce stade, je trouve que c'est un seul moyen. Y a-t-il un moyen de le faire fonctionner dans les deux sens?
iDVB du
4
Cela a très bien fonctionné avec quelques modifications. Mais je ne pouvais pas écrire dans la session depuis socket.io. J'ai trouvé un package NPM qui répond à tous mes besoins et demande à peu près le même effort que cette réponse à mettre en œuvre. npmjs.com/package/express-socket.io-session
Bacon Brad
6

Il y a à peine un mois et demi, j'ai traité le même problème et j'ai ensuite écrit un article de blog complet sur ce sujet qui va de pair avec une application de démonstration entièrement fonctionnelle hébergée sur GitHub. La solution repose sur des modules de nœud express-session , cookie-parser et connect-redis pour tout lier. Il vous permet d'accéder et de modifier des sessions à la fois depuis le contexte REST et Sockets, ce qui est très utile.

Les deux parties cruciales sont la configuration du middleware:

app.use(cookieParser(config.sessionSecret));
app.use(session({
    store: redisStore,
    key: config.sessionCookieKey,
    secret: config.sessionSecret,
    resave: true,
    saveUninitialized: true
}));

... et configuration du serveur SocketIO:

ioServer.use(function (socket, next) {
    var parseCookie = cookieParser(config.sessionSecret);
    var handshake = socket.request;

    parseCookie(handshake, null, function (err, data) {
        sessionService.get(handshake, function (err, session) {
            if (err)
                next(new Error(err.message));
            if (!session)
                next(new Error("Not authorized"));

            handshake.session = session;
            next();
        });
    });
});

Ils vont avec un simple module sessionService que j'ai créé qui vous permet d'effectuer des opérations de base avec des sessions et ce code ressemble à ceci:

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

var redisClient = null;
var redisStore = null;

var self = module.exports = {
    initializeRedis: function (client, store) {
        redisClient = client;
        redisStore = store;
    },
    getSessionId: function (handshake) {
        return handshake.signedCookies[config.sessionCookieKey];
    },
    get: function (handshake, callback) {
        var sessionId = self.getSessionId(handshake);

        self.getSessionBySessionID(sessionId, function (err, session) {
            if (err) callback(err);
            if (callback != undefined)
                callback(null, session);
        });
    },
    getSessionBySessionID: function (sessionId, callback) {
        redisStore.load(sessionId, function (err, session) {
            if (err) callback(err);
            if (callback != undefined)
                callback(null, session);
        });
    },
    getUserName: function (handshake, callback) {
        self.get(handshake, function (err, session) {
            if (err) callback(err);
            if (session)
                callback(null, session.userName);
            else
                callback(null);
        });
    },
    updateSession: function (session, callback) {
        try {
            session.reload(function () {
                session.touch().save();
                callback(null, session);
            });
        }
        catch (err) {
            callback(err);
        }
    },
    setSessionProperty: function (session, propertyName, propertyValue, callback) {
        session[propertyName] = propertyValue;
        self.updateSession(session, callback);
    }
};

Puisqu'il y a plus de code dans l'ensemble que cela (comme l'initialisation des modules, le travail avec les sockets et les appels REST à la fois du côté client et du côté serveur), je ne vais pas coller tout le code ici, vous pouvez le voir sur le GitHub et vous pouvez en faire ce que vous voulez.

pootzko
la source
4

express-socket.io-session

est une solution toute faite à votre problème. Normalement, la session créée à la fin de socket.io a un sid différent de ceux créés dans express.js

Avant de savoir ce fait, quand je travaillais dessus pour trouver la solution, j'ai trouvé quelque chose d'un peu bizarre. Les sessions créées à partir de l'instance express.js étaient accessibles à la fin de socket.io, mais la même chose n'était pas possible pour le contraire. Et bientôt, j'ai appris que je devais travailler mon chemin dans la gestion de Sid pour résoudre ce problème. Mais, il y avait déjà un paquet écrit pour s'attaquer à ce problème. Il est bien documenté et fait le travail. J'espère que ça aide

Rahil051
la source
2

En utilisant la réponse de Bradley Lederholz, voici comment je l'ai fait fonctionner pour moi-même. Veuillez vous référer à la réponse de Bradley Lederholz, pour plus d'explications.

var app = express();
var server  = require('http').createServer(app);
var io = require('socket.io');
var cookieParse = require('cookie-parser')();
var passport = require('passport');
var passportInit = passport.initialize();
var passportSession = passport.session();
var session = require('express-session');
var mongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
var sessionMiddleware = session({
  secret: 'some secret',
  key: 'express.sid',
  resave: true,
  httpOnly: true,
  secure: true,
  ephemeral: true,
  saveUninitialized: true,
  cookie: {},
  store:new mongoStore({
  mongooseConnection: mongoose.connection,
  db: 'mydb'
  });
});

app.use(sessionMiddleware);
io = io(server);
io.use(function(socket, next){
  socket.client.request.originalUrl = socket.client.request.url;
  cookieParse(socket.client.request, socket.client.request.res, next);
});

io.use(function(socket, next){
  socket.client.request.originalUrl = socket.client.request.url;
  sessionMiddleware(socket.client.request,   socket.client.request.res, next);
});

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

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

io.on('connection', function(socket){
  ...
});

... 
server.listen(8000);
Ali
la source
A travaillé pour moi. J'ai trouvé mon utilisateur dans socket.request.user
Milazi
0

Je l'ai un peu résolu, mais ce n'est pas parfait. Ne prend pas en charge les cookies signés, etc. J'ai utilisé la fonction getcookie de express-session . La fonction modifiée est la suivante:

    io.use(function(socket, next) {
        var cookie = require("cookie");
        var signature = require('cookie-signature');
        var debug = function() {};
        var deprecate = function() {};

        function getcookie(req, name, secret) {
            var header = req.headers.cookie;
            var raw;
            var val;

            // read from cookie header
            if (header) {
                var cookies = cookie.parse(header);

                raw = cookies[name];

                if (raw) {
                    if (raw.substr(0, 2) === 's:') {
                        val = signature.unsign(raw.slice(2), secret);

                        if (val === false) {
                            debug('cookie signature invalid');
                            val = undefined;
                        }
                    } else {
                        debug('cookie unsigned')
                    }
                }
            }

            // back-compat read from cookieParser() signedCookies data
            if (!val && req.signedCookies) {
                val = req.signedCookies[name];

                if (val) {
                    deprecate('cookie should be available in req.headers.cookie');
                }
            }

            // back-compat read from cookieParser() cookies data
            if (!val && req.cookies) {
                raw = req.cookies[name];

                if (raw) {
                    if (raw.substr(0, 2) === 's:') {
                        val = signature.unsign(raw.slice(2), secret);

                        if (val) {
                            deprecate('cookie should be available in req.headers.cookie');
                        }

                        if (val === false) {
                            debug('cookie signature invalid');
                            val = undefined;
                        }
                    } else {
                        debug('cookie unsigned')
                    }
                }
            }

            return val;
        }

        var handshake = socket.handshake;
        if (handshake.headers.cookie) {
            var req = {};
            req.headers = {};
            req.headers.cookie = handshake.headers.cookie;
            var sessionId = getcookie(req, "connect.sid", mysecret);
            console.log(sessionId);
            myStore.get(sessionId, function(err, sess) {
                console.log(err);
                console.log(sess);
                if (!sess) {
                    next(new Error("No session"));
                } else {
                    console.log(sess);
                    socket.session = sess;
                    next();
                }
            });
        } else {
            next(new Error("Not even a cookie found"));
        }
    });

    // Session backend config
    var RedisStore = connectRedis(expressSession);
    var myStore = new RedisStore({
        client: redisClient
    });
    var session = expressSession({
        store: myStore,
        secret: mysecret,
        saveUninitialized: true,
        resave: true
    });
    app.use(session);
Mustafa
la source
0

Maintenant, la réponse originale acceptée ne fonctionne pas non plus pour moi. Identique à @ Rahil051, j'ai utilisé le module express-socket.io-session , et cela fonctionne toujours. Ce module utilise un analyseur de cookies pour analyser l'ID de session avant d'entrer dans le middleware de session express. Je pense que la réponse de @pootzko, @Mustafa et @ Kosar est silencieuse.

J'utilise ces modules:

"dependencies": 
{
  "debug": "^2.6.1",
  "express": "^4.14.1",
  "express-session": "^1.15.1",
  "express-socket.io-session": "^1.3.2
  "socket.io": "^1.7.3"
}

vérifiez les données dans socket.handshake:

const debug = require('debug')('ws');
const sharedsession = require('express-socket.io-session');

module.exports = (server, session) => {
    const io = require('socket.io').listen(server);
    let connections = [];

    io.use(sharedsession(session, {
        autoSave: true,
    }));

    io.use(function (socket, next) {
        debug('check handshake %s', JSON.stringify(socket.handshake, null, 2));
        debug('check headers %s', JSON.stringify(socket.request.headers));
        debug('check socket.id %s', JSON.stringify(socket.id));
        next();
    });

    io.sockets.on('connection', (socket) => {
        connections.push(socket);
    });
};
Pentatonique
la source