Uwierzytelnianie Socket.IO


123

Próbuję użyć Socket.IO w Node.js i próbuję zezwolić serwerowi na nadanie tożsamości każdemu z klientów Socket.IO. Ponieważ kod gniazda znajduje się poza zakresem kodu serwera http, nie ma łatwego dostępu do wysyłanych informacji o żądaniu, więc zakładam, że będzie musiał zostać wysłany podczas połączenia. Jaki jest najlepszy sposób

1) uzyskać do serwera informację o tym, kto łączy się przez Socket.IO

2) uwierzytelnij tego, za kogo się podaje (obecnie używam Express, jeśli to ułatwi sprawę)

Odpowiedzi:


104

Użyj connect-redis i używaj redis jako magazynu sesji dla wszystkich uwierzytelnionych użytkowników. Upewnij się, że podczas uwierzytelniania wysyłasz klucz (zwykle req.sessionID) do klienta. Poproś klienta o zapisanie tego klucza w pliku cookie.

Podczas połączenia przez gniazdo (lub w dowolnym momencie później) pobierz ten klucz z pliku cookie i wyślij go z powrotem na serwer. Pobierz informacje o sesji w Redis przy użyciu tego klucza. (Weź klucz)

Na przykład:

Strona serwera (z redis jako magazynem sesji):

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

Strona klienta:

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

Po stronie serwera:

//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
Jest nowy interesujący link na ten temat => danielbaulig.de/socket-ioexpress
Alfred

1
Aha! Ten link jest naprawdę dobry. To jest nieaktualne (używa Socket.IO 0.6.3)! Zasadniczo ta sama koncepcja. Pobierz plik cookie, sprawdź w magazynie sesji i uwierzytelnij :)
Shripad Krishna

@NightWolf to powinno działać, ponieważ pobierasz plik cookie w javascript, a nie we flashu (actioncript). GetCookiejest funkcją javascript.
Shripad Krishna

1
@Alfred, ten link wydaje się być teraz martwy :(
Pro Q

Link do @ Alfreda jest ponownie ważny 01.02.2018
Tom

32

Podobał mi się także sposób, w jaki pusherapp tworzy kanały prywatne .wprowadź opis obrazu tutaj

Unikalny identyfikator gniazda jest generowany i wysyłany do przeglądarki przez Pusher. Jest to wysyłane do Twojej aplikacji (1) za pośrednictwem żądania AJAX, które upoważnia użytkownika do dostępu do kanału w ramach Twojego istniejącego systemu uwierzytelniania. Jeśli się powiedzie, aplikacja zwraca ciąg autoryzacji do przeglądarki podpisanej z Tobą kluczem Pusher. Jest to wysyłane do Pusher przez WebSocket, który kończy autoryzację (2), jeśli ciąg autoryzacji jest zgodny.

Ponieważ socket.ioma również unikalny identyfikator socket_id dla każdego gniazda.

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

Użyli podpisanych ciągów autoryzacji do autoryzacji użytkowników.

Jeszcze tego nie zrobiłem socket.io, ale myślę, że może to być całkiem interesująca koncepcja.


3
To jest niesamowite. Ale byłoby łatwiej po prostu używać plików cookie, jeśli serwer aplikacji i serwer WebSocket nie są oddzielone. Ale generalnie chciałbyś rozdzielić te dwa elementy (łatwiej będzie skalować serwer gniazd, jeśli zostaną rozdzielone). Więc jest dobrze :)
Shripad Krishna

1
@Shripad, jesteś całkowicie prawdziwy i bardzo mi się podoba twoja realizacja: P
Alfred

27

Wiem, że jest to trochę stare, ale dla przyszłych czytelników oprócz podejścia polegającego na analizowaniu plików cookie i pobieraniu sesji z magazynu (np. Passport.socketio ) można również rozważyć podejście oparte na tokenach.

W tym przykładzie używam tokenów sieciowych JSON, które są dość standardowe. Musisz przekazać stronie klienta token, w tym przykładzie wyobraź sobie punkt końcowy uwierzytelniania, który zwraca 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});
});

Teraz serwer socket.io można skonfigurować w następujący sposób:

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

Oprogramowanie pośredniczące socket.io-jwt oczekuje tokenu w ciągu zapytania, więc od klienta wystarczy go dołączyć podczas łączenia:

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

Bardziej szczegółowe wyjaśnienie na temat tej metody i plików cookie napisałem tutaj .


Hej! Szybkie pytanie, dlaczego wysyłasz profil z tokenem, jeśli nie można go zdekodować na kliencie?
Carpetfizz

To może. JWT to po prostu base64, podpisany cyfrowo. Klient może go zdekodować, ale nie może sprawdzić poprawności podpisu w tym przykładzie.
José F. Romaniello

3

Oto moja próba wykonania następującego działania:

  • ekspres : 4.14
  • socket.io : 1.5
  • paszport (na sesje): 0.3
  • redis : 2.6 (Naprawdę szybka struktura danych do obsługi sesji; ale możesz też używać innych, takich jak MongoDB. Zachęcam jednak do używania tego do danych sesji + MongoDB do przechowywania innych trwałych danych, takich jak Użytkownicy)

Ponieważ możesz również chcieć dodać kilka żądań API, użyjemy również protokołu http pakietu , aby zarówno HTTP, jak i gniazdo sieciowe działały na tym samym porcie.


server.js

Poniższy fragment zawiera tylko wszystko, czego potrzebujesz, aby skonfigurować poprzednie technologie. Możesz zobaczyć pełną wersję server.js, której użyłem w jednym z moich projektów tutaj .

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

gniazda / index.js

Nasz socketConnectionHandler, po prostu nie lubię umieszczać wszystkiego wewnątrz server.js (nawet jeśli można by to zrobić doskonale), zwłaszcza, że ​​ten plik może szybko zawierać całkiem sporo kodu.

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

Dodatkowy materiał (klient):

Po prostu bardzo podstawowa wersja tego, czym mógłby być klient 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}`);
});

Bibliografia:

Po prostu nie mogłem odwołać się do kodu, więc przeniosłem go tutaj.

1: Jak skonfigurować strategie paszportowe: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration


2

Ten artykuł ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) pokazuje, jak

  • zapisz sesje serwera HTTP w Redis (używając Predis)
  • pobrać te sesje z Redis w node.js za pomocą identyfikatora sesji wysłanego w pliku cookie

Używając tego kodu, możesz je również pobrać z 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

użyj sesji i redis między c / s

// po stronie serwera

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

Wygląda na to, że jeśli po prostu podłączysz ten sam kod, którego używasz do walidacji punktów końcowych Node.js (ale będziesz musiał dostosować wszystkie części, które obsługujesz obiekt żądania), możesz po prostu ponownie użyć swojego tokena do tras.
Nick Pineda

-5

to powinno wystarczyć

//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 w moim przypadku
ZiTAL

8
To nawet nie jest próba odpowiedzi. To nie jest uwierzytelnianie, to po prostu nawiązywanie połączenia.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.