我正在使用Angular开发单页应用程序。后端公开需要基本身份验证的REST服务。获取index.html或任何脚本不需要身份验证。
我有一个奇怪的情况,其中一个视图有<img>
,其中src
是需要身份验证的REST API的网址。 <img>
由浏览器处理,我没有机会为它所做的GET请求设置授权标头。这会导致浏览器提示输入凭据。
我试图通过这样做来解决这个问题:
img
src
留空
XMLHttpRequest
)创建/api/login
,以便进行身份验证。img src
属性,认为届时浏览器会知道在后续请求中包含授权标头... ......但事实并非如此。图像请求没有标题。如果我输入凭据,则页面上的所有其他图像都是正确的。
(我也试过和Angular的ng-src
,但产生了相同的结果)
我有两个问题:
XMLHttpRequest
后的所有请求中都没有包含标题? @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
答案 0 :(得分:24)
通过JavaScript加载图像并将其显示在网站上。优点是身份验证凭据永远不会进入HTML。他们会在JavaScript方面抗拒。
这是基本的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) {
// ...
}
};
现在,我们如何显示图像数据?使用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请求是相同的。我猜这是设计使然,但我看不到它在标准中提到。