如何在PHP Web服务器和Node.js服务器上的Socket.io之间创建握手?

9
我有一个运行在10.0.4.18服务器的node.js 4.0 websocket,端口号为8020。我使用socket.io v1.3、express和express-session实现了我的websocket。
项目定义
我需要能够为每个从PHP应用连接到它的用户创建一个socket.io会话。当用户发送第一个HTTP请求后,我将使用从PHP传递到socket.io的令牌对其进行身份验证。
在用户通过身份验证后,我需要在socket.io会话中保存一些个人数据以便以后重复使用。每次用户刷新PHP应用程序时,socket.io都需要知道已经创建的会话数据。
问题
每当用户重新加载/刷新“连接来源”的PHP页面时,会话数据就会丢失。服务器无法知道连接属于先前创建的XYZ会话。
我不确定如何在PHP和node.js之间创建握手,使两个服务器可以交换唯一数据以将socket.io会话绑定到该数据上。
非常接近问题

我在浏览器中打开了此链接https://10.0.4.18:8020/set/MikeA。 "这根据路由代码直接为我创建了一个来自node.js的会话"

然后我使用PHP连接到了websocket,现在我可以看到会话正常保持!我能够在浏览器中打开多个选项卡,并且预期会话仍然存在。

它这次能够正常工作的原因是因为URLhttps://10.0.4.18:8020/set/MikeA建立了会话并将其在我的浏览器和会话之间绑定,从那里我能够使用express-socket.io-session包https://www.npmjs.com/package/express-socket.io-session从socket.io读写我的会话。

但是,如果我不手动使用url创建会话,则会话仅对一次页面加载有效。每次重新加载页面时,会话都会被销毁,就像从未存在过一样!

问题

我需要开发与https://10.0.4.18:8020/set/MikeA通过websocket连接时相同的行为,当从socket.io连接时。
如何在PHP服务器和socket.io之间建立握手,使得每次重新加载PHP页面或打开新的浏览器选项卡时,两个服务器都可以将会话数据绑定到正确的用户?
这是我的websocket代码。
var app = require('express')(),
    https = require('https'),
    fs = require('fs'),
    session = require('express-session'),
    sharedsession = require("express-socket.io-session"),
    fileStore = require('session-file-store')(session),
    base64url = require('base64url'),
    cookieParser = require("cookie-parser"),
    env = require('./modules/config');


var server = https.createServer(
    {
        key: fs.readFileSync('certs/key.pem'),
        cert: fs.readFileSync('certs/cert.pem')
    }, app).listen(env.socket.port, env.socket.host, function () {
    console.log('\033[2J');
    console.log('Websocket is running at https://%s:%s', server.address().address, server.address().port);
});

var io = require('socket.io')(server);

var icwsReq = require('./modules/icws/request.js'),
    icwsConn = require('./modules/icws/connection.js'),
    icwsInter = require('./modules/icws/interactions.js'),
    sessionValidator = require('./modules/validator.js');

var icwsRequest = new icwsReq();
var sessionChecker = new sessionValidator();

var sessionStoreFile = new fileStore({path: './tmp/sessions'});

var clients = {};

var sessionOptions = {
        store: sessionStoreFile,
        secret: env.session.secret,
        name: env.session.name,
        rolling: true,
        saveUninitialized: false,
        resave: true,
        unset: 'keep',
        cookie: {
            maxAge: 60 * 60 * 1000
        }
    };

var sessionMiddleware = session(sessionOptions);

app.use(sessionMiddleware); // session support for the app


//Set access control headers on every express route.
app.use(function (req, res, next){
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
    next();
});


// Use shared session middleware for socket.io
io.use(sharedsession(sessionMiddleware, {
    autoSave: true
})); 

