javascript 为什么浏览器在经过身份验证的 XMLHttpRequest 后不重用授权标头?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20617720/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 18:53:57  来源:igfitidea点击:

Why doesn't the browser reuse the authorization headers after an authenticated XMLHttpRequest?

javascripthtmlangularjsbasic-authenticationsingle-page-application

提问by Sylvain

I'm developing Single Page App using Angular. The backend exposes REST services that require Basic authentication. Getting index.html or any of the scripts does not require authentication.

我正在使用 Angular 开发单页应用程序。后端公开需要基本身份验证的 REST 服务。获取 index.html 或任何脚本不需要身份验证。

I have an odd situation where one of my view has a <img>where the srcis the url of a REST API that requires authentication. The <img>is processed by the browser and I have no chance to set the authorization header for GET request it makes. That causes the browser to prompt for credentials.

我有一个奇怪的情况:我的观点的人有一个<img>,其中src是一个REST API需要验证的URL。该<img>是由浏览器处理,我没有机会来设置它使GET请求的授权头。这会导致浏览器提示输入凭据。

I attempted to fix this by doing this:

我试图通过这样做来解决这个问题:

  1. Leave imgsrcempty in the source
  2. At "document ready", make an XMLHttpRequestto a service (/api/login) with the Authorization header, just to cause the authentication to occur.
  3. Upon completing that call, set the img srcattribute, thinking that by then, the browser would know to include the Authorization header in subsequent requests...
  1. imgsrc在源空
  2. 在“文档准备好”时,使用 Authorization 标头XMLHttpRequest向服务 ( /api/login)发送一个,只是为了使身份验证发生。
  3. 完成该调用后,设置img src属性,认为到那时,浏览器将知道在后续请求中包含 Authorization 标头...

...but it doesn't. The request for the image goes out without the headers. If I enter the credentials, then all other images on the page are right. (I've also tried and Angular's ng-srcbut that produced the same result)

......但它没有。对图像的请求在没有标题的情况下发出。如果我输入凭据,则页面上的所有其他图像都是正确的。(我也试过和 Angular 的,ng-src但产生了相同的结果)

I have two questions:

我有两个问题:

  1. Why didn't the browser (IE10) include the headers in all requests after a successful XMLHttpRequest?
  2. What can I do to work around this problem?
  1. 为什么浏览器(IE10)在成功后没有在所有请求中包含标头XMLHttpRequest
  2. 我可以做些什么来解决这个问题?


@bergi asked for requests' details. Here they are.

@bergi 询问请求的详细信息。他们来了。

Request to /api/login

请求 /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

Response (/api/login)

响应(/api/登录)

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

Request to /user/picture/2218:

请求 /user/picture/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

And then the web browser prompts for credentials. If I enter them, I get this response:

然后 Web 浏览器会提示输入凭据。如果我输入它们,我会得到以下回复:

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

回答by ComFreek

Basic idea

基本理念

Load the images via JavaScript and display them on the site. The advantage is that the authentication credentials will never find their way into the HTML. They will resist at the JavaScript side.

通过 JavaScript 加载图像并在网站上显示它们。优点是身份验证凭据永远不会进入 HTML。他们会在 JavaScript 方面抵制。

Step 1: load the image data via JS

第一步:通过JS加载图片数据

That's basic AJAX functionality (see also XMLHttpRequest::open(method, uri, async, user, pw)):

这是基本的 AJAX 功能(另见 参考资料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) {
    // ...
  }
};

Step 2: format the data

第二步:格式化数据

Now, how can we display the image data? When using HTML, one would normally assign an URI to the srcattribute of the image element. We can apply the same principle here except for the fact that we use data URIsinstead of 'normal' http(s)://derivates.

现在,我们如何显示图像数据?使用 HTML 时,通常会src为图像元素的属性分配一个 URI 。我们可以在这里应用相同的原则,除了我们使用数据 URI而不是“正常”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 )));
}

Canvas

帆布

There is also another option which consists in painting the loaded data in a <canvas>field. This way, the user won't be able to right-click the image (the area where the canvas is positioned) as opposed to the <img>and data URIs where the user will see a long data URI when viewing the image properties panel.

还有另一个选项是在<canvas>字段中绘制加载的数据。这样,用户将无法右键单击图像(画布所在的区域),这<img>与用户在查看图像属性面板时会看到长数据 URI 的 URI 和数据 URI 不同。

回答by flup

The google drive uploader is created using angular js. Its authors faced a similar problem. The icons were hosted on a different domain and putting them as img src=violated the CSP. So, like you, they had to fetch the icon images using XHR and then somehow manage to get them into the imgtags.

谷歌驱动上传器是使用 angular js 创建的。它的作者面临着类似的问题。这些图标托管在不同的域中,并将它们视为img src=违反了 CSP。因此,像您一样,他们必须使用 XHR 获取图标图像,然后以某种方式设法将它们放入img标签中。

They describe how they solved it. After fetching the image using XHR, they write it to the HTML5 local file system. They put its URL on the local file system in the img's srcattribute using the ng-srcdirective.

他们描述了他们是如何解决它的。使用 XHR 获取图像后,他们将其写入 HTML5 本地文件系统。他们使用指令将其 URL 放在本地文件系统的img'ssrc属性中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);
  ...
}

