websocket EventSource中的HTTP授权标头(服务器发送的事件)

vjhs03f7  于 2023-05-07  发布在  其他
关注(0)|答案(9)|浏览(607)

我需要将Authorization头设置为HTML5 EventSource。自从Websockets出现后,服务器发送事件似乎被废弃了,我找不到任何有用的文档。我已经找到的方法是在url中传递授权数据。但我不喜欢这种方法
我正在使用AngularJS并在$httpProvider上设置拦截器,但是EventSource没有被AngularJS拦截,所以我不能添加任何头部。

dfuffjeb

dfuffjeb1#

我知道你的帖子是一年前的事了,但我发现自己和现在的好答案一样。我希望这能帮助到一些人,或者至少给予他们一些建议。
Cookie似乎很容易,但如果有人阻止Cookie会发生什么?我将不得不提示他们启用cookie来使用网站。在这一点上,他们开始怀疑他们是否可以信任该网站,因为他们出于“安全原因”禁用了cookie。一直以来,我希望cookie启用安全原因!
使用 AJAX ,人们可以很容易地通过SSL POST身份验证数据,但这在SSE中是不可能的。我看过很多帖子,人们说,“只要使用querystring”,但我不想通过以纯文本发送身份验证数据来危及客户的安全性(example.com/stream?sessionID=idvalue),有人可以窥探。
在绞尽脑汁了几个小时之后,我意识到我可以在不损害客户的授权数据的情况下完成总体目标。澄清一下,我还没有发现在建立EventSource连接时POST的方法,但它确实允许浏览器在每次重新连接时安全地通过EventSource传递身份验证令牌。关键是将所需的sessionID/令牌放入lastEventID。
用户可以像往常一样使用用户名/密码进行身份验证(或者通过 AJAX POST保存在本地存储中的令牌)。 AJAX auth进程将返回一个JSON对象,其中包含一个短期令牌(在60秒内到期,或在使用时到期),该令牌将保存在您所需的后端(例如:mySQL)沿着更持久的令牌。此时,您可以启动SSE连接,如下所示:

qString = "?slt=" + "value-that-expires-within-seconds";
    streamURL = "http://example.com/stream.php";
    var streamSource = new EventSource(streamURL + qString);

    streamSource.addEventListener('auth',function(e) {
        var authStatus = JSON.parse(e.data);
        if (authStatus.session !== 'valid') {
            qString = "";
            streamSource.close();
        }
    })

在相应的PHP中,你可以这样做:

header("Content-Type: text/event-stream\n");
        ob_end_flush();
        ob_start();

        if (isThisShortLivedTokenValid($_GET["slt"])) {
            // The short-lived-token is still valid... so we will lookup
            // the value of the corresponding longer-lasting token and
            // IMMEDIATELY invalidate the short-lived-token in the db.
            sendMsg($realToken,'auth','session','valid');
            exit;
        } else if (isThisRealTokenValid($_SERVER["HTTP_LAST_EVENT_ID"])){
            while (1) {
                // normal code goes here
                // if ($someCondition == 'newDataAvailable') sendMsg($realToken,'chat','msg-id','msg-content');
            }
        } else {
            http_response_code(404); // stop the browser from reconnecting.
            exit; //quit the PHP script and don't send anything.
        }

        function sendMsg($id, $event, $key, $val) {
            echo "{" . PHP_EOL;
            echo "event: " . $event . PHP_EOL;
            echo "id: $id" . PHP_EOL;
            echo 'data: {"' . $key . '" : "' . $val . '"}' . PHP_EOL;
            echo "}" . PHP_EOL;
            echo PHP_EOL;
            ob_flush();
            flush();
        }

        function isThisShortLivedTokenValid($sltValue) {
            //stuff to connect to DB and determine if the
            //value is still valid for authentication
            return $dbResult == $sltValue ? TRUE : FALSE;
        }

