การตรวจสอบความถูกต้องของ Socket.IO


123

ฉันกำลังพยายามใช้ Socket.IO ใน Node.js และกำลังพยายามอนุญาตให้เซิร์ฟเวอร์ระบุตัวตนให้กับไคลเอ็นต์ Socket.IO แต่ละตัว เนื่องจากรหัสซ็อกเก็ตอยู่นอกขอบเขตของรหัสเซิร์ฟเวอร์ http จึงไม่สามารถเข้าถึงข้อมูลคำขอที่ส่งได้โดยง่ายดังนั้นฉันจึงคิดว่าจะต้องส่งขึ้นในระหว่างการเชื่อมต่อ วิธีที่ดีที่สุดคืออะไร

1) รับข้อมูลไปยังเซิร์ฟเวอร์เกี่ยวกับผู้ที่เชื่อมต่อผ่าน Socket.IO

2) รับรองความถูกต้องว่าพวกเขาเป็นใคร (ฉันกำลังใช้ Express อยู่ถ้าสิ่งนั้นง่ายขึ้น)

คำตอบ:


104

ใช้ connect-redis และมี redis เป็นที่เก็บเซสชันของคุณสำหรับผู้ใช้ที่ได้รับการพิสูจน์ตัวตนทั้งหมด ตรวจสอบให้แน่ใจว่าคุณได้ส่งคีย์ (ปกติคือ req.sessionID) ไปยังไคลเอนต์ ให้ลูกค้าเก็บคีย์นี้ไว้ในคุกกี้

ในการเชื่อมต่อซ็อกเก็ต (หรือเมื่อใดก็ได้ในภายหลัง) ดึงคีย์นี้จากคุกกี้และส่งกลับไปที่เซิร์ฟเวอร์ ดึงข้อมูลเซสชันใน 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
AHA! ลิงค์นั้นดีจริงๆ สิ่งนี้ล้าสมัยแล้ว (ใช้ Socket.IO 0.6.3)! แนวคิดเดียวกันเป็นหลัก ดึงคุกกี้ตรวจสอบในร้านเซสชั่นและตรวจสอบสิทธิ์ :)
Shripad Krishna

@NightWolf ควรใช้งานได้เนื่องจากคุณกำลังดึงคุกกี้ในจาวาสคริปต์ไม่ใช่แฟลช (actionscript) GetCookieคือฟังก์ชันจาวาสคริปต์
Shripad Krishna

1
@ อัลเฟรดลิงค์นั้นดูเหมือนจะตายไปแล้ว :(
Pro Q

ลิงก์ของ @ Alfred ใช้ได้อีกครั้ง 2018-02-01
Tom

32

ฉันชอบวิธีที่pusherappทำช่องส่วนตัวด้วยใส่คำอธิบายภาพที่นี่

รหัสซ็อกเก็ตเฉพาะถูกสร้างขึ้นและส่งไปยังเบราว์เซอร์โดย Pusher สิ่งนี้ถูกส่งไปยังแอปพลิเคชันของคุณ (1) ผ่านคำขอ AJAX ซึ่งอนุญาตให้ผู้ใช้เข้าถึงช่องทางกับระบบการตรวจสอบสิทธิ์ที่มีอยู่ของคุณ หากแอปพลิเคชันของคุณประสบความสำเร็จจะส่งคืนสตริงการอนุญาตไปยังเบราว์เซอร์ที่ลงนามด้วย Pusher secret สิ่งนี้ถูกส่งไปยัง Pusher ผ่าน WebSocket ซึ่งจะทำให้การอนุญาต (2) เสร็จสมบูรณ์หากสตริงการอนุญาตตรงกัน

เนื่องจากยังsocket.ioมี socket_id ที่ไม่ซ้ำกันสำหรับทุกซ็อกเก็ต

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

พวกเขาใช้สตริงการอนุญาตที่ลงนามเพื่อให้สิทธิ์ผู้ใช้

ฉันยังไม่ได้สะท้อนสิ่งนี้socket.ioแต่ฉันคิดว่ามันน่าจะเป็นแนวคิดที่น่าสนใจทีเดียว


3
นี่มันเจ๋งมาก. แต่จะง่ายกว่าถ้าใช้คุกกี้หากเซิร์ฟเวอร์แอปและเซิร์ฟเวอร์ websocket ของคุณไม่ได้แยกออกจากกัน แต่โดยทั่วไปแล้วคุณต้องการแยกทั้งสอง (จะง่ายกว่าในการปรับขนาดเซิร์ฟเวอร์ซ็อกเก็ตหากแยกออกจากกัน) ดังนั้นจึงเป็นสิ่งที่ดี :)
Shripad Krishna

1
@Shripad คุณเป็นจริงอย่างสมบูรณ์และฉันก็ชอบการใช้งานของคุณมาก: P
Alfred

27

ฉันรู้ว่ามันค่อนข้างเก่า แต่สำหรับผู้อ่านในอนาคตนอกเหนือจากวิธีการแยกวิเคราะห์คุกกี้และการเรียกเซสชันจากที่เก็บข้อมูล (เช่นpassport.socketio ) คุณอาจพิจารณาวิธีการที่ใช้โทเค็น

ในตัวอย่างนี้ฉันใช้ JSON Web Tokens ซึ่งค่อนข้างได้มาตรฐาน คุณต้องมอบโทเค็นให้กับเพจไคลเอ็นต์ในตัวอย่างนี้ลองจินตนาการถึงจุดสิ้นสุดการพิสูจน์ตัวตนที่ส่งคืน 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
});

ผมเขียนคำอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการนี้และคุกกี้ที่นี่


เฮ้! คำถามด่วนทำไมคุณถึงส่งโปรไฟล์พร้อมโทเค็นหากไม่สามารถถอดรหัสบนไคลเอนต์ได้
Carpetfizz

มันสามารถ. JWT เป็นเพียง base64 ที่ลงนามแบบดิจิทัล ไคลเอนต์สามารถถอดรหัสได้ แต่ไม่สามารถตรวจสอบลายเซ็นในตัวอย่างนี้ได้
José F. Romaniello

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: วิธีตั้งค่ากลยุทธ์ Passport ของคุณ: 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 โดยรหัสเซสชันที่ส่งในคุกกี้

เมื่อใช้รหัสนี้คุณจะสามารถรับได้ใน 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

ใช้เซสชันและ 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();
});

ดูเหมือนว่าหากคุณเพียงแค่เสียบรหัสเดียวกับที่คุณใช้เพื่อตรวจสอบความถูกต้องของจุดสิ้นสุด Node.js ของคุณ (แต่คุณจะต้องปรับแต่งส่วนใด ๆ ที่คุณกำลังจัดการกับวัตถุที่ร้องขอ) คุณก็สามารถใช้โทเค็นของคุณซ้ำสำหรับเส้นทางของคุณได้
Nick Pineda

-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
นี่ไม่ใช่ความพยายามหาคำตอบ นี่ไม่ใช่การตรวจสอบความถูกต้องนี่เป็นเพียงการเชื่อมต่อ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.