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

时间:2015-01-27 17:57:06

标签: javascript angularjs html5 http websocket

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

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

8 个答案:

答案 0 :(得分:9)

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

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

答案 1 :(得分:8)

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

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

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

经过几个小时的努力,我意识到我可以在不影响客户身份验证数据的情况下实现总体目标。为了澄清一下,我在建立EventSource连接时没有发现一些POST方法,但它确实允许浏览器在每次重新连接时安全地传递一个带有EventSource的身份验证令牌。它们的关键是将所需的sessionID /令牌放入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();
        }
    })

在相应的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验证短生命令牌并将其从数据库中删除,因此它永远不会再次使用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]

答案 2 :(得分:7)

此polyfill添加了授权标头支持:https://github.com/Yaffle/EventSource/

所以你可以这样做:

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

答案 3 :(得分:3)

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

答案 4 :(得分:2)

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

我不知道你是否可以提供身份验证标头作为第二个参数,就像在rafaelzlisboa的例子中一样,我通过创建一个头文件对象并将我的授权标题放在那里来实现它,如下所示:

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

答案 5 :(得分:1)

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

答案 6 :(得分:0)

我仔细阅读了几篇文章,以试图确定auth令牌是否在EventSource()调用中发送。尽管有polyfill选项可以添加标题:https://github.com/whatwg/html/issues/2177 而其他人提到通过ssl发送身份验证令牌。

与其使用polyfill EventSource()或通过ssl在查询参数中发送auth令牌,不如通过ssl在EventSource url的参数中发送eventSource标识符(eventSrcUUID):-

在用户身份验证时,eventSrcUUID随服务器上的sseEmitter一起生成,并放置在sseEmitterMap中。

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

答案 7 :(得分:0)

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