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

Alv*_*nte 30 javascript html5 http websocket angularjs

我需要为HTML5 EventSource设置一个Authorization标头.由于Websockets出现后服务器发送事件似乎被废弃,我找不到任何有用的文档.我已经发现的方法是在URL中传递授权数据......但我不喜欢这种方法.

我正在使用AngularJS并在$ httpProvider上设置拦截器,但是AngularJS没有拦截EventSource,所以我无法添加任何头.

Ste*_*ven 13

自从提出这个问题以来,EventSource 领域发生了很多事情。

现在所有主流浏览器都支持获取流。

这意味着您现在可以EventSource在这些流之上实现。
这似乎是标头从未添加到规范中的主要原因EventSource

这种基于获取的实现的一个示例EventSourcehttps://github.com/Azure/fetch-event-source ( npm install @microsoft/fetch-event-source)。


小智 11

此polyfill添加了Authorization Header支持:https://github.com/Yaffle/EventSource/

所以你可以这样做:

new EventSource("https://domain/stream", { authorizationHeader: "Bearer ..." });
Run Code Online (Sandbox Code Playgroud)

  • 关于此实现的注释:仔细阅读代码后,看起来它包装了带有计时器的XMLHttpRequest以完成类似EventSource的结果。不要批评,只是权衡服务器上的连接负载时要考虑的一点。 (4认同)
  • 它不是polyfill,因为本机EventSource不支持用户定义的标头,因此当您的代码将使用本机EventSource时,您仅会得到隐藏的错误/错误。 (2认同)

sri*_*igi 10

EventSource没有用于将HTTP标头发送到服务器的API.当我使用SSE构建实时聊天时,我也在努力解决这个问题.

但是,如果您的SSE服务器与您的身份验证服务器是同一台服务器,我认为会自动发送cookie.


Man*_*tha 10

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);
};
Run Code Online (Sandbox Code Playgroud)


小智 9

我意识到你的帖子已经超过一年了,但我发现自己在同一条船上,现在有了很好的答案.我希望这可以帮助某人,或者至少给他们一些想法......

Cookies似乎很容易,但如果有人阻止cookie会发生什么?我必须提示他们启用cookie才能使用该网站.在那时,他们开始怀疑他们是否可以信任该网站,因为他们因"安全原因"禁用了cookie.一直以来,出于安全考虑,我希望启用cookie!

使用AJAX,可以轻松地通过SSL POST验证数据,但这对SSE来说是不可能的.我看过许多帖子,然后人们说"只是使用查询字符串",但我不想通过以纯文本(example.com/stream?sessionID=idvalue)发送auth数据来破坏客户的安全性可以窥探.

在绞尽脑汁几个小时后,我意识到我可以在不影响客户身份验证数据的情况下实现整体目标.为了澄清,我在建立EventSource连接时没有发现一些POST方法,但它确实允许浏览器在每次重新连接时安全地传递带有EventSource的身份验证令牌.它们的关键是将所需的sessionID/token添加到lastEventID中.

用户可以像往常一样使用用户名/密码进行身份验证(或通过AJAX POSTing您保存在localstorage中的令牌).AJAX身份验证过程将使用短期令牌(在60秒内过期,或在使用时)传回一个JSON对象,该对象将保存在您所需的后端(例如: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();
        }
    })
Run Code Online (Sandbox Code Playgroud)

在相应的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;
        }
Run Code Online (Sandbox Code Playgroud)

SSE连接短生命令牌,PHP验证短生命令牌并将其从数据库中删除,因此它永远不会再次使用AUTH.当您收到6位数的代码登录网上银行时,这有点类似.我们使用PHP来推送我们从数据库中检索的REAL令牌(稍后会过期)作为事件ID.Javascript不一定需要对此事件执行任何操作 - 服务器将自动结束连接,但如果您想要使用它,可以收听该事件.

此时,自PHP完成脚本以来,SSE连接已经结束.但是,浏览器将自动重新建立连接(通常为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]
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,查询字符串通过 https 进行保护,您不想使用它们的原因是它们经常记录在记录器中。请参阅 /sf/ask/22624031/ (7认同)
  • 似乎您甚至不需要做其中的很大一部分——让客户端使用它的授权凭据向 /getAuthCodeForEventConnection 发送一个正常的 POST 请求。服务器将一个短期令牌发送回 POST 响应,客户端立即将其发送到 new EventSource(`${uri}?slt=${sltToken})` 服务器然后在用户之间建立自己的关联和那个特定的套接字连接。您似乎不需要断开连接,也不需要对最后接收到的 id 字段使用一些奇怪的排列。 (2认同)

小智 5

如果您使用事件源 polyfill 的这个分支,您将能够以类似于 rafaelzlisboa 描述的方式添加授权标头:https : //github.com/AlexGalays/EventSource#923b9a0998fcfd7753040e09aa83764b3cc0230d

Ï 不知道您是否可以像在 rafaelzlisboa 的示例中那样提供身份验证标头作为第二个参数,我通过创建标头对象并将我的授权标头放入其中来使其工作,如下所示:

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