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

Posted

技术标签:

【中文标题】EventSource 中的 HTTP 授权标头(服务器发送事件)【英文标题】:HTTP Authorization Header in EventSource (Server Sent Events) 【发布时间】:2015-03-26 10:46:06 【问题描述】:

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

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

【问题讨论】:

你能分享你到目前为止的代码吗 【参考方案1】:

我知道你的帖子是一年多以前的事了,但我发现自己在同一条船上,现在有了很好的答案。我希望这可以帮助某人,或者至少给他们一些想法......

Cookie 看起来很简单,但如果有人阻止了 Cookie,会发生什么?我必须提示他们启用 cookie 才能使用该网站。那时他们开始怀疑他们是否可以信任该网站,因为他们出于“安全原因”禁用了 cookie。一直以来,出于安全原因,我希望启用 cookie!

使用 AJAX,可以轻松地通过 SSL 发布身份验证数据,但使用 SSE 则无法做到这一点。我看过很多帖子,人们说“只使用查询字符串”,但我不想通过以纯文本 (example.com/stream?sessionID=idvalue) 发送身份验证数据来危害客户的安全可以窥探。

绞尽脑汁几个小时后,我意识到我可以在不损害客户身份验证数据的情况下实现总体目标。澄清一下,在建立 EventSource 连接时,我还没有发现某种 POST 方法,但它确实允许浏览器在每次重新连接时安全地通过 EventSource 传递身份验证令牌。他们的关键是将所需的 sessionID/token 放入 lastEventID。

用户可以像往常一样使用用户名/密码进行身份验证(或通过 AJAX 发布您保存在本地存储中的令牌)。 AJAX 身份验证过程将传回一个带有短期令牌的 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 与短期令牌连接,PHP 验证短期令牌并将其从数据库中删除,因此它将永远无法再次进行身份验证。当您收到一个 6 位数的代码以登录网上银行时,这有点相似。我们使用 PHP 推送我们从数据库中检索的作为事件 ID 的 REAL 令牌(过期很久)。 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]

【讨论】:

看来你甚至不需要做大部分的工作——让客户端使用它的授权凭据发送一个普通的 POST 请求,比如 /getAuthCodeForEventConnection。服务器向 POST 响应发送回一个短暂的令牌,客户端立即使用 new EventSource($uri?slt=$sltToken) 发送该响应,然后服务器在用户和该特定套接字连接之间建立自己的关联。似乎不是您需要断开连接,或者对最后收到的 id 字段使用一些奇怪的排列。 FYI 查询字符串通过 https 保护,您不想使用它们的原因是它们经常登录到记录器中。见***.com/questions/323200/…【参考方案2】:

EventSource 没有用于将 HTTP 标头发送到服务器的 API。当我使用 SSE 构建实时聊天时,我也遇到了这个问题。

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

【讨论】:

【参考方案3】:

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

所以你可以这样做:

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

【讨论】:

你能提供一个角度的sn-p吗?我没有实现它:l 这个 polyfill 不支持用户定义的标题。你想链接到一个叉子吗? 它已经不是一个 polyfill,因为原生 EventSource 不支持用户定义的标头,所以当你的代码使用原生 EventSource 时,你只会得到一个隐藏的错误/错误。 关于此实现的注释:通读代码后,似乎它用计时器包装了 XMLHttpRequest 以完成类似 EventSource 的结果。不是批评,只是衡量服务器上的连接负载时要考虑的一点。【参考方案4】:

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

eventsource event-source-polyfill
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);
;

【讨论】:

【参考方案5】:

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

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

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

【讨论】:

【参考方案6】:

传递身份验证令牌的另一种方法是通过 URL 作为查询参数,但您应该考虑安全性。还可以通过服务器端的查询参数添加对授权的支持。

【讨论】:

这种情况下可以使用 SSL/TLS,以确保令牌安全可靠。否则 IMO 这个想法是最实用的。 @crollywood 令牌不是“安全可靠的”,因为它可能会随请求一起保存在网络服务器日志中(或从客户端到服务器的可能众多代理之一中)。【参考方案7】:

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

【讨论】:

【参考方案8】:

我浏览了很多帖子,试图弄清楚是否在 EventSource() 调用中发送了身份验证令牌。虽然有允许添加标题的 polyfill 替代方法:https://github.com/whatwg/html/issues/2177 而其他人则提到通过 ssl 发送身份验证令牌。

不要使用 polyfill EventSource() 或通过 ssl 在查询参数中发送身份验证令牌,而是通过 ssl 在 EventSource url 的参数中发送事件源标识符 (eventSrcUUID),如下所示:-

在用户认证时,eventSrcUUID 与服务器上的 sseEmitter 一起生成并放置在 sseEmitterMap 中。

客户端从响应中检索 eventSrcUUID 并使用参数中的 eventSrcUUID 调用 EventSource() 调用。在服务器上,引用 sseEmitterMap 来检索 eventSrc 对象。保存在会话数据中的 sseEmitter 对象用于向客户端发送事件通知。

【讨论】:

以上是关于EventSource 中的 HTTP 授权标头(服务器发送事件)的主要内容,如果未能解决你的问题,请参考以下文章

Angular 5:从拦截器中的 http 响应标头获取授权

Angular 4 HTTP GET 不包括用于 HTTP 标头中的授权的 JWT

下载带有 HTTP“授权”标头集的文件

如何使用 Express 和 Apollo-Server 获取 HTTP 授权标头

如何在自定义授权方 AWS lambda 函数中访问 http 标头

Google Batch API,多个授权标头