SSE与short-lived-token连接,PHP对short-lived-token进行验证并将其从DB中删除,因此它将永远无法再次进行AUTH。这有点类似于当你收到一个6位数的代码登录网上银行。我们使用PHP推送真实的令牌(过期时间较晚),该令牌是我们从数据库中检索的事件ID。JavaScript实际上并不需要对这个事件做任何事情--服务器会自动结束连接,但是如果你想对它做更多的事情,你可以监听这个事件。
此时,SSE连接已经结束,因为PHP完成了脚本。但是,浏览器将自动重新建立连接(通常需要3秒)。这一次,它将发送lastEventId...我们在断开连接之前将其设置为令牌值。在下一次连接时,此值将用作我们的令牌,应用程序将按预期运行。只要在发送消息/事件时开始使用真实令牌作为事件ID,就没有必要删除连接。此令牌值在浏览器接收到它时以及在每次后续连接到服务器时都通过SSL进行完全加密的传输。“以明文”传输的值在我们接收和使用它的几秒钟内就过期了,任何发现它的人都不能再使用它。如果有人试图使用它,他们将收到一个404响应
如果您已经将事件流ID用于其他目的,这可能无法“开箱即用”,除非您将auth-token和以前使用的值连接起来,并将其拆分为变量,以便对应用的其余部分透明。类似于:

// when sending data, send both values
    $sseID = $token_value . "_" . $previouslyUsedID;
    sendMsg($sseID,'chat','msg-id','msg-content');

    // when a new connection is established, break apart the values
    $manyIDs = explode("_", $_SERVER["HTTP_LAST_EVENT_ID"])
    $token_value = $manyIDs[0]
    $previouslyUsedID = $manyIDs[1]
qf9go6mv

qf9go6mv2#

EventSource没有API用于向服务器发送HTTP标头。当我使用SSE构建实时聊天时,我也在努力解决这个问题。
但是,我认为如果您的SSE服务器与您的身份验证服务器是同一服务器,则cookie将自动发送。

rqmkfv5c

rqmkfv5c3#

此polyfill添加了授权标头支持:https://github.com/Yaffle/EventSource/
因此,您可以:

new EventSource("https://domain/stream", { authorizationHeader: "Bearer ..." });
bkhjykvo

bkhjykvo4#

window.EventSource似乎还不支持传递额外的头。好消息是,还有一些流行的EventSource实现支持附加头。其中一些如下:

const eventSource = new EventSource(resoureUrl, {
            headers: {
                'Authorization': 'Bearer ' + authorizationToken
            }
        });

eventSource.onmessage = result => {
    const data = JSON.parse(result.data);
    console.log('Data: ', data);
};

eventSource.onerror = err => {
    console.log('EventSource error: ', err);
};
eni9jsuy

eni9jsuy5#

传递auth令牌的另一种方法是通过URL作为查询参数,但您应该考虑安全性。同时在服务器端增加了通过查询参数进行授权的支持。

o2g1uqev

o2g1uqev6#

如果你使用事件源polyfill的这个分支,你将能够像rafaelzlisboa描述的那样添加授权头:https://github.com/AlexGalays/EventSource#923b9a0998fcfd7753040e09aa83764b3cc0230d
π不知道你是否可以像rafaelzlisboa的例子一样提供身份验证头作为第二个参数,我通过创建一个headers对象,并将我的授权头放在那里,就像这样:
new EventSource("https://domain/stream", { headers: { Authorization: Bearer.... }});

edqdpe6u

edqdpe6u7#

我通过在sse调用之前额外的rest解决了这个问题,这个rest是普通的rest,需要与SSE调用和获取以及OTP响应所需的相同的安全协议。并发送此OTP以查看调用查询参数,并在Web过滤器中验证此OTP,并将其替换为身份验证标头。

chhkpiq4

chhkpiq48#

我浏览了很多帖子,试图弄清楚是否在EventSource()调用中发送auth令牌。尽管有允许添加标题的polyfill替代方案:https://github.com/whatwg/html/issues/2177,而其他人提到通过SSL发送认证令牌。
不是使用polyfill EventSource()或通过ssl在查询参数中发送auth令牌,而是通过ssl在EventSource url的参数中发送eventSource标识符(eventSrcUUID),如下所示:
在用户身份验证时,eventSrcUUID与sseEmitter沿着在服务器上生成,并放置在sseEmitterMap中。
客户端从响应中检索eventSrcUUID,并在参数中使用eventSrcUUID调用EventSource()调用。在服务器上,引用sseEmitterMap来检索eventSrc对象。保存在会话数据中的sseEmitter对象用于向客户端发送事件通知。

ffscu2ro

ffscu2ro9#

我认为this EventSource polyfill是我们目前最好的选择,因为它基于Fetch API,它比原始的EventSource API更符合时代,这是Chrome开发人员在最近几年决定维护的。

相关问题