我需要为HTML5 EventSource设置一个授权头部。由于Websockets出现后,Server Sent Events似乎已经不再使用,因此我无法找到任何有用的文档。我已经找到的方法是在url中传递授权数据...但我不喜欢这种方法。
我正在使用AngularJS并在$httpProvider上设置拦截器,但EventSource没有被AngularJS拦截,因此我无法添加任何头部。
我需要为HTML5 EventSource设置一个授权头部。由于Websockets出现后,Server Sent Events似乎已经不再使用,因此我无法找到任何有用的文档。我已经找到的方法是在url中传递授权数据...但我不喜欢这种方法。
我正在使用AngularJS并在$httpProvider上设置拦截器,但EventSource没有被AngularJS拦截,因此我无法添加任何头部。
我知道您的帖子已经过去一年了,但是我发现自己也面临同样的问题,却找不到好的答案。我希望这可以帮助某些人,或者至少给他们一些想法...
Cookie看起来很容易,但如果有人阻止Cookie怎么办?我必须提示他们启用Cookie才能使用该网站。此时,他们开始怀疑是否可以信任该网站,因为他们出于“安全原因”禁用了Cookie。然而,与此同时,我希望出于安全原因启用Cookie!
使用AJAX,可以轻松地通过SSL POST身份验证数据,但对于SSE来说,这是不可能的。我看过许多帖子,人们会说,“只需使用查询字符串”,但我不想通过明文发送auth数据(例如example.com/stream?sessionID=idvalue)来危及客户的安全,因为有人可能会偷窥。
经过几个小时的思考,我意识到我可以在不危及客户auth数据的情况下实现整体目标。需要澄清的是,我没有发现在建立EventSource连接时进行POST的方法,但它确实允许浏览器每次重新连接时与EventSource一起安全地传递身份验证令牌。关键是将所需的sessionID / token传递到lastEventID中。
用户可以像往常一样使用用户名/密码进行身份验证(或通过AJAX POST在localstorage中保存的令牌进行身份验证)。 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();
}
})
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;
}
// 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]
${uri}?slt=${sltToken})
一起发送。然后服务器建立自己的用户和特定套接字连接之间的关联。似乎不需要断开连接或使用某种奇怪的上次接收到的id字段的排列方式。 - Eric BladeEventSource没有发送HTTP头到服务器的API。当我使用SSE构建实时聊天时,我也曾为此问题苦恼。
然而,如果您的SSE服务器与身份验证服务器相同,则认为cookie将自动发送。
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);
};
这个Polyfill添加了授权头支持:https://github.com/Yaffle/EventSource/
因此,您可以执行以下操作:
new EventSource("https://domain/stream", { authorizationHeader: "Bearer ..." });
自从这个问题被问出来以后,EventSource领域发生了很多事情。
所有主要的浏览器现在支持获取流。
这意味着你现在可以在这些流之上实现一个EventSource
。
这似乎是为什么头部从未添加到EventSource
规范中的主要原因。
一个基于fetch的EventSource
实现的例子是https://github.com/Azure/fetch-event-source(npm install @microsoft/fetch-event-source
)。
new EventSource("https://domain/stream", { headers: { Authorization: Bearer.... }});
另一种传递身份验证令牌的方式是通过URL作为查询参数,但您应该考虑安全性。此外,要在服务器端添加支持通过查询参数进行授权的功能。
我通过在SSE调用之前增加额外的休息时间来解决了这个问题,这个休息时间是普通的休息时间,需要与我们在SSE调用中需要的安全协议相同,并且在响应中获取OTP。并将此OTP发送到sse调用查询参数中,在Web过滤器中验证此OTP并将其替换为身份验证标头。
await fetchEventSource('/api/sse', {
headers: {
'Authorization': 'Bearer my_token', // and/or any other headers you need
},
onmessage(ev) {
console.log(ev.data);
}
});
我浏览了很多帖子,试图找出在EventSource()调用中是否可以发送auth token。虽然有一些polyfill替代方案允许添加头文件:https://github.com/whatwg/html/issues/2177,但其他人则提到通过ssl发送auth token。
与其使用polyfill EventSource()或通过ssl在查询参数中发送auth token,不如在EventSource url的参数中发送eventSource标识符(eventSrcUUID):
在用户认证时,服务器会生成eventSrcUUID以及sseEmitter,并将它们放置在sseEmitterMap中。
客户端从响应中检索eventSrcUUID,并使用该参数调用EventSource()。在服务器上,引用sseEmitterMap以检索eventSrc对象。保存在会话数据中的sseEmitter对象用于向客户端发送事件通知。