Socket.IO-Authentifizierung

123

Ich versuche, Socket.IO in Node.js zu verwenden, und ich versuche, dem Server zu ermöglichen, jedem der Socket.IO-Clients eine Identität zuzuweisen. Da der Socket-Code außerhalb des Bereichs des http-Server-Codes liegt, hat er keinen einfachen Zugriff auf die gesendeten Anforderungsinformationen. Ich gehe daher davon aus, dass er während der Verbindung gesendet werden muss. Was ist der beste Weg zu

1) Informationen über die Verbindung über Socket.IO an den Server senden

2) Authentifizieren Sie, wer sie sagen, dass sie sind (ich verwende derzeit Express, wenn dies die Dinge einfacher macht)

Ryan
quelle

Antworten:

104

Verwenden Sie connect-redis und haben Sie redis als Sitzungsspeicher für alle authentifizierten Benutzer. Stellen Sie bei der Authentifizierung sicher, dass Sie den Schlüssel (normalerweise req.sessionID) an den Client senden. Lassen Sie den Client diesen Schlüssel in einem Cookie speichern.

Bei Socket-Verbindung (oder später) holen Sie diesen Schlüssel aus dem Cookie und senden ihn an den Server zurück. Rufen Sie mit dieser Taste die Sitzungsinformationen in Redis ab. (GET-Taste)

Z.B:

Serverseite (mit Redis als Sitzungsspeicher):

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

Client-Seite:

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

Serverseite:

//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
quelle
3
Es gibt einen neuen interessanten Link zu diesem => danielbaulig.de/socket-ioexpress
Alfred
1
Aha! Dieser Link ist wirklich gut. Dies ist veraltet (verwendet Socket.IO 0.6.3)! Im Wesentlichen das gleiche Konzept. Holen Sie sich Cookie, checken Sie im Session Store und authentifizieren Sie sich :)
Shripad Krishna
@ NightWolf sollte funktionieren, da Sie das Cookie in Javascript und nicht in Flash (Actionscript) abrufen. GetCookieist Javascript-Funktion.
Shripad Krishna
1
@ Alfred dieser Link scheint jetzt tot zu sein :(
Pro Q
@ Alfred's Link ist wieder gültig 2018-02-01
Tom
32

Ich mochte auch die Art und Weise pusherapp tut private Kanäle .Geben Sie hier die Bildbeschreibung ein

Eine eindeutige Socket-ID wird generiert und von Pusher an den Browser gesendet. Dies wird über eine AJAX-Anfrage an Ihre Anwendung (1) gesendet, die den Benutzer berechtigt, über Ihr vorhandenes Authentifizierungssystem auf den Kanal zuzugreifen. Bei Erfolg gibt Ihre Anwendung eine Autorisierungszeichenfolge an den mit Ihrem Pusher-Geheimnis signierten Browser zurück. Dies wird über das WebSocket an Pusher gesendet, wodurch die Autorisierung (2) abgeschlossen wird, wenn die Autorisierungszeichenfolge übereinstimmt.

Da socket.iohat auch eindeutige socket_id für jeden Socket.

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

Sie verwendeten signierte Autorisierungszeichenfolgen , um Benutzer zu autorisieren.

Ich habe dies noch nicht gespiegelt socket.io, aber ich denke, es könnte ein ziemlich interessantes Konzept sein.

Alfred
quelle
3
Das ist fantastisch. Es wäre jedoch einfacher, nur Cookies zu verwenden, wenn Ihr App-Server und Ihr Websocket-Server nicht getrennt sind. Im Allgemeinen möchten Sie die beiden jedoch trennen (es ist einfacher, den Socket-Server zu skalieren, wenn er getrennt ist). Also ist es gut :)
Shripad Krishna
1
@ Shripad Sie sind völlig wahr und ich mag auch Ihre Implementierung: P
Alfred
27

