Authentification Socket.IO

123

J'essaie d'utiliser Socket.IO dans Node.js, et j'essaie de permettre au serveur de donner une identité à chacun des clients Socket.IO. Comme le code du socket est en dehors du champ d'application du code du serveur http, il n'a pas un accès facile aux informations de demande envoyées, donc je suppose qu'il devra être envoyé pendant la connexion. Quelle est la meilleure façon de

1) Obtenez les informations au serveur sur qui se connecte via Socket.IO

2) authentifier qui ils prétendent être (j'utilise actuellement Express, si cela facilite les choses)

Ryan
la source

Réponses:

104

Utilisez connect-redis et faites de redis votre magasin de session pour tous les utilisateurs authentifiés. Assurez-vous que lors de l'authentification, vous envoyez la clé (normalement req.sessionID) au client. Demandez au client de stocker cette clé dans un cookie.

Lors de la connexion socket (ou à tout moment plus tard), récupérez cette clé dans le cookie et renvoyez-la au serveur. Récupérez les informations de session dans redis à l'aide de cette clé. (Obtenir la clé)

Par exemple:

Côté serveur (avec redis comme magasin de session):

req.session.regenerate...
res.send({rediskey: req.sessionID});

Côté client:

//store the key in a cookie
SetCookie('rediskey', <%= rediskey %>); //http://msdn.microsoft.com/en-us/library/ms533693(v=vs.85).aspx

//then when socket is connected, fetch the rediskey from the document.cookie and send it back to server
var socket = new io.Socket();

socket.on('connect', function() {
  var rediskey = GetCookie('rediskey'); //http://msdn.microsoft.com/en-us/library/ms533693(v=vs.85).aspx
  socket.send({rediskey: rediskey});
});

Du côté serveur:

//in io.on('connection')
io.on('connection', function(client) {
  client.on('message', function(message) {

    if(message.rediskey) {
      //fetch session info from redis
      redisclient.get(message.rediskey, function(e, c) {
        client.user_logged_in = c.username;
      });
    }

  });
});
Shripad Krishna
la source
3
Il y a un nouveau lien intéressant à ce sujet => danielbaulig.de/socket-ioexpress
Alfred
1
aha! Ce lien est vraiment bon. Ceci est obsolète (utilise Socket.IO 0.6.3)! Essentiellement le même concept. Récupérez le cookie, enregistrez-vous dans le magasin de session et authentifiez-vous :)
Shripad Krishna
@NightWolf cela devrait fonctionner car vous récupérez le cookie en javascript et non en flash (actionscript). GetCookieest la fonction javascript.
Shripad Krishna
1
@Alfred ce lien semble être mort maintenant :(
Pro Q
@ Le lien d'Alfred est à nouveau valide 01/02/2018
Tom
32

J'ai aussi aimé la façon dont pusherapp fait des chaînes privées .entrez la description de l'image ici

Un identifiant de socket unique est généré et envoyé au navigateur par Pusher. Celui-ci est envoyé à votre application (1) via une requête AJAX qui autorise l'utilisateur à accéder au canal par rapport à votre système d'authentification existant. En cas de succès, votre application renvoie une chaîne d'autorisation au navigateur signé avec votre secret Pusher. Celui-ci est envoyé à Pusher via WebSocket, qui complète l'autorisation (2) si la chaîne d'autorisation correspond.

Car a également socket.ioun socket_id unique pour chaque socket.

socket.on('connect', function() {
        console.log(socket.transport.sessionid);
});

Ils ont utilisé des chaînes d'autorisation signées pour autoriser les utilisateurs.

Je n'ai pas encore reflété cela socket.io, mais je pense que cela pourrait être un concept assez intéressant.

Alfred
la source
3
C'est génial. Mais il serait plus facile d'utiliser simplement des cookies si votre serveur d'application et votre serveur websocket ne sont pas séparés. Mais vous souhaitez généralement séparer les deux (il sera plus facile de mettre à l'échelle le serveur socket s'il est séparé). Alors c'est bon :)
Shripad Krishna
1
@Shripad vous êtes tout à fait vrai et j'aime aussi beaucoup votre implémentation: P
Alfred
27

Je sais que c'est un peu vieux, mais pour les futurs lecteurs, en plus de l'approche d'analyse des cookies et de récupération de la session à partir du stockage (par exemple, passeport.socketio ), vous pouvez également envisager une approche basée sur des jetons.

Dans cet exemple, j'utilise des jetons Web JSON qui sont assez standard. Vous devez donner à la page client le jeton, dans cet exemple, imaginez un point de terminaison d'authentification qui renvoie JWT:

var jwt = require('jsonwebtoken');
// other requires

app.post('/login', function (req, res) {

  // TODO: validate the actual user user
  var profile = {
    first_name: 'John',
    last_name: 'Doe',
    email: '[email protected]',
    id: 123
  };

  // we are sending the profile in the token
  var token = jwt.sign(profile, jwtSecret, { expiresInMinutes: 60*5 });

  res.json({token: token});
});

Maintenant, votre serveur socket.io peut être configuré comme suit:

var socketioJwt = require('socketio-jwt');

var sio = socketIo.listen(server);

sio.set('authorization', socketioJwt.authorize({
  secret: jwtSecret,
  handshake: true
}));

sio.sockets
  .on('connection', function (socket) {
     console.log(socket.handshake.decoded_token.email, 'has joined');
     //socket.on('event');
  });

Le middleware socket.io-jwt attend le jeton dans une chaîne de requête, donc du client, vous n'avez qu'à le joindre lors de la connexion:

var socket = io.connect('', {
  query: 'token=' + token
});

J'ai écrit une explication plus détaillée sur cette méthode et les cookies ici .

José F. Romaniello
la source
Hey! Question rapide, pourquoi envoyez-vous le profil avec le jeton s'il ne peut pas être décodé sur le client?
Carpetfizz
Ça peut. JWT est juste base64, signé numérique. Le client peut le décoder, mais il ne peut pas valider la signature dans cet exemple.
José F. Romaniello
3

Voici ma tentative de faire fonctionner les éléments suivants:

  • express : 4.14
  • socket.io : 1.5
  • passeport (en utilisant les sessions): 0,3
  • redis : 2.6 (Structure de données vraiment rapide pour gérer les sessions; mais vous pouvez également en utiliser d'autres comme MongoDB. Cependant, je vous encourage à l'utiliser pour les données de session + MongoDB pour stocker d'autres données persistantes comme les utilisateurs)

Puisque vous voudrez peut-être également ajouter des requêtes API, nous utiliserons également le package http pour que HTTP et Web socket fonctionnent sur le même port.


server.js

L'extrait suivant comprend uniquement tout ce dont vous avez besoin pour configurer les technologies précédentes. Vous pouvez voir la version complète de server.js que j'ai utilisée dans l'un de mes projets ici .

import http from 'http';
import express from 'express';
import passport from 'passport';
import { createClient as createRedisClient } from 'redis';
import connectRedis from 'connect-redis';
import Socketio from 'socket.io';

// Your own socket handler file, it's optional. Explained below.
import socketConnectionHandler from './sockets'; 

// Configuration about your Redis session data structure.
const redisClient = createRedisClient();
const RedisStore = connectRedis(Session);
const dbSession = new RedisStore({
  client: redisClient,
  host: 'localhost',
  port: 27017,
  prefix: 'stackoverflow_',
  disableTTL: true
});

// Let's configure Express to use our Redis storage to handle
// sessions as well. You'll probably want Express to handle your 
// sessions as well and share the same storage as your socket.io 
// does (i.e. for handling AJAX logins).
const session = Session({
  resave: true,
  saveUninitialized: true,
  key: 'SID', // this will be used for the session cookie identifier
  secret: 'secret key',
  store: dbSession
});
app.use(session);

// Let's initialize passport by using their middlewares, which do 
//everything pretty much automatically. (you have to configure login
// / register strategies on your own though (see reference 1)
app.use(passport.initialize());
app.use(passport.session());

// Socket.IO
const io = Socketio(server);
io.use((socket, next) => {
  session(socket.handshake, {}, next);
});
io.on('connection', socketConnectionHandler); 
// socket.io is ready; remember that ^this^ variable is just the 
// name that we gave to our own socket.io handler file (explained 
// just after this).

// Start server. This will start both socket.io and our optional 
// AJAX API in the given port.
const port = 3000; // Move this onto an environment variable, 
                   // it'll look more professional.
server.listen(port);
console.info(`🌐  API listening on port ${port}`);
console.info(`🗲 Socket listening on port ${port}`);

sockets / index.js

Notre socketConnectionHandler, je n'aime tout simplement pas tout mettre dans server.js (même si vous le pouvez parfaitement), d'autant plus que ce fichier peut finir par contenir beaucoup de code assez rapidement.

export default function connectionHandler(socket) {
  const userId = socket.handshake.session.passport &&
                 socket.handshake.session.passport.user; 
  // If the user is not logged in, you might find ^this^ 
  // socket.handshake.session.passport variable undefined.

  // Give the user a warm welcome.
  console.info(`⚡︎ New connection: ${userId}`);
  socket.emit('Grettings', `Grettings ${userId}`);

  // Handle disconnection.
  socket.on('disconnect', () => {
    if (process.env.NODE_ENV !== 'production') {
      console.info(`⚡︎ Disconnection: ${userId}`);
    }
  });
}

Matériel supplémentaire (client):

Juste une version très basique de ce que pourrait être le client JavaScript socket.io:

import io from 'socket.io-client';

const socketPath = '/socket.io'; // <- Default path.
                                 // But you could configure your server
                                // to something like /api/socket.io

const socket = io.connect('localhost:3000', { path: socketPath });
socket.on('connect', () => {
  console.info('Connected');
  socket.on('Grettings', (data) => {
    console.info(`Server gretting: ${data}`);
  });
});
socket.on('connect_error', (error) => {
  console.error(`Connection error: ${error}`);
});

Références:

Je ne pouvais tout simplement pas faire référence à l'intérieur du code, alors je l'ai déplacé ici.

1: Comment configurer vos stratégies Passport: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration

zurfyx
la source
2

Cet article ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) montre comment

  • stocker les sessions du serveur HTTP dans Redis (en utilisant Predis)
  • obtenir ces sessions de Redis dans node.js par l'identifiant de session envoyé dans un cookie

En utilisant ce code, vous pouvez également les obtenir dans socket.io.

var io = require('socket.io').listen(8081);
var cookie = require('cookie');
var redis = require('redis'), client = redis.createClient();
io.sockets.on('connection', function (socket) {
    var cookies = cookie.parse(socket.handshake.headers['cookie']);
    console.log(cookies.PHPSESSID);
    client.get('sessions/' + cookies.PHPSESSID, function(err, reply) {
        console.log(JSON.parse(reply));
    });
});
Lame1336
la source
2

utiliser session et redis entre c / s

// du côté serveur

io.use(function(socket, next) {
 console.log(socket.handshake.headers.cookie); // get here session id and match from redis session data
 next();
});
onplanner
la source
On dirait que si vous branchez simplement le même code que vous utilisez pour valider vos points de terminaison Node.js (mais vous devrez modifier toutes les parties que vous gérez l'objet de requête), vous pouvez simplement réutiliser votre jeton pour vos itinéraires.
Nick Pineda
-5

cela devrait le faire

//server side

io.sockets.on('connection', function (con) {
  console.log(con.id)
})

//client side

var io = io.connect('http://...')

console.log(io.sessionid)
dominic
la source
1
io.socket.sessionid dans mon cas
ZiTAL
8
Ce n'est même pas une tentative de réponse. Ce n'est pas une authentification, c'est simplement une connexion.