Javascript EventSource 中的 HTTP 授权标头(服务器发送的事件)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28176933/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
HTTP Authorization Header in EventSource (Server Sent Events)
提问by Alvaro Luis Bustamante
I need to set an Authorization header to an HTML5 EventSource. As Server Sent Events seems to be disused since Websockets appeared, I cannot find any useful documentation. The approach I have already found is to pass the authorization data within the url... but I don't like this method.
我需要将 Authorization 标头设置为 HTML5 EventSource。自 Websockets 出现以来,服务器发送的事件似乎已被废弃,我找不到任何有用的文档。我已经找到的方法是在 url 中传递授权数据......但我不喜欢这种方法。
I am using AngularJS and set interceptors on $httpProvider, but the EventSource is not intercepted by AngularJS, so I cannot add any header.
我正在使用 AngularJS 并在 $httpProvider 上设置拦截器,但是 AngularJS 没有拦截 EventSource,因此我无法添加任何标头。
回答by KeithMcFly
I realize your post was over a year ago, but I found myself in the same boat with now good answers. I'm hoping this may help someone, or at least give them some ideas...
我意识到你的帖子是一年多前的,但我发现自己在同一条船上,现在有了很好的答案。我希望这可以帮助某人,或者至少给他们一些想法......
Cookies seem easy enough, but what happens if someone is blocking cookies? I would have to prompt them to enable cookies to use the site. At that point they start to wonder if they can trust the site since they disabled cookies for 'security reasons'. All the while, I want cookies enabled for security reasons!
Cookie 看起来很简单,但是如果有人阻止 Cookie 会发生什么?我必须提示他们启用 cookie 才能使用该站点。那时他们开始怀疑他们是否可以信任该站点,因为他们出于“安全原因”禁用了 cookie。一直以来,出于安全原因,我都希望启用 cookie!
Using AJAX, one can easily POST authentication data over SSL, but that's just not possible with SSE. I've seen many posts where people then say, "just use the querystring", but I don't want to compromise a customer's security by sending the auth data in plain text (example.com/stream?sessionID=idvalue) which someone could snoop.
使用 AJAX,人们可以轻松地通过 SSL POST 身份验证数据,但这对于 SSE 来说是不可能的。我看过很多帖子,然后人们说“只使用查询字符串”,但我不想通过以纯文本(example.com/stream?sessionID=idvalue)发送身份验证数据来损害客户的安全可以窥探。
After racking my brain for a couple hours I realized that I CAN accomplish the the overall goal without compromising the customer's auth data. Just to clarify, I haven't discovered some way to POST when establishing an EventSource connection, but it does allow the browser to securely pass an authentication token with the EventSource each time it reconnects. They key is to get the desired sessionID/token into the lastEventID.
在绞尽脑汁几个小时后,我意识到我可以在不损害客户身份验证数据的情况下完成总体目标。澄清一下,我在建立 EventSource 连接时还没有发现某种 POST 方法,但它确实允许浏览器在每次重新连接时安全地通过 EventSource 传递身份验证令牌。他们的关键是将所需的 sessionID/token 放入 lastEventID。
The user can authenticate as usual with a username/password (or by AJAX POSTing a token you keep in localstorage). The AJAX auth process will pass back a JSON object with a short-lived-token (expires in 60 seconds, or when used) which would be saved in your desired backend (eg: mySQL) along with a longer-lasting token. At this point you initiate your SSE connection like:
用户可以像往常一样使用用户名/密码进行身份验证(或通过 AJAX 发布您保留在本地存储中的令牌)。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();
}
})
In the corresponding PHP you would do something like this:
在相应的 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 connects with the short-lived-token, PHP validates against the short-lived-token and deletes it from the DB so it will never be able to AUTH again. This is somewhat similar when you get texted a 6-digit code to login to online banking. We use PHP to push the REAL token (that expires much later) which we retrieved from the database as the event ID. It's not really necessary for Javascript to do anything with this event-- the server will end the connection automatically, but you can listen to the event if you want to do more with it.
SSE 与短期令牌连接,PHP 针对短期令牌进行验证并将其从数据库中删除,因此它将永远无法再次进行 AUTH。当您收到一个 6 位数的代码以登录网上银行时,这有点类似。我们使用 PHP 来推送我们从数据库中作为事件 ID 检索到的 REAL 令牌(过期很久)。Javascript 不需要对这个事件做任何事情——服务器会自动结束连接,但如果你想用它做更多事情,你可以收听这个事件。
At this point, the SSE connection has ended since PHP finished the script. However, the browser will automatically reestablish the connection (usually with 3 seconds). This time, it will send the lastEventId... which we set to the token value before we dropped the connection. On the next connection, this value will be used as our token and the app will run as expected. It's not really necessary to drop the connection as long as you start using the real token as the event-ID when you send messages/events. This token value is transmitted completely encrypted over SSL both when the browser receives it, and in every subsequent connection to the server. The value that was transmitted 'in the clear' was expired within seconds from when we receive & used it and it can no longer be used by anyone that discovers it. If someone does attempt to use it they will receive a 404 RESPONSE.
此时,自 PHP 完成脚本以来,SSE 连接已经结束。但是,浏览器会自动重新建立连接(通常需要 3 秒)。这一次,它将发送 lastEventId... 我们在断开连接之前将其设置为令牌值。在下一次连接时,此值将用作我们的令牌,应用程序将按预期运行。只要您在发送消息/事件时开始使用真实令牌作为事件 ID,就没有必要断开连接。此令牌值在浏览器接收到它时以及在与服务器的每个后续连接中都通过 SSL 完全加密传输。“明文”传输的值在我们收到并使用它后几秒钟内就过期了,任何发现它的人都不能再使用它。404 响应。
If you already use the event-stream ID for some other purpose, this may not work 'out of the box' unless you concatenate the auth-token and the previously used value, and split it into variables so it's transparent to the rest of the app. Something like:
如果您已经将事件流 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]
回答by rafaelzlisboa
This polyfill adds Authorization Header support: https://github.com/Yaffle/EventSource/
这个 polyfill 添加了 Authorization Header 支持:https: //github.com/Yaffle/EventSource/
So you can do:
所以你可以这样做:
new EventSource("https://domain/stream", { authorizationHeader: "Bearer ..." });
回答by srigi
EventSource doesn't have an APIfor sending HTTP headers to server. I was struggling with this problem too when I was building realtime-chat using SSE.
EventSource 没有用于将 HTTP 标头发送到服务器的API。当我使用 SSE 构建实时聊天时,我也遇到了这个问题。
However I think cookies will be sent automatically if your SSE server is the same server as your authentication server.
但是,如果您的 SSE 服务器与您的身份验证服务器是同一台服务器,我认为 cookie 将自动发送。
回答by Sergey Leyko
the other way to pass auth token is through the URL as query param, but you should take security in consideration. Also add support of authorization through query param on the sever side.
传递身份验证令牌的另一种方法是通过 URL 作为查询参数,但您应该考虑安全性。还通过服务器端的查询参数添加授权支持。
回答by Jeroen Dragt
If you use this fork of the event-source polyfill you will be able to add authorization headers similarly to the way rafaelzlisboa describes: https://github.com/AlexGalays/EventSource#923b9a0998fcfd7753040e09aa83764b3cc0230d
如果您使用事件源 polyfill 的这个分支,您将能够以类似于 rafaelzlisboa 描述的方式添加授权标头:https: //github.com/AlexGalays/EventSource#923b9a0998fcfd7753040e09aa83764b3cc0230d
? don't know if you can provide the authentication header as a second argument like in rafaelzlisboa's example, I got it to work by creating a headers object, and putting my authorization header in there, like this:
? 不知道您是否可以像在 rafaelzlisboa 的示例中一样提供身份验证标头作为第二个参数,我通过创建标头对象并将我的授权标头放入其中来使其工作,如下所示:
new EventSource("https://domain/stream", { headers: { Authorization: Bearer.... }});
new EventSource("https://domain/stream", { headers: { Authorization: Bearer.... }});
回答by Manoj Shrestha
The window.EventSourcedoesn't seem to support passing additional headers yet. Good news is there are some other implementations of EventSourcethat support additional headers. Some of them are as follows:
在window.EventSource似乎不支持通过附加头呢。好消息是还有其他一些EventSource支持附加标头的实现。其中一些如下:
const eventSource = new EventSource(resoureUrl, {
headers: {
'Authorization': 'Bearer ' + authorizationToken;
}
});
es.onmessage = result => {
const data = JSON.parse(result.data);
console.log('Data: ', data);
};
es.onerror = err => {
console.log('EventSource error: ', err);
};

