Je développe Single Page App en utilisant Angular. Le backend expose les services REST nécessitant une authentification de base. Obtenir index.html ou l'un des scripts ne nécessite pas d'authentification.
J'ai une situation étrange où l'un de mes points de vue a un <img>
où src
est l'URL d'une API REST qui nécessite une authentification. Le <img>
est traité par le navigateur et je n’ai aucune chance de définir l’en-tête d’autorisation pour la requête GET qu’il émet. Cela provoque le navigateur à demander des informations d'identification.
J'ai essayé de résoudre ce problème en faisant ceci:
img
src
vide dans le source XMLHttpRequest
sur un service (/api/login
) avec l'en-tête Authorization afin que l'authentification se produise.img src
, en pensant que le navigateur saurait alors inclure l'en-tête Authorization dans les requêtes suivantes ...... mais ça ne marche pas. La demande pour l'image s'éteint sans les en-têtes. Si j'entre les informations d'identification, toutes les autres images de la page sont exactes . (J'ai également essayé et le code ng-src
d'Angular, mais cela a donné le même résultat)
J'ai deux questions:
XMLHttpRequest
réussie? @bergi a demandé des détails sur les demandes. Les voici.
Demande à/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
Réponse (/ 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
Demande à/utilisateur/image/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
Et puis le navigateur Web demande des informations d'identification. Si je les saisis, j'obtiens cette réponse:
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
Chargez les images via JavaScript et affichez-les sur le site. L'avantage est que les informations d'authentification ne parviendront jamais dans le code HTML. Ils vont résister du côté JavaScript.
C'est la fonctionnalité de base AJAX (voir aussi 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) {
// ...
}
};
Maintenant, comment pouvons-nous afficher les données d'image? Lors de l'utilisation de HTML, on attribue normalement un URI à l'attribut src
de l'élément image. Nous pouvons appliquer le même principe ici, à l'exception du fait que nous utilisons des URI de données au lieu de dérivés 'normaux' http(s)://
.
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 )));
}
Il existe également une autre option qui consiste à peindre les données chargées dans un champ <canvas>
. De cette manière, l'utilisateur ne pourra pas cliquer avec le bouton droit de la souris sur l'image (la zone dans laquelle le canevas est positionné), par opposition à l'URI <img>
et aux données où l'utilisateur verra un long URI lorsqu'il affichera le panneau de propriétés de l'image.
Le programme de téléchargement de Google Drive est créé à l'aide de js angulaire. Ses auteurs étaient confrontés à un problème similaire. Les icônes étaient hébergées sur un domaine différent et les placer comme img src=
constituait une violation du CSP. Ainsi, comme vous, ils ont dû récupérer les images d'icônes à l'aide de XHR, puis réussir à les insérer dans les balises img
.
Ils décrivent comment ils l’ont résolu . Après avoir récupéré l'image à l'aide de XHR, ils l'écrivent dans le système de fichiers local HTML5. Ils placent son URL sur le système de fichiers local dans l'attribut img
de src
à l'aide de la directive ng-src
.
$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);
...
}
Quant au pourquoi, je ne sais pas. Je suppose que la création d'un jeton de session pour récupérer les images est hors de question? Je m'attendrais à ce que les en-têtes de cookie soient envoyés? Est-ce une demande d'origine croisée? Dans ce cas, définissez-vous la propriété withCredentials ? Est-ce une chose P3P peut-être?
Une autre approche consisterait à ajouter un point final à l’arrière-plan de votre site qui a proxy la demande d’image. Ainsi, votre page pourrait le demander sans informations d'identification et le back-end se chargerait de l'authentification. Le système dorsal peut également mettre en cache l’image si elle ne change pas souvent ou si vous connaissez la fréquence à laquelle elle est mise à jour. Ceci est assez facile à faire sur le back-end, simplifie votre front-end et évite l'envoi d'informations d'identification au navigateur.
Si le problème est l'authentification, les liens peuvent contenir un jeton à usage unique généré pour l'utilisateur authentifié et accessible uniquement à partir de la session de navigateur en cours. Donner un accès sécurisé au contenu uniquement à l'utilisateur auquel il était destiné et uniquement au moment où il est autorisé à y accéder. Cela nécessiterait également un travail dans le back-end, cependant.
Vous devez essayer d'utiliser l'en-tête Access-Control-Allow-Credentials: true
. Une fois, j’ai rencontré un problème avec IE qui a finalement abouti à l’utilisation de cet en-tête. Définissez également $httpProvider.defaults.headers.get = { 'withCredentials' : 'true' }
dans le code js angulaire.
Je toujours analyser
Set-Cookie en-tête dans la précédente (ou la première demande de connexion), puis envoie sa valeur dans les prochaines requêtes.
Quelque chose comme ça
Réponse après la première demande:
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
Toute demande suivante:
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;
Comme vous pouvez le voir, j'envoie ASP.NET_SessionId = "toute valeur" dans Cookie header. Si le serveur utilise php, vous devez analyser PHPSESSID = "une valeur"
Il me semble que pour résoudre votre problème, vous devez modifier la conception de votre application, au lieu d'essayer de modifier le fonctionnement des navigateurs.
Une requête vers une URL sécurisée nécessitera toujours une authentification, que le navigateur l’envoie avec une balise img ou en javascript.
Si vous pouvez effectuer l'autorisation automatiquement sans interaction de l'utilisateur, vous pouvez le faire côté serveur et vous n'avez pas besoin d'envoyer d'utilisateur + passe au client pour le faire. Si tel est le cas, vous pouvez modifier le code derrière https://myserver/dev30281_WebServices/api/user/picture/2218
pour exécuter l'autorisation et diffuser l'image, sans autorisation HTTP, uniquement si l'utilisateur est autorisé à la demander. Sinon, renvoyez une réponse 403 interdite ( http: // en .wikipedia.org/wiki/HTTP_403 ).
Une autre solution possible serait de séparer les pages contenant les images sécurisées du reste de l'application. Donc, théoriquement, vous auriez deux applications d'une seule page. L'utilisateur devrait se connecter pour accéder à la partie sécurisée. Je ne sais pas si cela est possible dans votre cas, puisque vous n'avez pas indiqué toutes les exigences. Mais il est plus logique que si vous souhaitez desservir des ressources sécurisées nécessitant une authentification, vous devez demander à l'utilisateur des informations d'identification, exactement comme le navigateur.
Pour ce qui est de la raison: j’ai essayé Chrome et Firefox, et je me souviens de l’autorisation de base seulement si les informations d’identité sont saisies directement à partir de l’UI du navigateur , c’est-à-dire la fenêtre contextuelle créée par le navigateur. Il ne s'en souviendra pas si les informations d'identification proviennent de JavaScript, bien que la demande HTTP soit la même. Je suppose que cela est voulu par la conception, mais je ne le vois pas mentionné dans la norme.