Wie werden Dateidownloads mit JWT-basierter Authentifizierung gehandhabt?

Lesezeit: 9 Minuten

Wie werden Dateidownloads mit JWT basierter Authentifizierung gehandhabt
Marco Righele

Ich schreibe eine Webapp in Angular, wo die Authentifizierung von einem JWT-Token abgewickelt wird, was bedeutet, dass jede Anfrage einen “Authentication”-Header mit allen notwendigen Informationen hat.

Dies funktioniert gut für REST-Aufrufe, aber ich verstehe nicht, wie ich mit Download-Links für Dateien umgehen soll, die auf dem Backend gehostet werden (die Dateien befinden sich auf demselben Server, auf dem die Webservices gehostet werden).

Ich kann nicht regelmäßig verwenden <a href="https://stackoverflow.com/questions/29452031/..."/> Links, da sie keinen Header tragen und die Authentifizierung fehlschlägt. Gleiches gilt für die verschiedenen Beschwörungen von window.open(...).

Einige Lösungen, die mir eingefallen sind:

  1. Generieren Sie einen temporären ungesicherten Download-Link auf dem Server
  2. Übergeben Sie die Authentifizierungsinformationen als URL-Parameter und bearbeiten Sie den Fall manuell
  3. Holen Sie sich die Daten über XHR und speichern Sie die Datei clientseitig.

Alle oben genannten Punkte sind weniger als zufriedenstellend.

1 ist die Lösung, die ich gerade verwende. Ich mag es aus zwei Gründen nicht: Erstens ist es sicherheitstechnisch nicht ideal, zweitens funktioniert es, aber es erfordert ziemlich viel Arbeit, insbesondere auf dem Server: Um etwas herunterzuladen, muss ich einen Dienst aufrufen, der eine neue “random ” url, speichert sie für einige Zeit irgendwo (möglicherweise in der DB) und gibt sie an den Client zurück. Der Client erhält die URL und verwendet window.open oder ähnliches damit. Auf Anfrage sollte die neue URL prüfen, ob sie noch gültig ist, und dann die Daten zurückgeben.

2 scheint mindestens genauso viel Arbeit zu sein.

3 scheint eine Menge Arbeit zu sein, selbst wenn verfügbare Bibliotheken verwendet werden, und viele potenzielle Probleme. (Ich müsste meine eigene Download-Statusleiste bereitstellen, die gesamte Datei in den Speicher laden und dann den Benutzer bitten, die Datei lokal zu speichern).

Die Aufgabe scheint jedoch ziemlich einfach zu sein, daher frage ich mich, ob es etwas viel Einfacheres gibt, das ich verwenden kann.

Ich suche nicht unbedingt nach einer Lösung “the Angular way”. Reguläres Javascript wäre in Ordnung.

  • Mit remote meinen Sie, dass sich die herunterladbaren Dateien in einer anderen Domäne befinden als die Angular-App? Kontrollieren Sie die Fernbedienung (haben Sie Zugriff, um das Backend zu ändern) oder nicht?

    – robertjd

    13. April 2015 um 22:33 Uhr

  • Ich meine, dass die Dateidaten nicht auf dem Client (Browser) sind; Die Datei wird auf derselben Domain gehostet und ich habe die Kontrolle über das Backend. Ich werde die Frage aktualisieren, um sie weniger zweideutig zu machen.

    – Marco Righele

    14. April 2015 um 9:52 Uhr


  • Die Schwierigkeit von Option 2 hängt von Ihrem Backend ab. Wenn Sie Ihr Back-End anweisen können, die Abfragezeichenfolge zusätzlich zum Autorisierungsheader für das JWT zu überprüfen, wenn es die Authentifizierungsschicht durchläuft, sind Sie fertig. Welches Backend verwendest du?

    – Technetium

    31. März 2017 um 21:49 Uhr

Wie werden Dateidownloads mit JWT basierter Authentifizierung gehandhabt
Technetium

Hier ist eine Möglichkeit, es auf dem Client herunterzuladen das Download-Attribut, die Abruf-APIund URL.createObjectURL. Sie würden die Datei mit Ihrem JWT abrufen, die Nutzlast in ein Blob konvertieren, das Blob in eine Objekt-URL einfügen, die Quelle eines Anker-Tags auf diese Objekt-URL setzen und auf diese Objekt-URL in Javascript klicken.

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

