wake-up-neo.net

Warum verwendet der Browser die Berechtigungsheader nach einem authentifizierten XMLHttpRequest nicht wieder?

Ich entwickle eine Einzelseiten-App mit Angular. Das Backend macht REST - Dienste verfügbar, für die die Standardauthentifizierung erforderlich ist. Um index.html oder eines der Skripts abzurufen, ist keine Authentifizierung erforderlich. 

Ich habe eine merkwürdige Situation, in der eine meiner Ansichten einen <img> hat, wobei src die URL einer REST - API ist, die eine Authentifizierung erfordert. Der <img> wird vom Browser verarbeitet, und ich habe keine Möglichkeit, den Berechtigungsheader für die GET-Anforderung festzulegen. Dadurch fordert der Browser zur Eingabe von Anmeldeinformationen auf.

Ich habe versucht, dies zu beheben, indem ich Folgendes tat:

  1. Lassen Sie imgsrc in der Quelle leer 
  2. Machen Sie bei "document ready" eine XMLHttpRequest zu einem Dienst (/api/login) mit dem Authorization-Header, um nur die Authentifizierung durchzuführen.
  3. Legen Sie nach Abschluss dieses Aufrufs das img src-Attribut fest und denken Sie, dass der Browser bis dahin den Authorization-Header in nachfolgenden Anforderungen verwenden kann.

... aber es geht nicht. Die Anforderung für das Bild geht ohne die Header aus. Wenn ich die Anmeldeinformationen eingebe, sind alle anderen Bilder auf der Seite richtig .. (. Ich habe auch Angulars ng-src ausprobiert, aber das Ergebnis war das gleiche).

Ich habe zwei Fragen:

  1. Warum hat der Browser (IE10) nach einer erfolgreichen XMLHttpRequest die Header nicht in alle Anfragen aufgenommen? 
  2. Was kann ich tun, um dieses Problem zu umgehen?

@bergi fragt nach den Details der Anfragen. Hier sind sie.

Anfrage an/api/login

