web-dev-qa-db-ja.com

JWTベースの認証でファイルのダウンロードを処理する方法は?

私はAngularで認証を処理します。認証はJWTトークンによって処理されます。つまり、すべての要求には必要なすべての情報を含む「認証」ヘッダーがあります。

これはREST呼び出しに対してうまく機能しますが、バックエンドでホストされているファイルのダウンロードリンクを処理する方法がわかりません(ファイルはWebサービスがホストされている同じサーバーにあります)。

通常の_<a href='...'/>_リンクを使用することはできません。これらのリンクはヘッダーを保持せず、認証が失敗するためです。 window.open(...)のさまざまな呪文についても同じです。

私が考えたいくつかのソリューション:

  1. サーバーで一時的な安全でないダウンロードリンクを生成する
  2. 認証情報をurlパラメーターとして渡し、ケースを手動で処理します
  3. XHRを介してデータを取得し、ファイルクライアント側を保存します。

上記のすべては満足できるものではありません。

1は現在使用しているソリューションです。私は2つの理由でそれが好きではありません:1つ目はセキュリティに関して理想的ではありません、2つ目は動作しますが、特にサーバー上で非常に多くの作業が必要です:何かをダウンロードするには、新しい「ランダムな"url、それをどこかに(おそらくDB上に)しばらく保存し、クライアントに返します。クライアントはURLを取得し、window.openまたはそれに類似したものを使用します。要求された場合、新しいURLはそれがまだ有効かどうかを確認し、データを返します。

2は少なくとも同じくらいの作業のようです。

3は、利用可能なライブラリを使用する場合でも、多くの作業が必要であり、多くの潜在的な問題があります。 (独自のダウンロードステータスバーを提供し、ファイル全体をメモリに読み込み、ユーザーにファイルをローカルに保存するように要求する必要があります)。

タスクは非常に基本的なもののようですが、使用できるもっと簡単なものはないかと思っています。

「Angular way」という解決策を必ずしも探しているわけではありません。通常のJavascriptで十分でしょう。

94
Marco Righele

ダウンロード属性フェッチAPI 、および RL.createObjectURL を使用して、クライアントにダウンロードする方法を次に示します。 JWTを使用してファイルを取得し、ペイロードをblobに変換し、blobをobjectURLに入れ、アンカータグのソースをそのobjectURLに設定し、javascriptでそのobjectURLをクリックします。

let anchor = document.createElement("a");
document.body.appendChild(anchor);
let file = 'https://www.example.com/some-file.pdf';

let headers = new Headers();
headers.append('Authorization', 'Bearer MY-TOKEN');

fetch(file, { headers })
    .then(response => response.blob())
    .then(blobby => {
        let objectUrl = window.URL.createObjectURL(blobby);

        anchor.href = objectUrl;
        anchor.download = 'some-file.pdf';
        anchor.click();

        window.URL.revokeObjectURL(objectUrl);
    });

download属性の値は、最終的なファイル名になります。必要に応じて、コンテンツ処理応答ヘッダーから目的のファイル名をマイニングできます 他の回答で説明されているように

36
Technetium

技術

JWTエバンジェリストとして知られるAuth0のMatias Woloskiの このアドバイス に基づいて、 Hawk で署名付きリクエストを生成することで解決しました。

ウォロスキーの引用:

これを解決する方法は、たとえばAWSのように署名付きリクエストを生成することです。

ここに例を示します アクティベーションリンクに使用されるこの手法の。

バックエンド

ダウンロードURLに署名するAPIを作成しました。

要求:

POST /api/sign
Content-Type: application/json
Authorization: Bearer...
{"url": "https://path.to/protected.file"}

応答:

{"url": "https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c"}

署名付きURLを使用すると、ファイルを取得できます

要求:

GET https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c

応答:

Content-Type: multipart/mixed; charset="UTF-8"
Content-Disposition': attachment; filename=protected.file
{BLOB}

フロントエンド(by jojoyuji

これにより、ユーザーが1回クリックするだけですべてを実行できます。

function clickedOnDownloadButton() {

  postToSignWithAuthorizationHeader({
    url: 'https://path.to/protected.file'
  }).then(function(signed) {
    window.location = signed.url;
  });

}
29

ダウンロード用のトークンを生成します。

内でangular一時的なトークンを取得するための認証されたリクエスト(1時間など)を行い、それをgetパラメーターとしてURLに追加します。この方法で、好きな方法でファイルをダウンロードできます開いた ...)

6
Fred

追加のソリューション:基本認証を使用します。バックエンドで少し作業が必要ですが、トークンはログに表示されず、URL署名を実装する必要はありません。


クライアント側

URLの例は次のとおりです。

_http://jwt:<user jwt token>@some.url/file/35/download_

ダミートークンの例:

_http://jwt:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwIiwibmFtZSI6IiIsImlhdCI6MH0.KsKmQOZM-jcy4l_7NFsv1lWfpH8ofniVCv75ZRQrWno@some.url/file/35/download_

その後、これを_<a href="...">_またはwindow.open("...")で突き止めることができます。残りはブラウザが処理します。


サーバ側

ここでの実装はユーザー次第であり、サーバーのセットアップに依存します-_?token=_クエリパラメーターを使用することとそれほど違いはありません。

Laravelを使用して、簡単な方法で基本認証パスワードをJWT _Authorization: Bearer <...>_ヘッダーに変換し、通常の認証ミドルウェアが残りを処理できるようにしました。

_class CarryBasic
{
    /**
     * @param Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle($request, \Closure $next)
    {
        // if no basic auth is passed,
        // or the user is not "jwt",
        // send a 401 and trigger the basic auth dialog
        if ($request->getUser() !== 'jwt') {
            return $this->failedBasicResponse();
        }

        // if there _is_ basic auth passed,
        // and the user is JWT,
        // shove the password into the "Authorization: Bearer <...>"
        // header and let the other middleware
        // handle it.
        $request->headers->set(
            'Authorization',
            'Bearer ' . $request->getPassword()
        );

        return $next($request);
    }

    /**
     * Get the response for basic authentication.
     *
     * @return void
     * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
     */
    protected function failedBasicResponse()
    {
        throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
    }
}
_
3
AlbinoDrought