画布图像跨平台不安全错误

4

我有这段代码,可以从不同的服务器URL创建画布图像

function getBase64Image(imageUri) { 
    var canvas = document.createElement("canvas");
    ctx = canvas.getContext("2d"); 
    var img = new Image();
    img.src = imageUri;
    img.crossOrigin = "Anonymous";
    img.onload = function() {
       canvas.width = this.width;
       canvas.height = this.height;
       ctx.drawImage(img, 0, 0, this.width, this.height);
       var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
       var dataURL = canvas.toDataURL("image/png");
       document.getElementById("dummyhiddenField").value = dataURL;
   };
}

我正在尝试从服务器获取图像,并将其画布URL设置为隐藏字段,但仅对本地图像有效。

根据stackoverflow上的其他答案,我设置了crossOrigin =“Anonymous”,但它不起作用,也无法从服务器设置访问来源。必须从localscript中管理所有内容。

谢谢


根据stackoverflow上的其他答案,是什么其他答案? - Quentin
我设置了 crossOrigin = "Anonymous",但它没有起作用。我并不意外。这只是意味着当进行 CORS 请求时,不会包括 cookies 等信息。 - Quentin
谢谢回复, 我已经花了8到10个小时来寻找解决方案,许多stackoverflow用户都遇到了以下类似的问题。 - Kirankumar Dafda
http://stackoverflow.com/questions/7988202/how-to-use-canvas-to-modify-images-from-another-domainhttps://dev59.com/iF8e5IYBdhLWcg3wcJ8qhttps://dev59.com/AGkw5IYBdhLWcg3wVpBi - Kirankumar Dafda
我只有Web服务来进行任何类型的图像更改,并且我正在创建一个本地运行的应用程序,那么是否有直接的解决方案可供使用?如果有,请给我建议。非常感谢。 - Kirankumar Dafda
显示剩余10条评论
1个回答

18

使用跨域内容更新Html5 Canvas

本文将介绍如何处理跨域内容对html5画布的影响,以及如何在遵守相关安全限制的情况下进行操作。请注意,此处更新的内容基于2016年1月发布的新方法,可以允许绘制跨域图像,而不会污染画布。

在html5画布上绘制跨域内容会导致画布被"污染"

您可以在画布上绘制来自其他域的图像,它们将显示在画布上。从其他域中访问资源称为“跨源资源共享”,通常简称为“CORS”。

出于安全原因,绘制CORS内容(例如图像)将导致画布被“污染”。

如果画布被污染,则无法使用以下canvas和context方法:

  • context.getImageData:获取画布上的像素数据
  • canvas.toDataURL:将画布导出为图像

您不能欺骗画布违反其CORS安全限制,但是您可以通过满足CORS安全限制来绘制跨源图像而不使画布被污染。

处理图像以避免污染画布的“通常”(也是最简单)方法:

将您的图像与网页放在同一中。您可以拥有多个物理服务器提供内容,但是图像必须与创建画布的html代码(或javascript代码)相同。这样就满足了CORS限制,画布就不会被污染。

关于在本地电脑上进行开发时的CORS注意事项

解决方案#1:您可以在开发计算机上安装Web服务器,并从一个域来提供网页文件(.html,.js等)和图像文件(.png,.jpg等)。

您的开发计算机的文件夹声明为不同的域。因此,从本地磁盘子目录绘制图像将违反CORS限制,因为不同的本地文件夹是不同的域。

解决方案#2: 在开发过程中,您可以将网页文件和图像文件都放在桌面上,这样图像将被声明为在同一个域中,而且画布将不会被污染。

在图像位于不同域时满足CORS限制

解决方案#3:可以在画布上使用跨域图像而不会污染它。为此,您必须满足以下要求:

  • Clientside:图像对象必须设置crossOrigin属性,以允许跨域内容。该属性可以在html元素标签或javascript中设置。启用的设置为“anonymous”和“use-credentials”。

  • Serverside:服务器必须配置返回头表明响应包含授权内容。

根据配置可能需要多个响应头:

Access-Control-Allow-Origin将返回匿名授权(*)或基于请求返回特定授权。

如果身份验证需要附加信息(如cookies),则需要Access-Control-Allow-Credentials

Access-Control-Expose-Headers使客户端可以访问其他响应信息。

在服务器上启用跨域请求可能很复杂,特别是当提供基于客户端角色的授权内容时。有关启动配置的更多信息,请访问http://enable-cors.org/index.html

使用允许匿名访问其图像的跨域图像主机

解决方案#4:一些公共图像主机允许您上传以CORS兼容方式为客户端提供服务的图像。几个例子是:imgurdropbox。Stackoverflow的图像托管在Imgur上。

以下是如何以CORS兼容方式在Dropbox.com上提供图像的示例:

  1. 注册Dropbox帐户
  2. 您会得到几个默认文件夹。将您的图像上传到特殊的“Public”文件夹中。这是Dropbox提供CORS兼容匿名访问的文件夹。
  3. 右键单击要提供服务的图像,然后选择“复制公共链接”。您的剪贴板将有一个指向CORS兼容图像的链接。
  4. 使用img标记或javascript加载图像到您的页面中。

以下是使用javascript从Dropbox获取CORS兼容图像对象的示例代码:

var img=new Image();
img.crossOrigin='anonymous';
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/sun.png";
img.onload=start;
function start(){
    context.drawImage(img,0,0);
    // The canvas is not tainted so 
    // the following both work without errors
    var url=canvas.toDataURL();
    var imageData=context.getImageData(0,0,100,100);
}

较新的解决方案:通过客户端用户同意来满足CORS

CORS安全限制旨在阻止坏人在您毫不知情的情况下秘密获取您的信息。

