Autenticazione Socket.IO


123

Sto cercando di utilizzare Socket.IO in Node.js e sto cercando di consentire al server di fornire un'identità a ciascuno dei client Socket.IO. Poiché il codice socket è al di fuori dell'ambito del codice del server http, non ha un facile accesso alle informazioni sulla richiesta inviate, quindi presumo che dovrà essere inviato durante la connessione. Qual è il modo migliore per

1) ottenere le informazioni al server su chi si connette tramite Socket.IO

2) autenticare chi dicono di essere (attualmente sto usando Express, se questo rende le cose più facili)

Risposte:


104

Usa connect-redis e have redis come session store per tutti gli utenti autenticati. Assicurati di inviare la chiave (normalmente req.sessionID) al client durante l'autenticazione. Chiedere al cliente di memorizzare questa chiave in un cookie.

Alla connessione socket (o in qualsiasi momento successivo), prendi questa chiave dal cookie e rimandala al server. Recupera le informazioni sulla sessione in redis utilizzando questa chiave. (Tasto GET)

Per esempio:

Lato server (con redis come session store):

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

Dalla parte 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});
});

Lato server:

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

  });
});

3
C'è un nuovo collegamento interessante su questo => danielbaulig.de/socket-ioexpress
Alfred

1
aha! Quel collegamento è davvero buono. Questo è obsoleto (usa Socket.IO 0.6.3)! Essenzialmente lo stesso concetto. Recupera il cookie, controlla nel session store e autenticati :)
Shripad Krishna

@ NightWolf dovrebbe funzionare perché stai recuperando il cookie in javascript non in flash (actioncript). GetCookieè la funzione javascript.
Shripad Krishna

1
@ Alfred quel collegamento sembra essere morto ora :(
Pro Q

Il link di @ Alfred è di nuovo valido 2018-02-01
Tom

32

Mi è piaciuto anche il modo in cui pusherapp fa canali privati .inserisci qui la descrizione dell'immagine

Un unico socket id viene generato e inviato al browser da Pusher. Questo viene inviato alla tua applicazione (1) tramite una richiesta AJAX che autorizza l'utente ad accedere al canale contro il tuo sistema di autenticazione esistente. In caso di esito positivo, l'applicazione restituisce una stringa di autorizzazione al browser firmata con il segreto Pusher. Questo viene inviato a Pusher tramite WebSocket, che completa l'autorizzazione (2) se la stringa di autorizzazione corrisponde.

Perché socket.ioha anche socket_id univoco per ogni socket.

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

Hanno usato stringhe di autorizzazione firmate per autorizzare gli utenti.

Non l'ho ancora rispecchiato socket.io, ma penso che potrebbe essere un concetto piuttosto interessante.


3
Questo e spettacolare. Ma sarebbe più semplice usare i cookie se il server delle app e il server websocket non sono separati. Ma generalmente vorresti separare i due (sarà più facile scalare il server socket se separato). Quindi va bene :)
Shripad Krishna

1
@Shripad sei completamente vero e mi piace molto anche la tua implementazione: P
Alfred

27

So che questo è un po 'vecchio, ma per i futuri lettori oltre all'approccio di analizzare i cookie e recuperare la sessione dall'archivio (ad es. Passport.socketio ) potresti anche considerare un approccio basato su token.

In questo esempio utilizzo i token Web JSON che sono piuttosto standard. Devi dare alla pagina client il token, in questo esempio immagina un endpoint di autenticazione che ritorni 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: 'john@doe.com',
    id: 123
  };

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

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

Ora, il tuo server socket.io può essere configurato come segue:

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

Il middleware socket.io-jwt si aspetta il token in una stringa di query, quindi dal client devi solo collegarlo durante la connessione:

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

Ho scritto una spiegazione più dettagliata su questo metodo e sui cookie qui .


Hey! Domanda veloce, perché stai inviando il profilo con il token se non può essere decodificato sul client?
Carpetfizz

Può. JWT è solo base64, con firma digitale. Il client può decodificarlo, ma non può convalidare la firma in questo esempio.
José F. Romaniello

3

Ecco il mio tentativo di far funzionare quanto segue:

  • espresso : 4.14
  • socket.io : 1.5
  • passaporto (utilizzando sessioni): 0.3
  • redis : 2.6 (Struttura dei dati molto veloce per gestire le sessioni; ma puoi anche usarne altri come MongoDB. Tuttavia, ti incoraggio a usarlo per i dati di sessione + MongoDB per memorizzare altri dati persistenti come Utenti)

Dal momento che potresti voler aggiungere anche alcune richieste API, useremo anche il pacchetto http per avere sia HTTP che Web socket funzionanti sulla stessa porta.


server.js

Il seguente estratto include solo tutto il necessario per impostare le tecnologie precedenti. Puoi vedere la versione completa di server.js che ho usato in uno dei miei progetti qui .

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

prese / index.js

Il nostro socketConnectionHandler, semplicemente non mi piace mettere tutto all'interno di server.js (anche se potresti perfettamente), soprattutto perché questo file può finire per contenere un bel po 'di codice abbastanza rapidamente.

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

Materiale extra (cliente):

Solo una versione molto semplice di ciò che potrebbe essere il 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}`);
});

Riferimenti:

Non potevo fare riferimento all'interno del codice, quindi l'ho spostato qui.

1: Come impostare le strategie per Passport: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration


2

Questo articolo ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) mostra come

  • memorizzare le sessioni del server HTTP in Redis (utilizzando Predis)
  • ottenere queste sessioni da Redis in node.js dall'id di sessione inviato in un cookie

Usando questo codice puoi ottenerli anche in 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));
    });
});

2

usa session e redis tra c / s

// lato server

io.use(function(socket, next) {
 console.log(socket.handshake.headers.cookie); // get here session id and match from redis session data
 next();
});

Sembra che se inserisci lo stesso codice che stai utilizzando per convalidare i tuoi endpoint Node.js (ma dovrai modificare tutte le parti con cui stai gestendo l'oggetto richiesta) potresti semplicemente riutilizzare il tuo token per i tuoi percorsi.
Nick Pineda

-5

questo dovrebbe farlo

//server side

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

//client side

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

console.log(io.sessionid)

1
io.socket.sessionid nel mio caso
ZiTAL

8
Questo non è nemmeno un tentativo di risposta. Questa non è autenticazione, ma semplicemente una connessione.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.