web-dev-qa-db-ja.com

EventSourceのHTTP認証ヘッダー(サーバー送信イベント)

AuthorizationヘッダーをHTML5 EventSourceに設定する必要があります。 Websocketが登場して以来、Server Sent Eventsは使用されていないようですので、有用なドキュメントは見つかりません。私がすでに見つけたアプローチは、URL内で認証データを渡すことですが...この方法は好きではありません。

私はAngularJSを使用しており、$ httpProviderでインターセプターを設定していますが、EventSourceはAngularJSによってインターセプトされないため、ヘッダーを追加できません。

EventSourceには、HTTPヘッダーをサーバーに送信するための [〜#〜] api [〜#〜] がありません。 SSEを使用してリアルタイムチャットを構築しているときも、この問題に苦労していました。

ただし、SSEサーバーが認証サーバーと同じサーバーである場合、Cookieは自動的に送信されると思います。

10
srigi

このポリフィルは、承認ヘッダーのサポートを追加します。 https://github.com/Yaffle/EventSource/

だからあなたができる:

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

あなたの投稿は1年以上前のものだったと思いますが、今では同じボートに乗っていて、良い答えが得られました。私はこれが誰かを助けるか、少なくとも彼らにいくつかのアイデアを与えることを望んでいます...

Cookieは簡単に思えますが、誰かがCookieをブロックしているとどうなりますか?サイトを使用するためにCookieを有効にするようにユーザーに要求する必要があります。その時点で、「セキュリティ上の理由」でCookieを無効にしているため、サイトを信頼できるかどうか疑問に思い始めます。その間、セキュリティ上の理由からCookieを有効にする必要があります!

AJAXを使用すると、SSLを介してデータを簡単にPOST認証できますが、SSEでは不可能です。私は人々が「クエリ文字列を使用する」と言う多くの投稿を見てきましたが、私は認証データをプレーンテキスト(example.com/stream?sessionID=idvalue)で送信して顧客のセキュリティを侵害したくありませんスヌープできました。

数時間頭を悩ませた後、顧客の認証データを損なうことなく全体的な目標を達成できることに気付きました。明確にするために、EventSource接続を確立するときにPOSTへの方法を発見していませんが、ブラウザーは再接続するたびにEventSourceで認証トークンを安全に渡すことができます。それらの鍵は、必要なセッションID /トークンをlastEventIDに取得することです。

ユーザーは、ユーザー名/パスワードを使用して(またはAJAX localstorageに保持しているトークンをPOSTすることで)通常どおり認証できます。 AJAX authプロセスは、目的のバックエンド(例:mySQL)に保存される、短命トークン(60秒で使用されるか、使用された場合)とともにJSONオブジェクトを返します。トークン。この時点で、次のように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を使用して、イベントIDとしてデータベースから取得したREALトークン(かなり後で期限切れになります)をプッシュします。 Javascriptがこのイベントで何もする必要はありません。サーバーは接続を自動的に終了しますが、イベントをさらに処理したい場合はイベントをリッスンできます。

この時点で、SSEがスクリプトを終了してからPHP接続が終了しました。ただし、ブラウザは自動的に接続を再確立します(通常3秒で)。今回は、接続をドロップする前にトークン値に設定したlastEventId ...を送信します。次の接続では、この値がトークンとして使用され、アプリは期待どおりに実行されます。メッセージ/イベントを送信するときに、実際のトークンをイベントIDとして使用し始めている限り、接続をドロップする必要はありません。このトークン値は、ブラウザーが受信したときと、サーバーへの後続の接続の両方で、SSLを介して完全に暗号化されて送信されます。 「クリア」で送信された値は、受信して使用してから数秒以内に期限切れになり、発見した人は使用できなくなります。誰かがそれを使用しようとすると、404 RESPONSEを受け取ります。

すでに他の目的でイベントストリーム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]
9
KeithMcFly

認証トークンを渡すもう1つの方法は、クエリパラメータとしてURLを使用することですが、セキュリティを考慮する必要があります。また、サーバー側の照会パラメーターを介して許可のサポートを追加します。

3
Sergey Leyko

イベントソースポリフィルのこのフォークを使用すると、rafaelzlisboaが記述する方法と同様に認証ヘッダーを追加できます: https://github.com/AlexGalays/EventSource#923b9a0998fcfd7753040e09aa83764b3cc0230d

Ra rafaelzlisboaの例のように認証ヘッダーを2番目の引数として提供できるかどうかわからない場合、次のようにヘッダーオブジェクトを作成し、そこに認証ヘッダーを配置することで機能するようになりました。

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

2
Jeroen Dragt

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);
};
1
Manoj Shrestha