【问题标题】:Socket.IO AuthenticationSocket.IO 认证
【发布时间】:2011-06-12 20:13:09
【问题描述】:

我正在尝试在 Node.js 中使用 Socket.IO,并尝试允许服务器为每个 Socket.IO 客户端提供身份。由于套接字代码超出了 http 服务器代码的范围,因此无法轻松访问发送的请求信息,因此我假设它需要在连接期间发送。最好的方法是什么

1) 向服务器获取有关谁通过 Socket.IO 连接的信息

2) 验证他们所说的身份(我目前正在使用 Express,如果这能让事情变得更容易的话)

【问题讨论】:

    标签: authentication node.js express socket.io


    【解决方案1】:

    在c/s之间使用session和Redis

    服务器端

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

    【讨论】:

    • 看起来如果你只是插入你用来验证你的 Node.js 端点的相同代码(但你必须调整你正在处理请求对象的任何部分)你可以重用你的您路线的令牌。
    【解决方案2】:

    这是我尝试进行以下工作的尝试:

    • 快递:4.14
    • socket.io:1.5
    • 护照(使用会话):0.3
    • redis:2.6(非常快速的数据结构来处理会话;但您也可以使用其他类似 MongoDB。但是,我鼓励您将它用于会话数据 + MongoDB 来存储其他持久性数据,如用户)

    由于您可能还想添加一些 API 请求,我们还将使用 http 包让 HTTP 和 Web 套接字在同一个端口上工作。


    server.js

    以下摘录仅包含设置先前技术所需的所有内容。你可以看到我在我的一个项目here中使用的完整的 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:如何设置您的 Passport 策略:https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration

    【讨论】:

      【解决方案3】:

      我知道这有点老了,但对于未来的读者来说,除了解析 cookie 和从存储中检索会话的方法(例如 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
      });
      

      我写了关于这个方法和cookieshere的更详细的解释。

      【讨论】:

      • 嘿!快速提问,如果无法在客户端解码,为什么还要发送带有令牌的配置文件?
      • 可以的。 JWT 只是 base64 ,数字签名。客户端可以解码,但无法验证本例中的签名。
      • @JoséF.Romaniello 您的文章已下架,不过我喜欢您的方法。
      【解决方案4】:

      使用 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;
            });
          }
      
        });
      });
      

      【讨论】:

      • 有一个关于这个的新链接 => danielbaulig.de/socket-ioexpress
      • 啊哈!那个链接真的很好。这是过时的(使用 Socket.IO 0.6.3)!本质上是同一个概念。获取 cookie,签入会话存储并进行身份验证 :)
      • @NightWolf 它应该可以工作,因为您是在 javascript 中获取 cookie,而不是在 flash (actionscript) 中。 GetCookie 是javascript函数。
      • @Alfred 该链接现在似乎已失效:(
      • @Alfred 的链接再次有效 2018-02-01
      【解决方案5】:

      这篇文章 (http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/) 展示了如何

      • 在 Redis 中存储 HTTP 服务器的会话(使用 Predis)
      • 通过在 cookie 中发送的会话 ID 从 node.js 中的 Redis 获取这些会话

      使用此代码,您也可以在 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));
          });
      });
      

      【讨论】:

        【解决方案6】:

        应该这样做

        //server side
        
        io.sockets.on('connection', function (con) {
          console.log(con.id)
        })
        
        //client side
        
        var io = io.connect('http://...')
        
        console.log(io.sessionid)
        

        【讨论】:

        • io.socket.sessionid 在我的情况下
        • 这甚至不是一个答案的尝试。这不是身份验证,这只是建立连接。
        【解决方案7】:

        我也喜欢pusherapp 做private channels. 的方式

        生成一个唯一的套接字 id 并且 Pusher 发送到浏览器。这是 通过发送到您的应用程序 (1) 授权用户的 AJAX 请求 访问您的频道 现有的认证系统。如果 成功您的应用程序返回一个 浏览器的授权字符串 与您签署的 Pusher 秘密。这是 通过 WebSocket 发送到 Pusher, 完成授权 (2) 如果授权字符串匹配。

        因为socket.io 对于每个套接字也有唯一的 socket_id。

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

        他们使用signed authorization strings 来授权用户。

        我还没有将这个镜像到socket.io,但我认为这可能是一个非常有趣的概念。

        【讨论】:

        • 这太棒了。但是,如果您的应用服务器和 websocket 服务器没有分开,那么只使用 cookie 会更容易。但是您通常希望将两者分开(如果分开,扩展套接字服务器会更容易)。所以很好:)
        • @Shripad 你是完全正确的,我也很喜欢你的实现:P
        猜你喜欢
        • 2016-01-12
        • 2016-04-24
        • 2018-04-14
        • 1970-01-01
        • 2017-12-02
        • 1970-01-01
        • 2014-12-14
        • 1970-01-01
        相关资源
        最近更新 更多