【问题标题】:Authenticate user for socket.io/nodejs验证 socket.io/nodejs 的用户
【发布时间】:2011-09-24 00:39:26
【问题描述】:

我有一个 php 登录,用户输入用户名/密码,它会根据登录信息检查 mysql 数据库。如果经过身份验证,则通过 php 创建会话,用户现在可以使用 php 会话访问系统。我的问题是,一旦他们通过 php/session 进行身份验证,授权用户查看他们是否具有使用 socket.io 访问 nodejs 服务器的正确登录权限的过程是什么?我不希望这个人能够访问 nodejs/socket.io 函数/服务器,除非他们已经通过 php 登录进行了身份验证。

【问题讨论】:

  • 当用户输入用户名/密码时,也将其发送到 socket.io 连接。对 socket.io 使用类似的会话部分,并让他们访问服务器上的更多功能。

标签: php session node.js socket.io


【解决方案1】:

更新

要求:

  1. 首先运行 redis。
  2. 接下来启动 socket.io。
  3. 终于上传/托管 PHP(存档中有依赖项)。

Socket.io

var express = require('express'),
        app         = express.createServer(),
        sio         = require('socket.io'),
        redis   = require("redis"),
    client  = redis.createClient(),
        io          = null;

/**
 *  Used to parse cookie
 */
function parse_cookies(_cookies) {
    var cookies = {};

    _cookies && _cookies.split(';').forEach(function( cookie ) {
        var parts = cookie.split('=');
        cookies[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim();
    });

    return cookies;
}

app.listen(3000, "localhost");
io = sio.listen(app);

io.of('/private').authorization(function (handshakeData, callback) {
        var cookies = parse_cookies(handshakeData.headers.cookie);

        client.get(cookies.PHPSESSID, function (err, reply) {
                handshakeData.identity = reply;
                callback(false, reply !== null);
        });
}).on('connection' , function (socket) {
        socket.emit('identity', socket.handshake.identity);
});

PHP

带有openid认证的php => http://dl.dropbox.com/u/314941/6503745/php.tar.gz

登录后您必须重新加载client.php 进行身份验证


p.s:我真的不喜欢创建另一个可能不安全的密码的概念。我建议你看看openID(例如通过Google),Facebook Connect(仅举几个选项)。

我的问题是一旦他们认证 通过 php/session 会是什么 对用户进行身份验证的过程 看看他们是否有正确的登录名 访问 nodejs 服务器的权限 与socket.io?我不想要那个人 可以访问 nodejs/socket.io 功能/服务器,除非他们有 通过php登录进行身份验证。

将唯一的session_id 添加到允许的 id 列表/集,以便 socket.io 可以authorize(搜索授权功能)该连接。我会让 PHP 使用 redis 与 node.js 通信,因为这将是闪电般的快速/真棒:)。现在我正在伪造来自redis-cli的PHP通信

安装 Redis

Download redis => 现在可以从http://redis.googlecode.com/files/redis-2.2.11.tar.gz下载稳定版

alfred@alfred-laptop:~$ mkdir ~/6502031
alfred@alfred-laptop:~/6502031$ cd ~/6502031/
alfred@alfred-laptop:~/6502031$ tar xfz redis-2.2.11.tar.gz 
alfred@alfred-laptop:~/6502031$ cd redis-2.2.11/src
alfred@alfred-laptop:~/6502031/redis-2.2.11/src$ make # wait couple of seconds

启动 Redis-server

alfred@alfred-laptop:~/6502031/redis-2.2.11/src$ ./redis-server 

Socket.io

npm 依赖项

如果npm尚未安装,请先访问http://npmjs.org

npm install express
npm install socket.io
npm install redis

根据npm ls列出我已经安装的依赖项,你也应该安装它们以防不兼容

alfred@alfred-laptop:~/node/socketio-demo$ npm ls
/home/alfred/node/socketio-demo
├─┬ express@2.3.12 
│ ├── connect@1.5.1 
│ ├── mime@1.2.2 
│ └── qs@0.1.0 
├── hiredis@0.1.12 
├── redis@0.6.0 
└─┬ socket.io@0.7.2 
  ├── policyfile@0.0.3 
  └── socket.io-client@0.7.2 

Code

server.js