Der Wert der download Das Attribut ist der endgültige Dateiname. Falls gewünscht, können Sie einen beabsichtigten Dateinamen aus dem Antwortheader für die Inhaltsdisposition herausfiltern, wie in anderen Antworten beschrieben.

  • Ich frage mich immer wieder, warum niemand diese Antwort berücksichtigt. Es ist einfach und da wir im Jahr 2017 leben, ist die Plattformunterstützung ziemlich gut.

    – Rafal Pastuszak

    18. September 2017 um 16:36 Uhr


  • Aber die iosSafari-Unterstützung für das Download-Attribut sieht ziemlich rot aus 🙁

    – Martin Cremer

    7. März 2018 um 17:37 Uhr

  • Das hat bei mir in Chrom gut funktioniert. Für Firefox funktionierte es, nachdem ich den Anker zum Dokument hinzugefügt hatte: document.body.appendChild(anchor); Keine Lösung für Edge gefunden…

    – Tompi

    12. September 2018 um 13:46 Uhr

  • Diese Lösung funktioniert, aber behandelt diese Lösung UX-Probleme mit großen Dateien? Wenn ich manchmal eine 300-MB-Datei herunterladen muss, kann der Download einige Zeit dauern, bevor ich auf den Link klicke und ihn an den Download-Manager des Browsers sende. Wir könnten die Mühe aufwenden, die Fetch-Progress-API zu verwenden und unsere eigene Download-Fortschritts-Benutzeroberfläche zu erstellen. Aber dann gibt es auch die fragwürdige Praxis, eine 300-MB-Datei in js-land (im Speicher?) zu laden, um sie lediglich an den Download weiterzugeben Manager.

    – scvnc

    13. September 2018 um 21:21 Uhr

  • @Tompi Auch ich konnte dies nicht für Edge und IE zum Laufen bringen

    – zappa

    28. Februar 2019 um 13:29 Uhr

Wie werden Dateidownloads mit JWT basierter Authentifizierung gehandhabt
Ezequias Dinella

Technik

Beyogen auf dieser Rat von Matias Woloski von Auth0, bekannter JWT-Evangelist, habe ich gelöst, indem ich eine signierte Anfrage mit generiert habe Falke.

Woloski zitieren:

Sie lösen dies, indem Sie eine signierte Anfrage generieren, wie es beispielsweise AWS tut.

Hier haben Sie ein Beispiel dieser Technik, die für Aktivierungslinks verwendet wird.

Backend

Ich habe eine API erstellt, um meine Download-URLs zu signieren:

Anfrage:

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

Antwort:

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

Mit einer signierten URL können wir die Datei abrufen

Anfrage:

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

Antwort:

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

Frontend (von jojoyuji)

Auf diese Weise können Sie alles mit einem einzigen Benutzerklick erledigen:

function clickedOnDownloadButton() {

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

}

  • Das ist cool, aber ich verstehe nicht, wie es aus Sicherheitssicht anders ist als die Option Nr. 2 des OP (Token als Parameter für Abfragezeichenfolgen). Tatsächlich kann ich mir vorstellen, dass die signierte Anfrage restriktiver sein könnte, dh nur auf einen bestimmten Endpunkt zugreifen darf. Aber die Nr. 2 des OP scheint einfacher / weniger Schritte zu sein, was ist daran falsch?

    – Tyler Collier

    17. Juli 2016 um 21:21 Uhr

  • Abhängig von Ihrem Webserver wird möglicherweise die vollständige URL in den Protokolldateien protokolliert. Möglicherweise möchten Sie nicht, dass Ihre IT-Mitarbeiter Zugriff auf alle Token haben.

    – Ezequias Dinella

    21. Juli 2016 um 21:14 Uhr

  • Außerdem wird die URL mit der Abfragezeichenfolge in Ihrem Benutzerverlauf gespeichert, sodass andere Benutzer desselben Computers auf die URL zugreifen können.

    – Ezequias Dinella

    21. Juli 2016 um 21:14 Uhr

  • Und was dies schließlich sehr unsicher macht, ist, dass die URL im Referer-Header aller Anfragen nach Ressourcen gesendet wird, sogar nach Ressourcen von Drittanbietern. Wenn Sie also beispielsweise Google Analytics verwenden, senden Sie Google das URL-Token und alles an sie.

    – Ezequias Dinella

    21. Juli 2016 um 21:14 Uhr

  • In meinen Implementierungen der Web-API für dieses Muster ist die signed.url nur für 1 Zugriff gut

    – bkwdesign

    27. März 2018 um 16:33 Uhr

Eine Alternative zu den bereits erwähnten existierenden “fetch/createObjectURL”- und “download-token”-Ansätzen ist ein Standard Form POST, das auf ein neues Fenster abzielt. Sobald der Browser den Header des Anhangs in der Serverantwort liest, schließt er die neue Registerkarte und beginnt mit dem Download. Derselbe Ansatz funktioniert auch gut, um eine Ressource wie ein PDF in einem neuen Tab anzuzeigen.

Dies bietet eine bessere Unterstützung für ältere Browser und vermeidet die Verwaltung eines neuen Tokentyps. Dies wird auch langfristig besser unterstützt als die grundlegende Authentifizierung für die URL, da Unterstützung für Benutzername/Passwort auf der URL wird von Browsern entfernt.

