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

时间:2013-12-16 18:05:26

标签: javascript html angularjs basic-authentication single-page-application

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

我有一个奇怪的情况,其中一个视图有<img>,其中src是需要身份验证的REST API的网址。 <img>由浏览器处理,我没有机会为它所做的GET请求设置授权标头。这会导致浏览器提示输入凭据。

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

  1. 在源
  2. 中将img src留空
  3. 在“文档就绪”中,使用Authorization标头为服务(XMLHttpRequest)创建/api/login,以便进行身份验证。
  4. 完成该调用后,设置img src属性,认为届时浏览器会知道在后续请求中包含授权标头...
  5. ......但事实并非如此。图像请求没有标题。如果我输入凭据,则页面上的所有其他图像都是正确的。 (我也试过和Angular的ng-src,但产生了相同的结果)

    我有两个问题:

    1. 为什么浏览器(IE10)在成功XMLHttpRequest后的所有请求中都没有包含标题?
    2. 如何解决此问题?

    3. @bergi询问了请求的详细信息。他们在这里。

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

      回复(/ 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
      

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

      然后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
      

7 个答案:

答案 0 :(得分:24)

基本理念

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

步骤1:通过JS

加载图像数据

这是基本的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) {
    // ...
  }
};

第2步:格式化数据

现在,我们如何显示图像数据?使用HTML时,通常会为图像元素的src属性分配URI。我们可以在此处应用相同的原则,除非我们使用data URIs而不是“普通”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>字段中绘制加载的数据。这样,用户将无法右键单击图像(画布所在的区域),而不是<img>和数据URI,用户在查看图像时将看到长数据URI物业小组。

答案 1 :(得分:8)

谷歌驱动器上传器使用角度js创建。它的作者遇到了类似的问题。这些图标托管在不同的域上,并将它们img src=置于违反CSP的位置。所以,像你一样,他们必须使用XHR获取图标图像,然后以某种方式设法将它们放入img标签。

他们describe how they solved it。在使用XHR获取图像后,他们将其写入HTML5本地文件系统。他们使用ng-src指令将其URL放在img的{​​{1}}属性中的本地文件系统上。

src

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

答案 2 :(得分:4)

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

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

答案 3 :(得分:2)

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

对安全URL的请求总是需要身份验证,关于浏览器使用img标记或javascript进行身份验证。

如果您可以在没有用户交互的情况下自动执行授权,则可以在服务器端执行此操作,并且您无需向客户端发送任何用户+传递来执行此操作。如果是这种情况,您可以更改https://myserver/dev30281_WebServices/api/user/picture/2218后面的代码以执行授权并在没有HTTP身份验证的情况下提供图像,前提是用户有权请求它,否则返回403禁止响应({{3 }})。

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

答案 4 :(得分:2)

我总是解析

先前(或第一次登录请求)中的

Set-Cookie 标头值,然后在下次请求中发送它的值。

像这样的东西

第一次请求后的回复:

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

任何下一个请求:

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;

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

答案 5 :(得分:2)

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

答案 6 :(得分:0)

原因:我尝试使用Chrome和Firefox,并且仅在直接从浏览器用户界面输入凭据(即弹出的窗口)时,都记住了基本授权 通过浏览器。即使凭据来自JavaScript,也不会记住它,尽管HTTP请求是相同的。我猜这是设计使然,但我看不到它在标准中提到。