一直以来,浏览器都依靠客户端-服务器配置来满足安全要求。最近,浏览器开始允许跨域内容,前提是用户积极参与决定使用哪些内容。

解决方案#5: Chrome和Firefox现在允许客户端用户右键单击画布并将画布保存为图像。这相当于手动使用canvas.toDataURL创建一个图像对象并将该图像对象保存到本地驱动器。因为用户决定了是否将画布内容保存到本地驱动器并积极右键单击启动下载过程,所以满足了CORS要求。

解决方案#6: 您可以使用input元素,type ='file'让客户端用户选择图像。 用户甚至可以选择互联网URL (http://...)。同样,由于用户参与了选择过程,因此满足了CORS要求。

以下是示例代码,显示如何侦听用户使用输入选择图像:

// canvas vars
var canvas=document.createElement("canvas");
var ctx=canvas.getContext("2d");

// define max resulting image width,height (after resizing)
var maxW=100;
var maxH=100;

// listen for user to select files
var input = document.getElementById('input');
input.addEventListener('change', handleFiles);

function handleFiles(e) {
  var img = new Image;
  img.onload = function(){
    var iw=img.width;
    var ih=img.height;
    // scale down, if necessary
    if(iw>maxW || ih>maxH){
      var scale=Math.min((maxW/iw),(maxH/ih));
      iw*=scale;
      ih*=scale
    }
    // set canvas width/height to scaled size
    canvas.width=iw;
    canvas.height=ih;
    // draw+scale the img onto the canvas
    ctx.drawImage(img,0,0,iw,ih);
    // create a jpeg URL (with medium quality to save "weight") 
    var jpg=canvas.toDataURL('image/jpeg',0.60);
    // In Demo: add the jpg to the window
    // In production, accumulate jpg's & send to server
    $('<img />',{src:jpg}).appendTo('body');
  }
  // In Demo: Just process the first selected file 
  // In production: process all selected files 
  img.src = URL.createObjectURL(e.target.files[0]);
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>You can even enter a web URI (http://...)</h4>
<input type="file" id="input"/><br>

解决方案#7: 您可以使用新的FileReader让用户选择要在画布上绘制的图像。

// dropDiv event handlers
var dropDiv=document.getElementById("dropDiv");
dropDiv.addEventListener("dragenter", handleDragEnter, false);
dropDiv.addEventListener("dragover", handleDragOver, false);
dropDiv.addEventListener("drop", handleDrop, false);
//
function handleDragEnter(e) {
  e.stopPropagation();
  e.preventDefault();
}
//
function handleDragOver(e) {
  e.stopPropagation();
  e.preventDefault();
}
//
function handleDrop(e) {
  e.stopPropagation();
  e.preventDefault();

  var dt = e.dataTransfer;
  var files = dt.files;

  handleFiles(files);
}

//
function handleFiles(files) {

  for (var i=0;i<files.length;i++) {
    var file = files[i];
    var imageType = /image.*/;

    if (!file.type.match(imageType)) {
      continue;
    }

    var img = document.createElement("img");
    img.classList.add("obj");
    img.file = file;
    preview.appendChild(img);

    var reader=new FileReader();
    reader.onload=(function(aImg){
      return function(e) {
        aImg.onload=function(){
          var canvas=document.createElement("canvas");
          var ctx=canvas.getContext("2d");
          canvas.width=aImg.width;
          canvas.height=aImg.height;
          ctx.drawImage(aImg,0,0);
          document.body.appendChild(canvas);
        }
        // e.target.result is a dataURL for the image
        aImg.src = e.target.result;
      }; 
    })(img);
    reader.readAsDataURL(file);

  } // end for

} // end handleFiles

function calcNewAspect(imgWidth, imgHeight, maxWidth, maxHeight) {
  var ratio = Math.min(maxWidth/imgWidth,maxHeight/imgHeight);
  return {width:imgWidth*ratio,height:imgHeight*ratio };
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
#dropDiv{border:1px solid blue; width:300px;height:300px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Drag 1+ image(s) from desktop to blue dropDiv.</h4>
<div id="dropDiv"></div>
<div id="preview"></div>

未完待续...

这次更新可能需要包含更多信息。如果我有所疏忽,请随时评论,我会继续更新。 :-)


一些注意事项:1 即使服务器配置良好且<img>的crossOrigin属性已设置,您也必须在服务器上(例如http://localhost):尝试从file://协议进行跨域请求将无法正常工作。 2 http://imgur.com没有发送正确的标头,请参见[此元问题](http://meta.stackexchange.com/questions/267805/cors-for-stack-imgur),其中提供了代理服务作为解决方法,也可以编写自己的代理服务。 - Kaiido
  1. 如果设置并支持crossOrigin属性,但服务器不允许请求,则图像将根本无法加载,这将在<img>上引发“error”事件。
  2. 我会添加一条注释,说明CORS不是污染画布的唯一方法:IE<Edge将污染任何绘制了SVG的画布,Safari 9将污染绘制了包含<foreignObject>元素的SVG的画布。
- Kaiido
你好@markE,非常感谢你的回答,对我和其他寻找此类解决方案的人都非常有帮助。正如我之前在与@Kaiido的评论中提到的,我只有一个选择,那就是使用Access-Control-Allow-Origin。因此,我将遵循你的“Solution#3:”,这是我成功完成工作的第一次也是最后一次机会。 - Kirankumar Dafda
@Kirankumar,你也可以通过先将不安全的数据绘制到一个1px*1px的测试画布上,再使用try{tester.toDataURL(); tainted = false;}catch(e){tester = tester.cloneNode(true); tainted= true;}来避免画布被污染。 - Kaiido
是的 @Kaiido,我能理解,但首先我必须从跨域访问图像,然后才能对其进行任何操作。 - Kirankumar Dafda
显示剩余2条评论

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