Auf der clientseitig wir gebrauchen target="_blank" Navigation auch in Fehlerfällen zu vermeiden, was besonders für SPAs (Single Page Apps) wichtig ist.

Die große Einschränkung ist, dass die serverseitig Die JWT-Validierung muss das Token von der abrufen Post-Daten und nicht aus der Überschrift. Wenn Ihr Framework den Zugriff auf Routenhandler automatisch mithilfe des Authentifizierungsheaders verwaltet, müssen Sie Ihren Handler möglicherweise als nicht authentifiziert/anonym markieren, damit Sie das JWT manuell validieren können, um eine ordnungsgemäße Autorisierung sicherzustellen.

Das Formular kann dynamisch erstellt und sofort gelöscht werden, damit es ordnungsgemäß bereinigt wird (Hinweis: Dies kann in einfachem JS erfolgen, aber JQuery wird hier zur Verdeutlichung verwendet) –

function DownloadWithJwtViaFormPost(url, id, token) {
    var jwtInput = $('<input type="hidden" name="jwtToken">').val(token);
    var idInput = $('<input type="hidden" name="id">').val(id);
    $('<form method="post" target="_blank"></form>')
                .attr("action", url)
                .append(jwtInput)
                .append(idInput)
                .appendTo('body')
                .submit()
                .remove();
}

Fügen Sie einfach alle zusätzlichen Daten hinzu, die Sie als versteckte Eingaben einreichen müssen, und stellen Sie sicher, dass sie an das Formular angehängt werden.

  • Ich glaube, diese Lösung wird stark unterschätzt. Es ist einfach, sauber und funktioniert perfekt.

    – Jura Fedoriv

    5. Februar 2020 um 22:32 Uhr


  • Diese Lösung funktioniert, die einzige Sorge, die ich habe, ist aus Sicht der Sicherheit. Der Dienst kann durch eine große Anzahl von Aufrufen angehängt werden, obwohl sie alle ein ungültiges jwt-Token haben. Es macht den Dienst beschäftigt.

    – Ida Amit

    7. Januar 2021 um 14:06 Uhr

  • @IdaAmit Ich kann Ihre Besorgnis verstehen. Solange die JWT-Validierung das erste ist, was getan wird, bin ich mir nicht sicher, wie dies einem DoS-Angriff stärker ausgesetzt ist als jeder der zuvor genannten Ansätze, die alle ein JWT-Token zum Herunterladen (oder zum Abrufen eines Downloads) validieren müssen Zeichen). Obwohl es Unterschiede zwischen den Servertechnologien gibt, ist eine öffentliche Route normalerweise ziemlich leichtgewichtig. Solange derselbe Validierungscode verwendet wird, sollte der Unterschied im Overhead minimal sein. Nur weil ein Framework den JWT-Validierungscode verbirgt, heißt das nicht, dass es diesen Overhead nicht hat.

    – James

    8. Januar 2021 um 21:53 Uhr

  • Was, wenn access_token abgelaufen ist download file Anfrage? Wie werden Sie sich erfrischen? access_token?

    – wosuc

    2. März 2021 um 5:29 Uhr

  • @wonsuc Die Lebensdauer des Tokens ist dieselbe wie der Rest Ihrer Anwendung. Wenn Sie beispielsweise Ihr access_token ablaufen lassen und dann versuchen, auf eine andere Ressource zuzugreifen, z. B. eine Liste mit Elementen, treten ähnliche Probleme auf. Normalerweise wird ein refresh_token verwendet, um regelmäßig ein neues access_token anzufordern, aber das hängt von Ihrer Anwendung ab und ist nicht spezifisch für dieses Szenario. Sie könnten die Ablaufinformationen verwenden, um zu überprüfen, ob das JWT noch gültig ist, bevor Sie den Post ausführen, aber auch dies ist eine Entwurfsentscheidung, die weit außerhalb des Rahmens dieser Frage liegt.

    – James

    3. März 2021 um 16:27 Uhr

Ich würde Token zum Download generieren.

Stellen Sie innerhalb von Angular eine authentifizierte Anfrage, um ein temporäres Token (z. B. eine Stunde) zu erhalten, und fügen Sie es dann als Get-Parameter zur URL hinzu. Auf diese Weise können Sie Dateien auf beliebige Weise herunterladen (window.open …)

Reine JS-Version von James Antwort

function downloadFile (url, token) {
    let form = document.createElement('form')
    form.method = 'post'
    form.target="_blank"
    form.action = url
    form.innerHTML = '<input type="hidden" name="jwtToken" value="' + token + '">'

    console.log('form:', form)

    document.body.appendChild(form)
    form.submit()
    document.body.removeChild(form)
}

828150cookie-checkWie werden Dateidownloads mit JWT-basierter Authentifizierung gehandhabt?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy