Autenticación Socket.IO

123

Estoy tratando de usar Socket.IO en Node.js, y estoy tratando de permitir que el servidor dé una identidad a cada uno de los clientes de Socket.IO. Como el código de socket está fuera del alcance del código del servidor http, no tiene fácil acceso a la información de solicitud enviada, por lo que supongo que tendrá que enviarse durante la conexión. ¿Cuál es la mejor manera de

1) envíe la información al servidor sobre quién se está conectando a través de Socket.IO

2) autenticar quiénes dicen ser (actualmente estoy usando Express, si eso facilita las cosas)

Ryan
fuente

Respuestas:

104

Use connect-redis y tenga redis como su almacén de sesiones para todos los usuarios autenticados. Asegúrese de que en la autenticación envíe la clave (normalmente req.sessionID) al cliente. Haga que el cliente almacene esta clave en una cookie.

En socket connect (o en cualquier momento posterior), obtenga esta clave de la cookie y envíela de vuelta al servidor. Obtenga la información de la sesión en redis usando esta clave. (Obtener la clave)

P.ej:

Lado del servidor (con redis como tienda de sesiones):

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

Lado del cliente:

//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});
});

Lado del servidor:

//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
fuente
3
Hay un nuevo enlace interesante sobre esto => danielbaulig.de/socket-ioexpress
Alfred
1
¡Ajá! Ese vínculo es realmente bueno. ¡Esto está desactualizado (usa Socket.IO 0.6.3)! Esencialmente el mismo concepto. Obtener la cookie, registrar la sesión, almacenar y autenticar :)
Shripad Krishna
@NightWolf debería funcionar porque está obteniendo la cookie en javascript, no en flash (actionscript). GetCookiees la función de JavaScript.
Shripad Krishna
1
@Alfred, ese enlace parece estar muerto ahora :(
Pro Q
El enlace de @ Alfred es válido nuevamente 2018-02-01
Tom
32

También me gustó la forma en que pusherapp hace los canales privados .ingrese la descripción de la imagen aquí

Pusher genera una identificación de socket única y la envía al navegador. Esto se envía a su aplicación (1) a través de una solicitud AJAX que autoriza al usuario a acceder al canal contra su sistema de autenticación existente. Si tiene éxito, su solicitud devuelve una cadena de autorización al navegador firmado con usted Pusher secret. Esto se envía a Pusher a través de WebSocket, que completa la autorización (2) si la cadena de autorización coincide.

Porque también socket.iotiene un socket_id único para cada socket.

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

Utilizaron cadenas de autorización firmadas para autorizar a los usuarios.

Todavía no he reflejado esto socket.io, pero creo que podría ser un concepto bastante interesante.

Alfredo
fuente
3
Esto es asombroso. Pero sería más fácil usar cookies si su servidor de aplicaciones y servidor websocket no están separados. Pero generalmente le gustaría separar los dos (será más fácil escalar el servidor de socket si está separado). Entonces es bueno :)
Shripad Krishna
1
@Shripad, eres completamente sincero y también me gusta mucho tu implementación: P
Alfred
27

Sé que esto es un poco antiguo, pero para los futuros lectores, además del enfoque de analizar la cookie y recuperar la sesión del almacenamiento (por ejemplo, passport.socketio ), también podría considerar un enfoque basado en token.

En este ejemplo, utilizo JSON Web Tokens, que son bastante estándar. Tienes que darle a la página del cliente el token, en este ejemplo imagina un punto final de autenticación que devuelve 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});
});

Ahora, su servidor socket.io se puede configurar de la siguiente manera:

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');
  });

El middleware socket.io-jwt espera el token en una cadena de consulta, por lo que desde el cliente solo tiene que adjuntarlo al conectarse:

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

Escribí una explicación más detallada sobre este método y las cookies aquí .

José F. Romaniello
fuente
¡Oye! Pregunta rápida, ¿por qué envía el perfil con el token si no se puede decodificar en el cliente?
Carpetfizz
Puede. JWT es solo base64, firmado digitalmente. El cliente puede decodificarlo, pero no puede validar la firma en este ejemplo.
José F. Romaniello
3

Aquí está mi intento de que funcione lo siguiente:

  • expreso : 4.14
  • socket.io : 1.5
  • pasaporte (usando sesiones): 0.3
  • redis : 2.6 (Estructura de datos realmente rápida para manejar sesiones; pero también puedes usar otros como MongoDB. Sin embargo, te animo a usar esto para datos de sesión + MongoDB para almacenar otros datos persistentes como Usuarios)

Dado que es posible que también desee agregar algunas solicitudes de API, también usaremos el paquete http para que tanto HTTP como Web socket funcionen en el mismo puerto.


server.js

El siguiente extracto solo incluye todo lo que necesita para configurar las tecnologías anteriores. Puedes ver la versión completa de server.js que utilicé en uno de mis proyectos aquí .

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

Nuestro socketConnectionHandler, simplemente no me gusta poner todo dentro de server.js (aunque perfectamente podría hacerlo), especialmente porque este archivo puede terminar conteniendo bastante código bastante rápido.

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}`);
    }
  });
}

Material extra (cliente):

Solo una versión muy básica de lo que podría ser el cliente 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}`);
});

Referencias:

Simplemente no podía hacer referencia dentro del código, así que lo moví aquí.

1: Cómo configurar sus estrategias de Passport: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration

zurfyx
fuente
2

Este artículo ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) muestra cómo

  • almacenar sesiones del servidor HTTP en Redis (usando Predis)
  • obtener estas sesiones de Redis en node.js por el ID de sesión enviado en una cookie

Usando este código, también puede obtenerlos en 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));
    });
});
Blade1336
fuente
2

use session y redis entre c / s

// lado del servidor

io.use(function(socket, next) {
 console.log(socket.handshake.headers.cookie); // get here session id and match from redis session data
 next();
});
planificador
fuente
Parece que si simplemente conecta el mismo código que está usando para validar sus puntos finales de Node.js (pero tendrá que modificar cualquier parte que esté manejando el objeto de solicitud), podría reutilizar su token para sus rutas.
Nick Pineda
-5

esto debería hacerlo

//server side

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

//client side

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

console.log(io.sessionid)
dominica
fuente
1
io.socket.sessionid en mi caso
ZiTAL
8
Esto ni siquiera es un intento de respuesta. Esto no es autenticación, es simplemente hacer una conexión.