As for the why, I don't know. I assume that creating a session token for retrieving the images is out of the question? I'd expect that Cookie headers do get sent? Is it a cross-origin request? In that case, do you set the withCredentialsproperty? Is it a P3P thing perhaps?

至于为什么,我也不知道。我认为创建用于检索图像的会话令牌是不可能的?我希望 Cookie 标头确实被发送?是跨域请求吗?在这种情况下,您是否设置了withCredentials属性?也许是P3P的东西?

回答by SzabV

Another approach would be to add an end point to your sites back end that proxied the image request. So your page could request it without credentials and the back end would take care of the authentication. The back end could also cache the image if it didn't change frequently or you knew the frequency with which it was updated. This is fairly easy to do on the back end, makes your front end simple and prevents credentials being sent to the browser.

另一种方法是向代理图像请求的站点后端添加一个端点。因此,您的页面可以在没有凭据的情况下请求它,后端将负责身份验证。如果图像不经常更改或者您知道更新的频率,后端也可以缓存图像。这在后端很容易做到,使您的前端变得简单并防止将凭据发送到浏览器。

If the issue is authentication then the links could contain a single use token generated for the user that is authenticated and only accessible from their current browser session. Giving secure access to the content only for the user it was intended for and only for the time they are authorized to access it. This would also require work in the back end, however.

如果问题是身份验证,则链接可能包含为经过身份验证且只能从其当前浏览器会话访问的用户生成的一次性令牌。仅向预期的用户授予对内容的安全访问权限,并且仅在他们被授权访问内容的时间内提供安全访问权限。然而,这也需要在后端工作。

回答by Jonas

It seems to me that to solve your problem you should change the design of your app, instead of trying to hack your way around how browsers actually work.

在我看来,要解决您的问题,您应该更改应用程序的设计,而不是试图绕过浏览器的实际工作方式。

A request to a secure URL will always need authentication, regarding of it being done by the browser with an img tag or in javascript.

对安全 URL 的请求总是需要身份验证,因为它是由浏览器使用 img 标签或在 javascript 中完成的。

If you can perform authorization automatically without user interaction, you can do it on the server side and you don't need to send any user+pass to the client to do this. If that is the case, you could change the code behind https://myserver/dev30281_WebServices/api/user/picture/2218to perform the authorization and serve the image, without HTTP auth, only if the user is authorized to request it, otherwise return a 403 forbidden response (http://en.wikipedia.org/wiki/HTTP_403).

如果可以在没有用户交互的情况下自动执行授权,则可以在服务器端进行,而无需向客户端发送任何用户+密码即可执行此操作。如果是这种情况,您可以更改背后的代码https://myserver/dev30281_WebServices/api/user/picture/2218以执行授权并提供图像,无需 HTTP 身份验证,仅当用户被授权请求它时,否则返回 403 禁止响应(http://en.wikipedia.txt)。组织/维基/HTTP_403)。

Another possible solution would be separate the pages that include the secure images from the rest of the app. So you would theoretically have two single-page-apps. The user would be required to login to access the secure part. I'm not sure though if this is possible in your case, since you didn't state all requirements. But it makes more sense that if you want to serve secure resources that require authentication, that the user should be prompted for credentials, just as the browser does.

另一种可能的解决方案是将包含安全图像的页面与应用程序的其余部分分开。所以理论上你会有两个单页应用程序。用户需要登录才能访问安全部分。我不确定这在您的情况下是否可行,因为您没有说明所有要求。但更有意义的是,如果您想提供需要身份验证的安全资源,则应提示用户输入凭据,就像浏览器所做的那样。

回答by Sergey Pekar

I always parse

我总是解析

Set-Cookieheader value in previous (or first login request) and then send it's value in next requests.

在前一个(或第一次登录请求)中设置 Cookie标头值,然后在下一个请求中发送它的值。

Something like this

像这样的东西

Response after first request:

第一次请求后的响应:

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

Any next request:

任何下一个请求:

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;

As you can see I send ASP.NET_SessionId="any value" value in Cookieheader. If server uses php you should parse PHPSESSID="some value"

如您所见,我在Cookie标头中发送了 ASP.NET_SessionId="any value" 值。如果服务器使用 php 你应该解析 PHPSESSID="some value"

回答by Aziz Shaikh

You need to try using the Access-Control-Allow-Credentials: trueheader. I once encountered an issue with IE which eventually boiled down to the use of this header. Also set $httpProvider.defaults.headers.get = { 'withCredentials' : 'true' }in the angular js code.

您需要尝试使用Access-Control-Allow-Credentials: true标题。我曾经遇到过 IE 的问题,最终归结为使用此标头。也在$httpProvider.defaults.headers.get = { 'withCredentials' : 'true' }angular js代码中设置。

回答by Franklin Yu

As for the reason: I tried Chrome and Firefox, and both remember basic authorization only ifthe credential is entered directly from Browser UI, i.e. the pop-up made by browser. It will not remember it if the credential came from JavaScript, although the HTTP request is the same. I guess this is by design, but I don't see it mentioned in standard.

至于原因:我试过 Chrome 和 Firefox,两者都只直接从浏览器 UI输入凭据时才记住基本授权,即浏览器弹出的窗口。如果凭证来自 JavaScript,它不会记住它,尽管 HTTP 请求是相同的。我想这是设计使然,但我没有看到标准中提到它。