Autenticação Socket.IO


123

Estou tentando usar o Socket.IO no Node.js e estou tentando permitir que o servidor forneça uma identidade para cada um dos clientes do Socket.IO. Como o código do soquete está fora do escopo do código do servidor http, ele não tem acesso fácil às informações de solicitação enviadas, portanto, suponho que elas precisem ser enviadas durante a conexão. Qual é a melhor maneira de

1) leve as informações ao servidor sobre quem está se conectando via Socket.IO

2) autenticar quem eles dizem ser (atualmente estou usando o Express, se isso facilitar as coisas)

Respostas:


104

Use connect-redis e tenha redis como seu armazenamento de sessão para todos os usuários autenticados. Verifique se, na autenticação, você envia a chave (normalmente req.sessionID) para o cliente. Faça com que o cliente armazene essa chave em um cookie.

No socket connect (ou a qualquer momento depois), busque essa chave no cookie e envie-a de volta ao servidor. Busque as informações da sessão em redis usando esta chave. (Tecla GET)

Por exemplo:

Lado do servidor (com redis como armazenamento de sessão):

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

Lado do 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 do 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;
      });
    }

  });
});

3
Existe um novo link interessante sobre isso => danielbaulig.de/socket-ioexpress
Alfred

1
aha! Esse link é realmente bom. Isso está desatualizado (usa o Socket.IO 0.6.3)! Essencialmente o mesmo conceito. Fetch biscoito, o check-in armazenamento de sessão e autenticar :)
Shripad Krishna

@ NightWolf, ele deve funcionar porque você está buscando o cookie em javascript e não em flash (actionscript). GetCookieé a função javascript.
Shripad Krishna

1
@ Alfred esse link parece estar morto agora :(
Pro Q

O link de @ Alfred é válido novamente 01/02/2018
Tom

32

Também gostei da maneira como o pusherapp faz canais privados .insira a descrição da imagem aqui

Um ID de soquete exclusivo é gerado e enviado ao navegador pelo Pusher. Isso é enviado ao seu aplicativo (1) por meio de uma solicitação AJAX que autoriza o usuário a acessar o canal no seu sistema de autenticação existente. Se for bem-sucedido, seu aplicativo retornará uma sequência de autorização para o navegador assinado com o seu Pusher secret. Isso é enviado ao Pusher pelo WebSocket, que completa a autorização (2) se a sequência de autorização corresponder.

Porque também socket.iopossui socket_id exclusivo para cada socket.

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

Eles usaram cadeias de autorização assinadas para autorizar usuários.

Ainda não refleti isso socket.io, mas acho que poderia ser um conceito bastante interessante.


3
Isso é incrível. Mas seria mais fácil usar apenas cookies se o servidor do aplicativo e o servidor do websocket não estiverem separados. Mas você geralmente gostaria de separar os dois (será mais fácil dimensionar o servidor de soquete se separado). Por isso é bom :)
Shripad Krishna

1
@ Sripad, você é completamente verdadeiro e eu também gosto muito da sua implementação: P
Alfred

27

Eu sei que isso é um pouco antigo, mas para futuros leitores, além da abordagem de analisar cookies e recuperar a sessão do armazenamento (por exemplo: passport.socketio ), você também pode considerar uma abordagem baseada em token.

Neste exemplo, eu uso JSON Web Tokens, que são bastante padrão. Você deve fornecer à página do cliente o token; neste exemplo, imagine um terminal de autenticação que retorne 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});
});

Agora, seu servidor socket.io pode ser configurado da seguinte maneira:

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

O middleware socket.io-jwt espera o token em uma sequência de consultas, portanto, do cliente você só precisará anexá-lo ao conectar:

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

Eu escrevi uma explicação mais detalhada sobre esse método e cookies aqui .


Ei! Pergunta rápida, por que você está enviando o perfil com o token se ele não pode ser decodificado no cliente?
Carpetfizz

Pode. O JWT é apenas base64, assinado digitalmente. O cliente pode decodificá-lo, mas não pode validar a assinatura neste exemplo.
José F. Romaniello

3

Aqui está minha tentativa de ter o seguinte funcionando:

  • express : 4,14
  • socket.io : 1.5
  • passaporte (usando sessões): 0.3
  • redis : 2.6 (Estrutura de dados muito rápida para lidar com sessões; mas você também pode usar outros como o MongoDB. No entanto, incentivo você a usá-lo para dados da sessão + MongoDB para armazenar outros dados persistentes como Usuários)

Como você também pode adicionar algumas solicitações de API, também usaremos o pacote http para que o soquete HTTP e Web funcionem na mesma porta.


server.js

A extração a seguir inclui apenas tudo o que você precisa para configurar as tecnologias anteriores. Você pode ver a versão completa do server.js que usei em um dos meus projetos aqui .

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

Nossa socketConnectionHandler, eu simplesmente não gosto de colocar tudo no server.js (mesmo que você possa perfeitamente), especialmente porque esse arquivo pode acabar contendo muito código 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}`);
    }
  });
}

Material extra (cliente):

Apenas uma versão muito básica do que o cliente socket.io do JavaScript poderia ser:

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

Referências:

Como não consegui fazer referência ao código, mudei-o para cá.

1: Como configurar suas estratégias do Passport: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration


2

Este artigo ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) mostra como

  • armazenar sessões do servidor HTTP em Redis (usando Predis)
  • obtenha essas sessões do Redis no node.js pelo ID da sessão enviado em um cookie

Usando esse código, você também pode obtê-los no 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

use session e redis entre c / s

// lado do servidor

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

Parece que, se você acabou de conectar o mesmo código que está usando para validar os pontos de extremidade do Node.js. (mas precisará ajustar as partes que estão manipulando o objeto de solicitação), poderá reutilizar seu token para suas rotas.
Nick Pineda

-5

Isso deve servir

//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 no meu caso
ZiTAL 14/05

8
Isso nem é uma tentativa de resposta. Isso não é autenticação, é simplesmente fazer uma conexão.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.