Socket.IO Otentikasi

123

Saya mencoba menggunakan Socket.IO di Node.js, dan mencoba mengizinkan server untuk memberikan identitas kepada setiap klien Socket.IO. Karena kode soket berada di luar cakupan kode server http, kode soket tidak memiliki akses yang mudah ke informasi permintaan yang dikirim, jadi saya berasumsi bahwa itu perlu dikirim selama koneksi. Apa cara terbaik untuk melakukannya

1) dapatkan informasi ke server tentang siapa yang terhubung melalui Socket.IO

2) mengotentikasi siapa yang mereka katakan (saya saat ini menggunakan Express, jika itu membuat segalanya lebih mudah)

Ryan
sumber

Jawaban:

104

Gunakan connect-redis dan miliki redis sebagai penyimpanan sesi Anda untuk semua pengguna yang diautentikasi. Pastikan pada otentikasi Anda mengirim kunci (biasanya req.sessionID) ke klien. Minta klien menyimpan kunci ini dalam cookie.

Pada soket terhubung (atau kapan saja nanti) ambil kunci ini dari cookie dan kirimkan kembali ke server. Ambil informasi sesi di redis menggunakan kunci ini. (GET key)

Misalnya:

Sisi server (dengan redis sebagai penyimpanan sesi):

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

Sisi klien:

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

Sisi server:

//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
sumber
3
Ada tautan baru yang menarik tentang ini => danielbaulig.de/socket-ioexpress
Alfred
1
aha! Tautan itu sangat bagus. Ini sudah usang (menggunakan Socket.IO 0.6.3)! Konsep dasarnya sama. Ambil cookie, check in penyimpanan sesi dan otentikasi :)
Shripad Krishna
@NightWolf seharusnya berfungsi karena Anda mengambil cookie di javascript, bukan di flash (skrip tindakan). GetCookieadalah fungsi javascript.
Shripad Krishna
1
@Alfred tautan itu sepertinya sudah mati sekarang :(
Pro Q
Tautan @ Alfred berlaku lagi 2018-02-01
Tom
32

Saya juga menyukai cara pusherapp melakukan saluran pribadi .masukkan deskripsi gambar di sini

ID soket unik dibuat dan dikirim ke browser oleh Pusher. Ini dikirim ke aplikasi Anda (1) melalui permintaan AJAX yang memberi otorisasi kepada pengguna untuk mengakses saluran terhadap sistem otentikasi yang ada. Jika berhasil aplikasi Anda mengembalikan string otorisasi ke browser yang ditandatangani dengan rahasia pendorong Anda. Ini dikirim ke Pendorong melalui WebSocket, yang menyelesaikan otorisasi (2) jika string otorisasi cocok.

Karena juga socket.iomemiliki socket_id unik untuk setiap soket.

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

Mereka menggunakan string otorisasi yang ditandatangani untuk memberi otorisasi kepada pengguna.

Saya belum mencerminkan ini socket.io, tapi saya pikir ini bisa menjadi konsep yang cukup menarik.

Alfred
sumber
3
Ini luar biasa. Tetapi akan lebih mudah untuk hanya menggunakan cookie jika server aplikasi dan server websocket Anda tidak dipisahkan. Tetapi biasanya Anda ingin memisahkan keduanya (akan lebih mudah untuk menskalakan server soket jika terpisah). Jadi itu bagus :)
Shripad Krishna
1
@Shripad Anda benar-benar benar dan saya juga sangat menyukai penerapan Anda: P
Alfred
27

Saya tahu ini agak lama, tetapi untuk pembaca di masa mendatang selain pendekatan parsing cookie dan mengambil sesi dari penyimpanan (misalnya passport.socketio ) Anda juga dapat mempertimbangkan pendekatan berbasis token.

Dalam contoh ini saya menggunakan Token Web JSON yang cukup standar. Anda harus memberikan token kepada halaman klien, dalam contoh ini bayangkan titik akhir otentikasi yang mengembalikan 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: '[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});
});

Sekarang, server socket.io Anda dapat dikonfigurasi sebagai berikut:

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

Middleware socket.io-jwt mengharapkan token dalam string kueri, jadi dari klien Anda hanya perlu melampirkannya saat menghubungkan:

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

Saya menulis penjelasan yang lebih rinci tentang metode dan cookie ini di sini .

José F. Romaniello
sumber
Hei! Pertanyaan singkat, mengapa Anda mengirim profil dengan token jika tidak dapat didekodekan pada klien?
Carpetfizz
Bisa. JWT hanyalah base64, bertanda tangan digital. Klien dapat mendekodekannya, tetapi tidak dapat memvalidasi tanda tangan dalam contoh ini.
José F. Romaniello
3

Inilah upaya saya agar yang berikut ini berfungsi:

  • ekspres : 4.14
  • socket.io : 1.5
  • paspor (menggunakan sesi): 0.3
  • redis : 2.6 (Struktur data yang sangat cepat untuk menangani sesi; tetapi Anda juga dapat menggunakan yang lain seperti MongoDB. Namun, saya mendorong Anda untuk menggunakan ini untuk data sesi + MongoDB untuk menyimpan data persisten lainnya seperti Pengguna)

Karena Anda mungkin ingin menambahkan beberapa permintaan API juga, kami juga akan menggunakan paket http agar HTTP dan soket Web bekerja di port yang sama.


server.js

Ekstrak berikut hanya mencakup semua yang Anda butuhkan untuk mengatur teknologi sebelumnya. Anda dapat melihat versi server.js lengkap yang saya gunakan di salah satu proyek saya di sini .

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

Kami socketConnectionHandler, saya hanya tidak suka meletakkan semuanya di dalam server.js (meskipun Anda bisa), terutama karena file ini dapat berisi cukup banyak kode dengan cukup cepat.

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

Materi tambahan (klien):

Hanya versi yang sangat dasar dari apa yang bisa menjadi klien 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}`);
});

Referensi:

Saya hanya tidak bisa merujuk ke dalam kode, jadi saya memindahkannya ke sini.

1: Cara mengatur strategi Paspor Anda: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration

zurfyx
sumber
2

Artikel ini ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) menunjukkan cara

  • menyimpan sesi dari server HTTP di Redis (menggunakan Predis)
  • dapatkan sesi ini dari Redis di node.js dengan id sesi yang dikirim dalam cookie

Dengan menggunakan kode ini, Anda juga bisa mendapatkannya di 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
sumber
2

gunakan sesi dan redis antara c / s

// sisi server

io.use(function(socket, next) {
 console.log(socket.handshake.headers.cookie); // get here session id and match from redis session data
 next();
});
onplanner
sumber
Sepertinya jika Anda hanya memasukkan kode yang sama yang Anda gunakan untuk memvalidasi titik akhir Node.js Anda (tetapi Anda harus menyesuaikan bagian mana pun yang Anda tangani objek permintaan) Anda bisa menggunakan kembali token Anda untuk rute Anda.
Nick Pineda
-5

ini harus melakukannya

//server side

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

//client side

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

console.log(io.sessionid)
dominic
sumber
1
io.socket.sessionid dalam kasus saya
ZiTAL
8
Ini bahkan bukan upaya untuk menjawab. Ini bukan otentikasi, ini hanya membuat koneksi.