GET https://myserver/dev30281_WebServices/api/login HTTP/1.1
Accept: */*
Authorization: Basic <header here>
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729)
Connection: Keep-Alive

Antwort (/ api/login)

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 4
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 20 Dec 2013 14:44:52 GMT

Anfrage an/Benutzer/Bild/2218:

GET https://myserver/dev30281_WebServices/api/user/picture/2218 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729)
Connection: Keep-Alive

Der Webbrowser fordert dann zur Eingabe von Anmeldeinformationen auf. Wenn ich sie eingebe, erhalte ich folgende Antwort:

HTTP/1.1 200 OK
Cache-Control: public, max-age=60
Content-Length: 3119
Content-Type: image/png
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 20 Dec 2013 14:50:17 GMT
39
Sylvain

Die Grundidee

Laden Sie die Bilder über JavaScript und zeigen Sie sie auf der Website an. Der Vorteil ist, dass die Authentifizierungsnachweise niemals in den HTML-Code gelangen. Sie werden auf der JavaScript-Seite Widerstand leisten.

Schritt 1: Laden Sie die Bilddaten über JS

Dies ist eine grundlegende AJAX - Funktionalität (siehe auch XMLHttpRequest::open(method, uri, async, user, pw) ):

var xhr = new XMLHttpRequest();
xhr.open("GET", "your-server-path-to-image", true, "username", "password");

xhr.onload = function(evt) {
  if (this.status == 200) {
    // ...
  }
};

Schritt 2: Formatieren Sie die Daten

Wie können wir nun die Bilddaten anzeigen? Bei der Verwendung von HTML würde man normalerweise dem Attribut src des image-Elements einen URI zuweisen. Dasselbe Prinzip können wir hier anwenden, außer dass wir data URIs anstelle von "normalen" http(s)://-Derivaten verwenden.

xhr.onload = function(evt) {
  if (this.status == 200) {
    var b64 = utf8_to_b64(this.responseText);
    var dataUri = 'data:image/png;base64,' + b64; // Assuming a PNG image

    myImgElement.src = dataUri;
  }
};

// From MDN:
// https://developer.mozilla.org/en-US/docs/Web/API/window.btoa
function utf8_to_b64( str ) {
    return window.btoa(unescape(encodeURIComponent( str )));
}

Segeltuch

Es gibt auch eine andere Möglichkeit, die geladenen Daten in ein <canvas>-Feld zu malen. Auf diese Weise kann der Benutzer nicht mit der rechten Maustaste auf das Bild (den Bereich, in dem die Zeichenfläche positioniert ist) klicken, im Gegensatz zu den <img>- und Daten-URIs, bei denen dem Benutzer beim Anzeigen des Bildeigenschaftenbereichs ein langer Daten-URI angezeigt wird.

24
ComFreek

Der Google Drive-Uploader wird mit Hilfe von angle js erstellt. Die Autoren hatten ein ähnliches Problem. Die Icons wurden auf einer anderen Domain gehostet und als img src= gegen den CSP verletzt. So wie Sie mussten sie die Symbolbilder mit XHR abrufen und es dann irgendwie schaffen, sie in die img-Tags zu bekommen. 

Sie beschreiben, wie sie es gelöst haben . Nachdem sie das Bild mit XHR abgerufen haben, schreiben sie es in das lokale HTML5-Dateisystem. Sie legen ihre URL in das lokale Dateisystem im img-Attribut von src mit der Direktive ng-src ein. 

$http.get(doc.icon, {responseType: 'blob'}).success(function(blob) {
  console.log('Fetched icon via XHR');
  blob.name = doc.iconFilename; // Add icon filename to blob.
  writeFile(blob); // Write is async, but that's ok.
  doc.icon = window.URL.createObjectURL(blob);
  ...
}

Was das Warum angeht, weiß ich nicht. Ich gehe davon aus, dass das Erstellen eines Sitzungstokens zum Abrufen der Bilder nicht in Frage kommt? Ich würde erwarten, dass Cookie-Header gesendet werden? Ist es eine Cross-Origin-Anfrage? Legen Sie in diesem Fall die Eigenschaft withCredentials fest? Ist es vielleicht eine P3P-Sache?

8
flup

Ein anderer Ansatz wäre das Hinzufügen eines Endpunkts zum Backend Ihrer Site, der die Image-Anfrage weitergeleitet hat. Ihre Seite könnte also ohne Anmeldeinformationen anfordern, und das Back-End würde sich um die Authentifizierung kümmern. Das Backend könnte das Bild auch zwischenspeichern, wenn es nicht häufig geändert wurde oder Sie wussten, wie oft es aktualisiert wurde. Dies ist im Backend relativ einfach durchzuführen, vereinfacht das Frontend und verhindert, dass Anmeldeinformationen an den Browser gesendet werden. 

Wenn es sich bei der Authentifizierung um ein Problem handelt, können die Links ein Token für die einmalige Verwendung enthalten, das für den Benutzer generiert wird, der authentifiziert ist und nur über die aktuelle Browsersitzung zugänglich ist. Sicherer Zugriff auf den Inhalt nur für den Benutzer, für den er bestimmt war, und nur für die Zeit, zu der er berechtigt ist, darauf zuzugreifen. Dies würde jedoch auch Arbeit im hinteren Bereich erfordern. 

4
SzabV

Sie müssen versuchen, den Access-Control-Allow-Credentials: true-Header zu verwenden. Ich bin einmal mit IE auf ein Problem gestoßen, das sich schließlich auf die Verwendung dieses Headers beschränkte. Setzen Sie auch $httpProvider.defaults.headers.get = { 'withCredentials' : 'true' } im Winkelcode js.

2
Aziz Shaikh

Ich analysiere immer

Set-Cookie Header-Wert in der vorherigen (oder ersten Anmeldeanforderung) und dann in den nächsten Anforderungen senden.

Etwas wie das

Antwort nach erster Anfrage:

Date:Thu, 26 Dec 2013 16:20:53 GMT
Expires:-1
Pragma:no-cache
Set-Cookie:ASP.NET_SessionId=lb1nbxeyfhl5suii2hfchxpx; domain=.example.com; path=/; secure; HttpOnly
Vary:Accept-Encoding
X-Cdn:Served-By-Akamai
X-Powered-By:ASP.NET

Jede nächste Anfrage:

Accept:text/html,application/xhtml+xml
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8,ru;q=0.6
Cache-Control:no-cache
Connection:keep-alive
Cookie:ASP.NET_SessionId=lb1nbxeyfhl5suii2hfchxpx;

Wie Sie sehen, sende ich den Wert von ASP.NET_SessionId = "any value" in Cookie header. Wenn der Server php verwendet, sollten Sie PHPSESSID = "irgendein Wert" analysieren.

2
Sergey Pekar

Mir scheint, dass Sie, um Ihr Problem zu lösen, das Design Ihrer App ändern sollten, anstatt zu versuchen, sich mit der Funktionsweise von Browsern auseinanderzusetzen.

Eine Anfrage an eine sichere URL erfordert immer eine Authentifizierung, da der Browser sie mit einem img-Tag oder in Javascript erstellt.

Wenn Sie die Autorisierung automatisch ohne Benutzerinteraktion durchführen können, können Sie dies auf der Serverseite tun, und Sie müssen dafür keinen Benutzer + Pass an den Client senden. Wenn dies der Fall ist, können Sie den Code hinter https://myserver/dev30281_WebServices/api/user/picture/2218 ändern, um die Autorisierung durchzuführen und das Image ohne HTTP-Authentifizierung nur dann zu liefern, wenn der Benutzer dazu berechtigt ist, ihn anzufordern. Andernfalls geben Sie eine unzulässige 403-Antwort zurück ( http: // de .wikipedia.org/wiki/HTTP_403 ).

Eine andere mögliche Lösung wäre, die Seiten, die die sicheren Bilder enthalten, vom Rest der App zu trennen. Sie hätten also theoretisch zwei Single-Page-Apps. Der Benutzer muss sich anmelden, um auf den sicheren Teil zugreifen zu können. Ich bin mir jedoch nicht sicher, ob dies in Ihrem Fall möglich ist, da Sie nicht alle Anforderungen angegeben haben. Es ist jedoch sinnvoller, wenn der Benutzer sichere Ressourcen bereitstellen möchte, die eine Authentifizierung erfordern, dass der Benutzer ebenso wie der Browser zur Eingabe von Anmeldeinformationen aufgefordert wird.

2
Jonas

Der Grund: Ich habe Chrome und Firefox ausprobiert, und beide erinnern sich an die grundlegende Berechtigung nur wenn der Berechtigungsnachweis eingegeben wird direkt über die Browser-Benutzeroberfläche , d. Es wird sich nicht erinnern, wenn der Berechtigungsnachweis aus JavaScript stammt, obwohl die HTTP-Anforderung dieselbe ist. Ich denke, das ist vom Design her, aber ich sehe es nicht als Standard.

0
Franklin Yu