Xác thực Socket.IO


123

Tôi đang cố gắng sử dụng Socket.IO trong Node.js và đang cố gắng cho phép máy chủ cung cấp danh tính cho từng máy khách Socket.IO. Vì mã ổ cắm nằm ngoài phạm vi của mã máy chủ http, nó không dễ dàng truy cập vào thông tin yêu cầu được gửi, vì vậy tôi cho rằng nó sẽ cần được gửi lên trong quá trình kết nối. Cách tốt nhất để

1) nhận thông tin đến máy chủ về ai đang kết nối qua Socket.IO

2) xác thực họ nói họ là ai (tôi hiện đang sử dụng Express, nếu điều đó giúp mọi thứ dễ dàng hơn)

Câu trả lời:


104

Sử dụng connect-redis và có redis làm nơi lưu trữ phiên của bạn cho tất cả người dùng đã xác thực. Đảm bảo rằng khi xác thực, bạn gửi khóa (thường là req.sessionID) cho máy khách. Yêu cầu khách hàng lưu trữ khóa này trong cookie.

Trên socket kết nối (hoặc bất cứ lúc nào sau đó) tìm nạp khóa này từ cookie và gửi nó trở lại máy chủ. Tìm nạp thông tin phiên trong redis bằng cách sử dụng phím này. (GET key)

Ví dụ:

Phía máy chủ (với redis làm cửa hàng phiên):

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

Phía khách hàng:

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

Phía máy chủ:

//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
Có một liên kết thú vị mới về điều này => danielbaulig.de/socket-ioexpress
Alfred

1
aha! Liên kết đó thực sự tốt. Điều này đã lỗi thời (sử dụng Socket.IO 0.6.3)! Thực chất là cùng một khái niệm. Fetch cookie, kiểm tra tại cửa hàng phiên và xác thực :)
Shripad Krishna

@NightWolf nó sẽ hoạt động vì bạn đang tìm nạp cookie trong javascript không phải trong flash (actioncript). GetCookielà hàm javascript.
Shripad Krishna

1
@Alfred rằng liên kết đó dường như đã chết :(
Pro Q

Liên kết của @ Alfred hợp lệ trở lại 2018-02-01
Tom

32

Tôi cũng thích cách pusherapp làm kênh tin .nhập mô tả hình ảnh ở đây

Một id socket duy nhất được tạo và gửi đến trình duyệt bởi Pusher. Điều này được gửi đến ứng dụng của bạn (1) thông qua một yêu cầu AJAX cho phép người dùng truy cập kênh dựa trên hệ thống xác thực hiện có của bạn. Nếu thành công, ứng dụng của bạn sẽ trả về một chuỗi ủy quyền cho trình duyệt đã ký với bạn Pusher secret. Điều này được gửi đến Pusher qua WebSocket, nó sẽ hoàn tất việc cấp phép (2) nếu chuỗi ủy quyền khớp.

Bởi vì cũng socket.iocó socket_id duy nhất cho mọi ổ cắm.

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

Họ đã sử dụng chuỗi ủy quyền đã ký để ủy quyền cho người dùng.

Tôi vẫn chưa phản ánh điều này socket.io, nhưng tôi nghĩ nó có thể là một khái niệm khá thú vị.


3
Điều này thật tuyệt. Nhưng sẽ dễ dàng hơn nếu chỉ sử dụng cookie nếu máy chủ ứng dụng và máy chủ websocket của bạn không được tách biệt. Nhưng bạn thường muốn tách biệt cả hai (sẽ dễ dàng mở rộng máy chủ ổ cắm nếu tách riêng). Vì vậy, nó là tốt :)
Shripad Krishna

1
@Shripad bạn là hoàn toàn đúng sự thật và tôi cũng rất thích thực hiện của bạn: P
Alfred

27