var express = require('express'),
        app         = express.createServer(),
        sio         = require('socket.io'),
        redis   = require("redis"),
    client  = redis.createClient(),
        io          = null;

/**
 *  Used to parse cookie
 */
function parse_cookies(_cookies) {
    var cookies = {};

    _cookies && _cookies.split(';').forEach(function( cookie ) {
        var parts = cookie.split('=');
        cookies[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim();
    });

    return cookies;
}

app.listen(3000, "localhost");
io = sio.listen(app);

io.configure(function () {
  function auth (data, fn) {
    var cookies = parse_cookies(data.headers.cookie);
    console.log('PHPSESSID: ' + cookies.PHPSESSID);

        client.sismember('sid', cookies.PHPSESSID, function (err , reply) {
            fn(null, reply);    
        });
  };

  io.set('authorization', auth);
});

io.sockets.on('connection', function (socket) {
  socket.emit('access', 'granted');
});

要运行服务器,只需运行node server.js

client.php

<?php

session_start();

echo "<h1>SID: " . session_id() . "</h1>";
?>
<html>
<head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
    <script src="http://localhost:3000/socket.io/socket.io.js"></script>
</head>
<body>
    <p id="text">access denied</p>
    <script>
        var socket = io.connect('http://localhost:3000/');
        socket.on('access', function (data) {
            $("#text").html(data);
        });
    </script>
</body>

测试认证

当您从 Web 浏览器加载网页(PHP 文件)时,会显示消息 access denied,但是当您将浏览器中也显示的 session_id 添加到 redis 服务器时,将显示消息 access granted。当然通常你不会做任何复制粘贴,而是让 PHP 直接与 Redis 通信。。但是对于这个演示,您将 SID ramom807vt1io3sqvmc8m4via1 放入 redis 中,然后授予访问权限。

alfred@alfred-laptop:~/database/redis-2.2.0-rc4/src$ ./redis-cli 
redis> sadd sid ramom807vt1io3sqvmc8m4via1
(integer) 1
redis> 

【讨论】:

  • 这种方法不会容易受到中间人攻击或模拟 - 假设客户端会将他的 cookie SID 值更改为其他一些 SID(这在当前上下文),因此他将被冒充为其他用户?
  • @Alfred 谢谢你的解释。那么你是不是在我应该为用户更新这条记录的每一页上都说了很多?因为否则这会在一段时间后失败,我会假设由于使用通过 php 重新生成 id。名称 sid 还应该是每个用户的唯一标识符吗?或者唯一标识符是 sid 键本身?
  • 我还看到他们有 php 类/接口来将会话数据存储在 redis 中。听起来这比使用在 mysql 数据库中存储会话的正常方式更快,你同意 Alfred 吗?
  • @yojimbo87。这将很容易受到中间人攻击。你不能在 PHP(单独)中做任何事情。你所有的 PHP 也可能容易受到攻击。您可以/应该通过致电session_regenerate_id 来尽量减少曝光。但在那之后,保护自己免受中间人攻击的唯一方法就是使用 SSL。
  • @Alfred,你真的应该加入 socket.io google 组并帮助他们使用 redis,显然 redis memorystore 将在 0.8 中集成!此外,在所有 redis 帮助下都不错,我看到您参与了很多答案!赞一个!
【解决方案2】:

请记住,会话只是存储在 php 会话目录中的文件。 node.js 从 cookie 中获取 session id 然后检查 session 是否真的存在于 session 目录中不会有问题。要获取会话目录的路径,请参阅 php.ini 中的 session.save_path 指令。

【讨论】:

    【解决方案3】:

    这里是反序列化和 utf8 代码,如果你也想要的话,它最初来自 phpjs.org - 必须稍微编辑一下才能使它与 node.js 一起工作,所以如果你愿意,可以四处寻找和比较

    function utf8_decode (str_data) {
        // http://kevin.vanzonneveld.net
        // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
        // +      input by: Aman Gupta
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   improved by: Norman "zEh" Fuchs
        // +   bugfixed by: hitwork
        // +   bugfixed by: Onno Marsman
        // +      input by: Brett Zamir (http://brett-zamir.me)
        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // *     example 1: utf8_decode('Kevin van Zonneveld');
        // *     returns 1: 'Kevin van Zonneveld'
        var tmp_arr = [],
            i = 0,
            ac = 0,
            c1 = 0,
            c2 = 0,
            c3 = 0;
    
        str_data += '';
    
        while (i < str_data.length) {
            c1 = str_data.charCodeAt(i);
            if (c1 < 128) {
                tmp_arr[ac++] = String.fromCharCode(c1);
                i++;
            } else if (c1 > 191 && c1 < 224) {
                c2 = str_data.charCodeAt(i + 1);
                tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
                i += 2;
            } else {
                c2 = str_data.charCodeAt(i + 1);
                c3 = str_data.charCodeAt(i + 2);
                tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
    
        return tmp_arr.join('');
    }
    exports.utf8_decode = utf8_decode;
    
    function unserialize (data) {
        // http://kevin.vanzonneveld.net
        // +     original by: Arpad Ray (mailto:arpad@php.net)
        // +     improved by: Pedro Tainha (http://www.pedrotainha.com)
        // +     bugfixed by: dptr1988
        // +      revised by: d3x
        // +     improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +        input by: Brett Zamir (http://brett-zamir.me)
        // +     improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +     improved by: Chris
        // +     improved by: James
        // +        input by: Martin (http://www.erlenwiese.de/)
        // +     bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +     improved by: Le Torbi
        // +     input by: kilops
        // +     bugfixed by: Brett Zamir (http://brett-zamir.me)
        // -      depends on: utf8_decode
        // %            note: We feel the main purpose of this function should be to ease the transport of data between php & js
        // %            note: Aiming for PHP-compatibility, we have to translate objects to arrays
        // *       example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}');
        // *       returns 1: ['Kevin', 'van', 'Zonneveld']
        // *       example 2: unserialize('a:3:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";s:7:"surName";s:9:"Zonneveld";}');
        // *       returns 2: {firstName: 'Kevin', midName: 'van', surName: 'Zonneveld'}
        var that = this;
        var utf8Overhead = function (chr) {
            // http://phpjs.org/functions/unserialize:571#comment_95906
            var code = chr.charCodeAt(0);
            if (code < 0x0080) {
                return 0;
            }
            if (code < 0x0800) {
                return 1;
            }
            return 2;
        };
    
    
        var error = function (type, msg, filename, line) {
            console.log('[[[[[[[[[[[[[[[[[[ERROR]]]]]]]]]]]]]]]]]]]','msg:', msg, 'filename:',filename, 'line:',line);
        };
        var read_until = function (data, offset, stopchr) {
                if (stopchr == ';' && !data.match(/;$/)) data += ';';
            var buf = [];
            var chr = data.slice(offset, offset + 1);
            var i = 2;
            while (chr != stopchr) {
                if ((i + offset) > data.length) {
                    error('Error', 'Invalid','php.js','126');
                }
                buf.push(chr);
                chr = data.slice(offset + (i - 1), offset + i);
                i += 1;
                //console.log('i:',i,'offset:',offset, 'data:',data,'chr:',chr,'stopchr:',stopchr);
            }
            return [buf.length, buf.join('')];
        };
        var read_chrs = function (data, offset, length) {
            var buf;
    
            buf = [];
            for (var i = 0; i < length; i++) {
                var chr = data.slice(offset + (i - 1), offset + i);
                buf.push(chr);
                length -= utf8Overhead(chr);
            }
            return [buf.length, buf.join('')];
        };
        var _unserialize = function (data, offset) {
            var readdata;
            var readData;
            var chrs = 0;
            var ccount;
            var stringlength;
            var keyandchrs;
            var keys;
    
            if (!offset) {
                offset = 0;
            }
            var dtype = (data.slice(offset, offset + 1)).toLowerCase();
    
            var dataoffset = offset + 2;
            var typeconvert = function (x) {
                return x;
            };
    
            switch (dtype) {
            case 'i':
                typeconvert = function (x) {
                    return parseInt(x, 10);
                };
                readData = read_until(data, dataoffset, ';');
                chrs = readData[0];
                readdata = readData[1];
                dataoffset += chrs + 1;
                break;
            case 'b':
                typeconvert = function (x) {
                    return parseInt(x, 10) !== 0;
                };
                readData = read_until(data, dataoffset, ';');
                chrs = readData[0];
                readdata = readData[1];
                dataoffset += chrs + 1;
                break;
            case 'd':
                typeconvert = function (x) {
                    return parseFloat(x);
                };
                readData = read_until(data, dataoffset, ';');
                chrs = readData[0];
                readdata = readData[1];
                dataoffset += chrs + 1;
                break;
            case 'n':
                readdata = null;
                break;
            case 's':
                ccount = read_until(data, dataoffset, ':');
                chrs = ccount[0];
                stringlength = ccount[1];
                dataoffset += chrs + 2;
    
                readData = read_chrs(data, dataoffset + 1, parseInt(stringlength, 10));
                chrs = readData[0];
                readdata = readData[1];
                dataoffset += chrs + 2;
                if (chrs != parseInt(stringlength, 10) && chrs != readdata.length) {
                    error('SyntaxError', 'String length mismatch','php.js','206');
                }
    
                // Length was calculated on an utf-8 encoded string
                // so wait with decoding
                readdata = utf8_decode(readdata);
                break;
            case 'a':
                readdata = {};
    
                keyandchrs = read_until(data, dataoffset, ':');
                chrs = keyandchrs[0];
                keys = keyandchrs[1];
                dataoffset += chrs + 2;
    
                for (var i = 0; i < parseInt(keys, 10); i++) {
                    var kprops = _unserialize(data, dataoffset);
                    var kchrs = kprops[1];
                    var key = kprops[2];
                    dataoffset += kchrs;
    
                    var vprops = _unserialize(data, dataoffset);
                    var vchrs = vprops[1];
                    var value = vprops[2];
                    dataoffset += vchrs;
    
                    readdata[key] = value;
                }
    
                dataoffset += 1;
                break;
            default:
                error('SyntaxError', 'Unknown / Unhandled data type(s): ' + dtype,'php.js','238');
                break;
            }
            return [dtype, dataoffset - offset, typeconvert(readdata)];
        };
    
        return _unserialize((data + ''), 0)[2];
    }
    exports.unserialize = unserialize;
    

    【讨论】:

      【解决方案4】:

      我在这里查看解决方案并决定尝试 rcode 所说的,因为它似乎比接受答案的巨大代码墙容易得多。

      最终效果很好,而且很容易做到。

      我确实最终安装了一些我想避免但使用 node 相对容易做到的依赖项。

      在控制台中输入以下内容:

      npm install cookie

      npm install php-unserialize

      此解决方案使用机器上的会话文件 - 您不必更改此行。

      session.save_handler = files

      ^ 在你的 php.ini 文件中应该是这样的(默认)。

      (人们建议使用 memcache,但切换到那个系统似乎很头疼。)

      这是获取会话数据的超级简单代码:

      var cookie = require('cookie');
      var fs = require('fs');
      var phpUnserialize = require('php-unserialize');
      
      //This should point to your php session directory.
      //My php.ini says session.save_path = "${US_ROOTF}/tmp"
      var SESS_PATH = "C:/SomeDirectory/WhereYourPHPIs/tmp/";
      
      io.on('connection', function(socket) {
          //I just check if cookies are a string - may be better method
          if(typeof socket.handshake.headers.cookie === "string") {
              var sid = cookie.parse(socket.handshake.headers.cookie);
              if(typeof sid.PHPSESSID === "undefined") {
                console.log("Undefined PHPSESSID");
              }
              else {
                  console.log("PHP Session ID: " + sid.PHPSESSID);
                  fs.readFile(SESS_PATH + "sess_" + sid.PHPSESSID, 'utf-8', function(err,data) {
                      if(!err) {
                          console.log("Session Data:");
                          var sd = phpUnserialize.unserializeSession(data);
                          console.log(sd);
                      }
                      else {
                         console.log(err);
                      }
                  });
              }
          }
      }
      

      结果:

      编辑:我只是想补充一点,当有人登录并在那里传递凭据时,让 PHP 告诉您的 Node.js 服务器可能会更容易。

      我在另一个答案中解释了如何很容易地做到这一点。

      https://stackoverflow.com/a/49864533/1274820

      【讨论】:

        猜你喜欢
        • 2014-12-14
        • 1970-01-01
        • 2015-07-20
        • 2013-11-02
        • 1970-01-01
        • 1970-01-01
        • 2021-11-18
        • 1970-01-01
        • 2019-05-04
        相关资源
        最近更新 更多