为什么canvas.toDataURL()会抛出安全异常?

95

我睡眠不足了吗?下面的代码:

var frame=document.getElementById("viewer");
frame.width=100;
frame.height=100;

var ctx=frame.getContext("2d");
var img=new Image();
img.src="http://www.ansearch.com/images/interface/item/small/image.png"

img.onload=function() {
    // draw image
    ctx.drawImage(img, 0, 0)

    // Here's where the error happens:
    window.open(frame.toDataURL("image/png"));
}

出现了这个错误:

SECURITY_ERR: DOM Exception 18

这个应该没有不工作的理由!请问有人可以解释一下吗?


请点击此处查看解决方案:如何在Adobe AIR中使用canvas.toDataURL()获取图像的base64? - Danny Beckett
2
这个解决方案对于不使用Adobe AIR的人似乎没有用处。 - ObscureRobot
10个回答

69

规范中提到:

每当调用toDataURL()方法的画布元素其起源干净标志设置为false时,该方法必须引发SECURITY_ERR异常。

如果图像来自另一个服务器,则无法使用toDataURL()。


6
如果攻击者能够猜测到你私人网站上的一张图片的名称,他可以通过在画布上绘制它并将新图像发送到他的网站来获取该图片的副本。从我的角度来看,主要限制是避免绘制另一个网站的内容,但安全性很复杂,因为攻击者可以找到任何意想不到的网站中的漏洞。 - AlfonsoML
3
请注意子域名也很重要。根据我的经验,在Chrome中,当调用被认为跨越子域名时,会出现一个SECURITY_ERR:DOM例外18:
  1. 在http://www.example.com/some/path/index.html中,对于foo.example.com中的视频或图像
  2. 当按照1中相同的页面输入URL http://example.com/some/path/index.html,并尝试调用www.example.com中视频或图像的toDataUrl()函数时。
- Sam Dutton
3
@AlfonsoML,也许我错了,但是“如果攻击者能够猜到您在私人网站上的图片名称”,他可能会使用curl而不是浏览器来获取该图像。我的观点是:公共URL公共内容。 - kilianc
4
即使我使用数据URL,我仍然遇到了这个问题。是否有一种方法可以解决数据URL的这个问题? - Sujay
7
这项限制是为了防止攻击者通过让您的浏览器(使用认证cookie)获取图像并将其内容发送给攻击者来实施攻击;攻击者没有您的cookie,因此无法使用curl获取您被授权获取的相同资源。 "公共URL,公共内容" 的想法相当有缺陷:我的Facebook页面有公共组件,但只有具备正确认证令牌的用户才能访问更多内容。 - apsillers
显示剩余3条评论

21

将跨域属性设置在图像对象上对我有用(我正在使用fabricjs)

    var c = document.createElement("img");
    c.onload=function(){
        // add the image to canvas or whatnot
        c=c.onload=null
    };
    c.setAttribute('crossOrigin','anonymous');
    c.src='http://google.com/cat.png';

对于那些使用fabricjs的人,这里是如何修补Image.fromUrl的方法

// patch fabric for cross domain image jazz
fabric.Image.fromURL=function(d,f,e){
    var c=fabric.document.createElement("img");
    c.onload=function(){
        if(f){f(new fabric.Image(c,e))}
        c=c.onload=null
    };
    c.setAttribute('crossOrigin','anonymous');
    c.src=d;
};

谢谢,它在Chrome中对我有效。不过我没有使用fabric.js。 - Philip007
我确实使用fabricjs,但无法解决它。您会如何使用此代码完成此操作?function wtd_load_bg_image(img_url) { 如果(img_url) { var bg_img = new Image(); bg_img.onload = function() { canvasObj.setBackgroundImage(bg_img.src,canvasObj.renderAll.bind(canvasObj),{ originX:'left', originY:'top', left:0, top:0 }); }; bg_img.src = img_url; } } - HOY
同样适用于Firefox。 - vanowm
我稍微调整了你的代码,但它确实帮了我很大的忙。 - Marcin Żmigrodzki

17
如果图片存储在设置了Access-Control-Allow-Origin或Access-Control-Allow-Credentials的主机上,您可以使用跨源资源共享(CORS)。有关更多详细信息,请参见此处(crossorigin属性)
另一种选择是让您的服务器拥有一个端点来获取和提供图像。(例如 http://your_host/endpoint?url=URL) 这种方法的缺点是延迟和理论上不必要的获取。
如果还有其他的替代方案,我很乐意听取。

嗨,我刚刚做了这件事,我在图像上设置了Access-Control-Allow-Origin:*,我设置了crossorigin ='anonymous',然后我在画布上绘制图像,然后调用toDataUrl,但仍然出现SECURITY_ERR:DOM异常18。 - skrat

14

如果图像托管能够为图像资源提供以下HTTP头,并且浏览器支持CORS,则似乎有一种方法可以防止这种情况发生:

access-control-allow-origin: *
access-control-allow-credentials: true

在此处说明:http://www.w3.org/TR/cors/#use-cases


1
access-control-allow-credentials: true 与 access-control-allow-origin: * 不兼容。当前者被设置时,后者应该有一个来源值,而不是来自 https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS 的通配符值。 - Silver Gonzales

4

最后我找到了解决方案。只需要在fromURL函数中添加crossOrigin作为第三个参数即可。

fabric.Image.fromURL(imageUrl, function (image) {
            //your logic
    }, { crossOrigin: "Anonymous" });

3

我曾经遇到相同的问题,所有图片都托管在同一个域名下...所以,如果有人遇到相同的问题,这是我解决的方法:

我有两个按钮:一个用于生成画布,另一个用于从画布生成图像。只有当我将所有代码写在第一个按钮上时,它才能正常工作。很抱歉我不知道为什么。所以当我点击它时,同时生成画布和图像...

当代码位于不同函数中时,我总是遇到此安全问题... =/


2

我能够使用以下方法使其工作:

在源服务器上,将以下内容写在您的.htaccess文件的第一行:

Header add Access-Control-Allow-Origin "*"

创建<img>元素时,请按以下方式进行:
// jQuery
var img = $('<img src="http://your_server/img.png" crossOrigin="anonymous">')[0]

// or pure
var img = document.createElement('img');
img.src='http://your_server/img.png';
img.setAttribute('crossOrigin','anonymous');

1

如果你只是在画布上绘制一些图像,请确保从同一个域加载图像。

www.example.comexample.com 是不同的。

所以请确保你的图像和浏览器地址栏中的网址是相同的,无论是否带有"www"。


1

你的ID中不能包含空格。

更新

我猜测图片位于与执行脚本不同的服务器上。当我在自己的页面上运行它时,我能够复制你的错误,但是一旦我使用了在同一域上托管的图像,它就可以正常工作了。所以这与安全有关 - 将图像放在你的网站上。有人知道为什么会这样吗?


-1
我正在使用fabric.js,可以通过使用toDatalessJSON而不是toDataURL来解决这个问题:
canvas.toDatalessJSON({ format: 'jpeg' }).objects[0].src

编辑:算了吧。这样导出的只是背景图像,没有上面的绘画,所以实际上并不是很有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接