Аутентифікація Socket.IO


123

Я намагаюся використовувати Socket.IO в Node.js і намагаюся дозволити серверу надати ідентифікацію кожному з клієнтів Socket.IO. Оскільки код сокета знаходиться за межами коду http-сервера, він не має простого доступу до надісланої інформації запиту, тому я припускаю, що його потрібно буде надсилати під час з'єднання. Який найкращий спосіб

1) отримати інформацію на сервер про те, хто підключається через Socket.IO

2) засвідчити, хто вони говорять (зараз я використовую Express, якщо це полегшує справи)

Відповіді:


104

Використовуйте connect-redis і матимете redis в якості магазину сеансів для всіх зареєстрованих користувачів. Переконайтеся, що під час аутентифікації ви надсилаєте клієнту ключ (як правило, req.sessionID). Попросіть клієнта зберігати цей ключ у файлі cookie.

Під час підключення сокета (або будь-коли пізніше) вийміть цей ключ із файлу cookie та відправте його назад на сервер. За допомогою цього ключа виберіть інформацію про сеанс у червоному кольорі. (Клавіша GET)

Наприклад:

Сторона сервера (з Redis як сесія магазину):

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

Сторона клієнта:

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

Сторона сервера:

//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
Є нове цікаве посилання про це => danielbaulig.de/socket-ioexpress
Альфред

1
ага! Це посилання дійсно добре. Це застаріло (використовує Socket.IO 0.6.3)! По суті те саме поняття. Отримати файли cookie, перевірити в магазині сеансів і підтвердити автентифікацію :)
Shripad Krishna

@NightWolf це має працювати, тому що ви отримуєте файл cookie у javascript, а не flash (actioncript). GetCookieє функцією javascript.
Шріпад Кришна

1
@Alfred це посилання начебто мертве :(
Pro Q

@ Посилання Альфреда знову дійсне 2018-02-01
Том

32

Мені також сподобалось те, як pushherapp робить приватні канали .введіть тут опис зображення

Pusher генерує і надсилає в браузер унікальний ідентифікатор сокета. Це надсилається у вашу заявку (1) через запит AJAX, який дає право користувачу на доступ до каналу проти вашої існуючої системи аутентифікації. У разі успіху ваша програма повертає рядок авторизації в браузер, підписаний з вами Pusher secret. Це надсилається на Pusher через WebSocket, який завершує авторизацію (2), якщо рядок авторизації відповідає.

Тому що також socket.ioє унікальний socket_id для кожного сокета.

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

Вони використовували підписані рядки авторизації для авторизації користувачів.

Я ще цього не відображав socket.io, але думаю, що це може бути досить цікава концепція.


3
Це круто. Але було б простіше використовувати файли cookie, якщо ваш сервер додатків і сервер веб-сокетів не розділені. Але ви, як правило, хотіли б розділити два (буде простіше масштабувати сервер сокет, якщо вони будуть розділені). Тож добре :)
Шріпад Кришна

1
@Shripad ви абсолютно правдиві, і мені також дуже подобається ваша реалізація: P
Альфред

27

Я знаю, що це трохи старе, але для майбутніх читачів, крім підходу до розбору файлів cookie та отримання сеансу зі сховища (наприклад, passport.socketio ), ви також можете розглянути підхід на основі лексеми.

У цьому прикладі я використовую веб-маркери JSON, які є досить стандартними. Ви повинні надати клієнтській сторінці маркер, у цьому прикладі уявіть кінцеву точку аутентифікації, яка повертає 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});
});

Тепер ваш сервер socket.io можна налаштувати так:

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

Посереднє програмне забезпечення socket.io-jwt очікує маркер у рядку запиту, тому від клієнта вам потрібно приєднати його лише під час з'єднання:

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

Більш детальне пояснення щодо цього методу та файлів cookie я написав тут .


Гей! Швидке запитання, чому ви надсилаєте профіль із маркером, якщо його неможливо розшифрувати на клієнті?
Carpetfizz

Це може. JWT - це просто base64, цифровий підпис. Клієнт може його розшифрувати, але він не може перевірити підпис у цьому прикладі.
Хосе Ф. Романьялло

3

Ось моя спроба працювати так:

  • експрес : 4.14
  • socket.io : 1.5
  • паспорт (з використанням сеансів): 0,3
  • redis : 2.6 (Дійсно швидка структура даних для обробки сеансів; але ви можете використовувати й інші, як MongoDB. Однак, я рекомендую вам використовувати це для даних сесії + MongoDB для зберігання інших постійних даних, таких як Користувачі)

Оскільки ви можете також додати деякі запити API, ми також використовуватимемо пакет http, щоб HTTP та веб-сокет працювали в одному порту.


server.js

Наступний витяг містить лише все, що потрібно для налаштування попередніх технологій. Ви можете побачити повну версію server.js, яку я використовував в одному зі своїх проектів тут .

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

Наші socketConnectionHandler, мені просто не подобається вкладати все в server.js (навіть якщо ви цілком могли), тим більше, що цей файл може досить швидко містити досить багато коду.

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

Додатковий матеріал (клієнт):

Просто дуже основна версія того, яким може бути клієнт 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}`);
});

Список літератури:

Я просто не міг посилання всередині коду, тому я перемістив його сюди.

1: Як налаштувати свої паспортні стратегії: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration


2

У цій статті ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) показано, як зробити

  • зберігати сеанси HTTP-сервера в Redis (використовуючи Predis)
  • отримати ці сеанси від Redis в node.js за ідентифікатором сеансу, надісланим у файлі cookie

Використовуючи цей код, ви також можете отримати їх у 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

використовувати сеанс і повторно між c / s

// серверна сторона

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

Схоже, що якщо ви просто підключите той самий код, який ви використовуєте для перевірки кінцевих точок Node.js (але вам доведеться налаштувати будь-які частини, якими ви обробляєте об'єкт запиту), ви можете просто використати маркер для своїх маршрутів.
Нік Пінеда

-5

це має робити

//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 у моєму випадку
ZiTAL

8
Це навіть не спроба відповіді. Це не автентифікація, це просто встановлення зв'язку.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.