//Middleware for authorizing a user before establishing a connection
io.use(function(socket, next) {

    var myIP = socket.request.socket.remoteAddress || '';
    var token = socket.handshake.query.tokenId || '';
    var session = socket.handshake.session || {};

    if(!session && !token){
        console.log('Log: No session and no token!');
        return next(new Error('No tken/session found'));
    }

    if(!token){
        console.log('Log: token was not found');
        return next(new Error('Token not found'));
    }

    //SessionID should be defined on a page reload
    console.log('IP Address: ' + myIP + '      SessionID: ' + socket.handshake.sessionID);

    //allow any user that is authorized
    if(session && session.autherized && token == session.token){
        console.log('Log: you are good to go');
        return next(new Error('You are good to go'));
    }

    //if the client changed their token "client logged out"
    //terminate the open session before opening a new one
    if (session.autherized && token != session.token){

    var decodedToken = base64url.decode(token);

    sessionChecker.validateData(decodedToken, myIP, env.session.duration, function(isValid, icws){

        if(!isValid){
            console.log('Log: token could not be validated!');
            return next(new Error('Token could not be validated!'));
        }

        session.authorized = true;
        session.icwsServer = icws.host;
        session.icwsPort = icws.port;
        session.token = token;
        session.icwsSessionId = null;
        session.icwsToken = null;

        icwsRequest.setConnection(icws.host, icws.port);
        var icwsConnection = new icwsConn(icwsRequest);

        session.save(function(){
            console.log('Log: new connection to websocket!');
            return next();
        });
    });

});


io.on('connection', function (socket) { 

    console.log('Connection is validated and ready for action!');

    var socketId = socket.id;

    if(!socket.handshake.sessionID){
        console.log('sessionId was not found');
        return false;
    }

    var sessionID = socket.handshake.sessionID;
    var userCons = clients[sessionID] || [];

    //Add this socket to the user's connection
    if(userCons.indexOf(socketId) == -1){
        userCons.push(socketId);
    }

    clients[sessionID] = userCons;

    socket.on('chat', function(msg){

        for (var key in clients[sessionID]) {
          if (clients[sessionID].hasOwnProperty(key)) {
            var id = clients[sessionID][key];
            console.log('Client Said: ' + msg);
            io.to(id).emit('chat', {message: 'Server Said: ' + msg});
          }
        }
    });


    socket.on('disconnect', function(msg){
        console.log('Closing sessionID: ' + sessionID);
        var userCons = clients[sessionID] || [];

        var index = userCons.indexOf(socketId);

        if(index > -1){
            userCons.splice(index, 1);
            console.log('Removed Disconnect Message: ' + msg);
        } else {
            console.log('Disconnect Message: ' + msg);
        }

    }); 

    socket.on('error', function(msg){
        console.log('Error Message: ' + msg);
    }); 

});


app.get('/', function (req, res) {
    res.send('welcome: ' + req.sessionID);
});

app.get('/read', function (req, res) {
    res.send('welcome: ' + req.session.name);
});

app.get('/set/:name', function (req, res) {
    req.session.name = req.params.name;
    res.send('welcome: ' + req.session.name);
});

这是我从PHP服务器连接到WebSocket的方法。
<!doctype html>
<html lang="en-US">
  <head>
    <title>Socket.IO chat</title>
    <meta charset="utf-8">
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>

    <script src="https://10.0.4.18:8020/socket.io/socket.io.js"></script>
    <script type="text/javascript" src="/js/jquery-2.1.0.min.js"></script>
    <script>

        $(function(){
            var socket = io.connect('https://10.0.4.18:8020', {secure: true, port: 8020,  query : 'PHP generated token that will be used to authenticate the user from node.js'});

           //When the "send" button is clicked
            $('#f').click(function(e){
                e.preventDefault();
                var message = $('#m').val().trim();
                if( message  == ''){
                    return false;
                }

                socket.emit('chat', message);
                $('#m').val('');
            });

            socket.on('chat', function(msg){
                $('#messages').append($('<li>').text(msg.message));
            });

        });

    </script>

  </head>
  <body>
    <ul id="messages"></ul>
    <form action="" id="f">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

我认为这是不可能的,因为当页面重新加载或浏览器跳转到另一个页面时,所有的JavaScript实例都会死亡,因为它们是每个页面独有的,需要建立新的套接字连接,如果您想从新连接中识别用户,最好的方法是使用会话,当用户第一次登录或连接时,服务器将创建一个唯一的会话ID并发送给用户并在本地存储,因此每次客户端启动新连接时,它会向服务器识别自己的会话ID,并且服务器应该能够识别它。 - Daniel Mendel
@DanielMendel 我该如何将会话传递回客户端?从node.js服务器开始,第一次请求后我该如何加载特定的sessionID? - Junior
1个回答

2

在浏览器中存储令牌,并在每个来自php的请求中发送它,使用类似于授权承载者的授权标头:

Authorization: Bearer token

每当Node.js服务器收到请求时,您可以比较令牌以确定对应的用户并获取数据。您可以在表格中存储userid和token之间的关系及其过期日期,并在每次登录时刷新令牌。此外,如果您不想使用表格,可以使用JWT将数据存储在令牌中。您可以在此处查找更多信息:http://jwt.io/

我完全同意Alex7的观点。事实上,之前我使用socket.io node加入了php会话,并且它对我来说运行得很好,但是我在服务器上附加了许多与物理php会话耦合的代码...这是一个可扩展性问题,另一个原因是...如果您可以编写未耦合的后端响应,例如Json格式,您可以使用客户端性能来呈现视图,而不是在每个请求中使用服务器发送响应视图...这将提高性能,也可以更快地将其打包到应用程序中(如ionic,phonegap,react等)。 - Joaquin Javi
@Alex7 谢谢您的反馈。如果我理解正确,我应该在我的node.js上有一个关系对象,它保持会话ID和标头中传递的令牌之间的关系。然后在中间件中添加一个检查,如果令牌已经存在,则加载现有会话。如果没有,则生成一个新的会话并将其存储在关系对象中。但是,我该如何强制express-session生成一个新的会话ID呢? - Junior
@MikeA 我认为你正在寻找Session.regenerate(),你可以在这里找到它 https://github.com/expressjs/session#sessionregenerate。但是有两种情况:第一种,你让密钥在1小时后过期,并且你需要重新生成以避免用户反复登录;第二种,你让会话在1周后过期,当用户必须再次登录时,你不需要重新生成,只需创建一个新的会话。你可以创建一个机制,当你升级应用程序时,删除存储sessionID和token的对象,所有用户都必须重新登录,这样你就不会有旧数据。 - Alex7
@Alex7,我尝试使用regenerate()但是它没有起作用。我收到这个错误socket.session.regenerate(function(err) { ^ TypeError: Cannot read property 'regenerate' of undefined at Array.<anonymous> - Junior
@MikeA 我对Node不是很熟悉,但从他们的文档中所理解的来看,我认为会话重建必须在请求上进行,比如 req.session.regenerate。我不确定您的 socket 是否相当于他们示例中的 req 变量。也许您使用 req.session 生成会话,并将其保存在 socket.session 中。因此,要重建它,您必须执行类似的操作。我不确定是否还能为您提供更多帮助,希望这可以帮到您。 - Alex7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接