【问题标题】: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】:

        我也喜欢pusherappprivate 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
        相关资源
        最近更新 更多