Ich weiß, dass dies etwas alt ist, aber für zukünftige Leser könnten Sie neben dem Ansatz, Cookies zu analysieren und die Sitzung aus dem Speicher abzurufen (z. B. passport.socketio ), auch einen tokenbasierten Ansatz in Betracht ziehen.

In diesem Beispiel verwende ich JSON-Web-Token, die ziemlich Standard sind. Sie müssen der Client-Seite das Token geben. Stellen Sie sich in diesem Beispiel einen Authentifizierungsendpunkt vor, der JWT zurückgibt:

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

Jetzt kann Ihr socket.io-Server wie folgt konfiguriert werden:

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

Die Middleware socket.io-jwt erwartet das Token in einer Abfragezeichenfolge, sodass Sie es vom Client nur beim Herstellen einer Verbindung anhängen müssen:

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

Ich schrieb eine ausführlichere Erklärung über diese Methode und Cookies hier .

José F. Romaniello
quelle
Hallo! Kurze Frage, warum senden Sie das Profil mit dem Token, wenn es auf dem Client nicht dekodiert werden kann?
Carpetfizz
Es kann. JWT ist nur base64, digital signiert. Der Client kann es dekodieren, aber die Signatur in diesem Beispiel nicht validieren.
José F. Romaniello
3

Hier ist mein Versuch, Folgendes zum Laufen zu bringen:

  • Express : 4.14
  • socket.io : 1.5
  • Reisepass (unter Verwendung von Sitzungen): 0.3
  • redis : 2.6 (Sehr schnelle Datenstruktur für die Verarbeitung von Sitzungen; Sie können jedoch auch andere wie MongoDB verwenden. Ich empfehle Ihnen jedoch, diese für Sitzungsdaten + MongoDB zu verwenden, um andere persistente Daten wie Benutzer zu speichern.)

Da Sie möglicherweise auch einige API-Anforderungen hinzufügen möchten, verwenden wir auch das http- Paket, damit sowohl HTTP als auch Web-Socket am selben Port arbeiten.


server.js

Der folgende Auszug enthält nur alles, was Sie zum Einrichten der vorherigen Technologien benötigen. Hier können Sie die vollständige Version von server.js sehen, die ich in einem meiner Projekte verwendet habe .

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

Wir socketConnectionHandlermögen es einfach nicht, alles in server.js zu platzieren (obwohl Sie es perfekt könnten), zumal diese Datei ziemlich schnell eine Menge Code enthalten kann.

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

Zusätzliches Material (Kunde):

Nur eine sehr einfache Version dessen, was der JavaScript-Client socket.io sein könnte:

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

Verweise:

Ich konnte im Code einfach nicht referenzieren, also habe ich ihn hierher verschoben.

1: So richten Sie Ihre Passport-Strategien ein: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration

zurfyx
quelle
2

Dieser Artikel ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) zeigt, wie es geht

  • Speichern von Sitzungen des HTTP-Servers in Redis (mit Predis)
  • Rufen Sie diese Sitzungen von Redis in node.js anhand der in einem Cookie gesendeten Sitzungs-ID ab

Mit diesem Code können Sie sie auch in socket.io abrufen.

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
quelle
2

benutze session und redis zwischen c / s

// Serverseite

io.use(function(socket, next) {
 console.log(socket.handshake.headers.cookie); // get here session id and match from redis session data
 next();
});
Planer
quelle
Wenn Sie nur den gleichen Code eingeben, den Sie zum Überprüfen Ihrer Node.js-Endpunkte verwenden (Sie müssen jedoch alle Teile optimieren, die Sie für das Anforderungsobjekt verwenden), können Sie Ihr Token einfach für Ihre Routen wiederverwenden.
Nick Pineda
-5

das sollte es tun

//server side

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

//client side

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

console.log(io.sessionid)
dominant
quelle
1
io.socket.sessionid in meinem Fall
ZiTAL
8
Dies ist nicht einmal ein Versuch, eine Antwort zu finden. Dies ist keine Authentifizierung, sondern stellt lediglich eine Verbindung her.