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

123

Я пытаюсь использовать Socket.IO в Node.js, и я пытаюсь разрешить серверу идентифицировать каждого из клиентов Socket.IO. Поскольку код сокета выходит за рамки кода HTTP-сервера, у него нет легкого доступа к отправленной информации запроса, поэтому я предполагаю, что ее нужно будет отправить во время соединения. Как лучше всего

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

2) подтвердить подлинность того, кем они являются (в настоящее время я использую Express, если это упрощает задачу)

Райан
источник

Ответы:

104

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

При подключении к сокету (или в любое время позже) извлеките этот ключ из файла cookie и отправьте его обратно на сервер. Получите информацию о сеансе в Redis с помощью этого ключа. (Получить ключ)

Например:

Сторона сервера (с 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
Alfred
1
Ага! Эта ссылка действительно хороша. Это устарело (использует Socket.IO 0.6.3)! По сути та же концепция. Получить cookie, проверить в хранилище сеансов и
пройти
@NightWolf, он должен работать, потому что вы получаете файл cookie в javascript, а не во флэш-памяти (ActionScript). GetCookieэто функция javascript.
Shripad Krishna
1
@Alfred, эта ссылка, кажется, сейчас мертва :(
Pro Q
Ссылка @ Alfred снова действительна 01.02.2018
Tom
32

Мне также понравилось, как pusherapp делает частные каналы .введите описание изображения здесь

Pusher генерирует уникальный идентификатор сокета и отправляет его браузеру. Он отправляется в ваше приложение (1) через запрос AJAX, который разрешает пользователю доступ к каналу с использованием вашей существующей системы аутентификации. В случае успеха ваше приложение возвращает строку авторизации браузеру, подписанному вашим секретом Pusher. Он отправляется 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 и извлечению сеанса из хранилища (например, паспорт.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 05
Это может. 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}`);

розетки / 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

zurfyx
источник
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));
    });
});
Blade1336
источник
2

использовать сеанс и redis между c / s

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

io.use(function(socket, next) {
 console.log(socket.handshake.headers.cookie); // get here session id and match from redis session data
 next();
});
onplanner
источник
Похоже, что если вы просто подключите тот же код, который используете для проверки своих конечных точек 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)
Dominic
источник
1
io.socket.sessionid в моем случае
ZiTAL
8
Это даже не попытка ответа. Это не аутентификация, это просто соединение.