Tôi biết điều này hơi cũ, nhưng đối với những độc giả trong tương lai, ngoài phương pháp phân tích cú pháp cookie và truy xuất phiên từ bộ nhớ (ví dụ: passport.socketio ), bạn cũng có thể xem xét phương pháp dựa trên mã thông báo.

Trong ví dụ này, tôi sử dụng Mã thông báo web JSON khá chuẩn. Bạn phải cung cấp cho trang khách hàng mã thông báo, trong ví dụ này, hãy tưởng tượng một điểm cuối xác thực trả về 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});
});

Bây giờ, máy chủ socket.io của bạn có thể được cấu hình như sau:

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

Phần mềm trung gian socket.io-jwt mong đợi mã thông báo trong một chuỗi truy vấn, vì vậy từ máy khách, bạn chỉ phải đính kèm nó khi kết nối:

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

Tôi đã viết một giải thích chi tiết hơn về phương pháp này và cookie ở đây .


Chào! Câu hỏi nhanh, tại sao bạn gửi hồ sơ với mã thông báo nếu nó không thể được giải mã trên máy khách?
Carpetfizz

Nó có thể. JWT chỉ là base64, được ký kỹ thuật số. Máy khách có thể giải mã nó, nhưng nó không thể xác thực chữ ký trong ví dụ này.
José F. Romaniello

3

Đây là nỗ lực của tôi để làm việc sau:

  • thể hiện : 4,14
  • socket.io : 1.5
  • hộ chiếu (sử dụng phiên): 0,3
  • redis : 2.6 (Cấu trúc dữ liệu thực sự nhanh để xử lý các phiên; nhưng bạn cũng có thể sử dụng các phiên khác như MongoDB. Tuy nhiên, tôi khuyến khích bạn sử dụng cấu trúc này cho dữ liệu phiên + MongoDB để lưu trữ dữ liệu liên tục khác như Người dùng)

Vì bạn có thể muốn thêm một số yêu cầu API, chúng tôi cũng sẽ sử dụng gói http để có cả HTTP và ổ cắm Web hoạt động trong cùng một cổng.


server.js

Phần trích xuất sau chỉ bao gồm mọi thứ bạn cần để thiết lập các công nghệ trước đó. Bạn có thể xem phiên bản server.js hoàn chỉnh mà tôi đã sử dụng trong một trong những dự án của mình tại đây .

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

Của chúng tôi socketConnectionHandler, tôi chỉ không thích đặt mọi thứ bên trong server.js (mặc dù bạn hoàn toàn có thể làm được), đặc biệt là vì tệp này có thể chứa khá nhiều mã khá nhanh.

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

Vật liệu phụ (khách hàng):

Chỉ là một phiên bản rất cơ bản của ứng dụng JavaScript socket.io có thể là:

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

Người giới thiệu:

Tôi chỉ không thể tham chiếu bên trong mã, vì vậy tôi đã chuyển nó vào đây.

1: Cách thiết lập chiến lược Hộ chiếu của bạn: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration


2

Bài viết này ( http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/ ) cho biết cách

  • lưu trữ các phiên của máy chủ HTTP trong Redis (sử dụng Predis)
  • nhận các phiên này từ Redis trong node.js bằng id phiên được gửi trong cookie

Sử dụng mã này, bạn cũng có thể lấy chúng trong 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

sử dụng phiên và chuyển đổi giữa các c / s

// phía máy chủ

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

Có vẻ như nếu bạn chỉ cắm cùng một mã bạn đang sử dụng để xác thực điểm cuối Node.js của mình (nhưng bạn sẽ phải tinh chỉnh bất kỳ phần nào bạn đang xử lý đối tượng yêu cầu), bạn có thể chỉ cần sử dụng lại mã thông báo của mình cho các tuyến đường của mình.
Nick Pineda

-5

điều này nên làm nó

//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 trong trường hợp của tôi
ZiTAL

8
Đây thậm chí không phải là một nỗ lực để tìm câu trả lời. Đây không phải là xác thực mà chỉ đơn giản là tạo kết nối.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.