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

我需要将 Authorization 头设置为 HTML5 EventSource。由于自 Websockets 出现以来,Server Sent Events 似乎已经停用,因此我找不到任何有用的文档。我已经找到的方法是在 url 中传递授权数据... ... 但我不喜欢这种方法。

我正在使用 AngularJS 并在 $httpProvider 上设置拦截器,但是 AngularJS 没有拦截 EventSource,所以我不能添加任何头文件。

58418 次浏览

EventSource 没有用于将 HTTP 头发送到服务器的 空气污染指数。当我使用 SSE 构建实时聊天时,我也在与这个问题作斗争。

然而,我认为如果您的 SSE 服务器与您的身份验证服务器是同一个服务器,Cookie 将被自动发送。

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

这种填充添加了授权头支持: https://github.com/Yaffle/EventSource/

所以你可以这样做:

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

我知道你的帖子已经过去一年多了,但我发现自己现在的处境和你一样。我希望这能帮到某些人,或者至少给他们一些想法..。

饼干看起来很简单,但是如果有人阻止了饼干,会发生什么呢?我必须提示他们启用 Cookie 来使用站点。这时,他们开始怀疑是否可以信任这个网站,因为他们出于“安全原因”禁用了 cookies。与此同时,出于安全原因,我希望启用 cookie!

通过使用 AJAX,可以很容易地通过 SSL 发布身份验证数据,但是在 SSE 中不可能做到这一点。我看过很多帖子,人们会说,“只要使用 querystring”,但我不想因为发送普通文本( example.com/stream?sessionid=idvalue )的 auth 数据而损害客户的安全,因为有人可能会窥探这些数据。

在绞尽脑汁思考了几个小时后,我意识到我可以在不损害客户认证数据的情况下实现总体目标。澄清一下,我还没有发现在建立 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 连接到短命令牌,PHP 根据短命令牌进行验证,并从 DB 中删除它,这样它就再也不能使用 AUTH 了。这有点类似于当你得到一个6位数字的代码短信登录到网上银行。我们使用 PHP 将从数据库中检索到的 REAL 令牌(过期时间要晚得多)作为事件 ID。Javascript 实际上没有必要对这个事件做任何事情——服务器将自动终止连接,但是如果您想对它做更多的事情,您可以监听这个事件。

此时,自 PHP 完成脚本以来,SSE 连接已经结束。但是,浏览器会自动重新建立连接(通常为3秒)。这一次,它将发送 lastEventId... 我们在放弃连接之前将其设置为令牌值。在下一个连接中,这个值将被用作我们的令牌,应用程序将按预期运行。只要在发送消息/事件时开始使用真正的令牌作为事件 ID,就没有必要删除连接。这个令牌值是通过 SSL 完全加密传输的,无论是在浏览器接收到它时,还是在随后到服务器的每个连接中。当我们接收和使用它的时候,传输的数值已经在几秒钟内过期,不能再被任何发现它的人使用。如果有人试图使用它,他们将收到一个 404响应

如果您已经将事件流 ID 用于其他用途,那么这可能无法“即时”工作,除非您将认证令牌和之前使用的值连接起来,并将其拆分为变量,以便对应用程序的其他部分是透明的。比如:

    // 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]

如果您使用事件源 polyfill 的这个分支,您将能够像 rafaelzlisboa 描述的那样添加授权头: Https://github.com/alexgalays/eventsource#923b9a0998fcfd7753040e09aa83764b3cc0230d

不知道你能不能像 rafaelzlisboa 的例子那样,提供验证头作为第二个参数,我创建了一个头对象,把我的验证头放在这里,像这样:

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

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);
};

为了弄清楚 auth 令牌是否在 EventSource ()调用中发送,我阅读了相当多的文章。尽管有一些多填充的替代方案允许添加头文件: < a href = “ https://github.com/whatwg/html/issues/2177”rel = “ nofollow norefrer”> https://github.com/whatwg/html/issues/2177 而其他人提到通过 ssl 发送认证令牌。

不要使用 polyfill EventSource ()或通过 ssl 在查询参数中发送认证令牌,而是通过 ssl 在 EventSource url 的参数中发送一个 eventSource 标识符(eventSrcUUID) ,如下所示:-

在用户身份验证时,将在服务器上生成 eventSrcUUID 和 sseEmitter,并将其放在 sseEmitterMap 中。

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

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