图片下载跨域

1,680 阅读5分钟

研究vue-canvas-poster时,图片下载解决的跨域问题,在此将对其的理解做下汇总

一、跨域

1、跨域定义
广义:
第一:资源跳转:各种页面跳转。如:链接跳转;路由重定向;提交表单;
第二:资源嵌入:文件的引入。如:linkscriptimgframe,另外样式中背景图片以及字体的引入background:url()@font-face()
第三:脚本请求:各种的接口请求。js发起的ajax请求
狭义:
浏览器不能执行其他网站的脚本,浏览器同源策略引起的。同源:域名,协议,端口一致。
限制:Cookie、LocalStorage和IndexDB无法读取;DOM和Js对象无法获得;AJAX请求不能发送。
栗子:http【协议】://blog【子域名】.test【主域名】.com:8080【端口号】/

image.png
2、解决方案
方案一:document.domain + iframe跨域(仅适用于主域相同,子域不同)

 实现原理:通过document.domain来设置基础主域,浏览器通过查看它来判断是否为同域,共享cookie。

// 父窗口:(http://www.domain.com/a.html);    子窗口:(http://child.domain.com/b.html)
 // 父窗口代码
 <script>
    document.domain = 'domain.com';
    var user = 'admin';
  </script>
  <iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
// 子窗口代码
  <script>
    document.domain = 'domain.com';
    alert(window.parent.user); // 获取父窗口中变量
  </script>

方案二:location.hash + iframe跨域

 实现原理:A域与B域间跨域相互通信,通过中间页C来实现,三个页面,不同域利用iframe的location.hash传值,相同域间直接js访问通信。

 栗子描述:A与B不同域,B与C不同域,而A与C同域;不同域间利用hash值单向通信,同域间通过parent.parent访问

  // A域,a.html  http://www.domainA.com/a.html
   <iframe id="iframe" src="http://www.domainB.com/b.html" style="display:none;"></iframe>
   <script>
      var iframe = document.getElementById('iframe');
      setTimeout(function() {
        iframe.src = iframe.src + '#user=admin'; // 向b.html传hash值
      }, 1000);
      function onCallback(res) {
        alert('data from c.html ---> ' + res); // 开放给同域c.html的回调
      }
    </script>
  // B域,b.html  http://www.domainB.com/b.html
   <iframe id="iframe" src="http://www.domainA.com/c.html" style="display:none;"></iframe>
   <script>
      var iframe = document.getElementById('iframe');
      window.onhashchange = function () {
        iframe.src = iframe.src + location.hash
      }
    </script>
  // A域,c.html  http://www.domainA.com/c.html
   <script>
     // 监听的是b.html传过来的hash值
      window.onhashchange = function () {
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')) // 操作a.html的回调,将结果传回
      }
    </script>

方案三、window.name + iframe跨域

 实现原理:利用window的name值,在不同页面或者说不同域下加载后仍然存在,并且可以支持很长的name(2MB)

1、a.html   http://www.domain1.com/a.html
    var proxy = function(url, callback) {
      var state = 0;
      var iframe = document.createElement('iframe');
      iframe.src = url; // 加载跨域页面
      // onload触发两次,第一次加载跨域页面,并留存数据于window.name
      iframe.onload = function() {
        if (state === 1) {
          // 第二次onload(同域proxy页)成功后,读取同域window.name中数据
          callback(iframe.contentWindow.name);
          destoryFrame()
         } else if (state === 0) {
           // 第一次onload(跨域页)成功后,切换到同域代理页面
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1;
         }
       };
       document.body.appendChild(iframe);
       // 获取数据以后销毁这个iframe,释放内存;这保证了安全(不被其他域frame js访问)
       function destoryFrame () {
         iframe.contentWindow.document.write('');
         iframe.contentWindow.close();
         document.body.removeChild(iframe);
       }
    }
 
    // 请求跨域b页面数据 
    proxy('http://www.domain2.com/b.html',function(data){
       alert(data)
     })
2、proxy.html  http://www.domain1.com/proxy.html
  中间代理页,与a.html同域,内容为空即可  
3、 b.html  http://www.domain2.com/b.html
    <script>
        window.name = 'This is domain2 data!';
    </script>

 小结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。巧妙地绕过了浏览器的跨域访问限制。

方案四、postMessage跨域

 介绍:postMessageAPI是为数不多可以跨域操作的window属性之一。

 解决的问题:

   1、页面和其打开的新窗口的数据传递;

   2、多窗口之间消息传递;

   3、页面与嵌套的iframe消息传递;

   4、上面三个场景的跨域数据传递。

 使用参数:postMessage(data,origin)

  data:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。

  origin:协议 + 主机 + 端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话为"/"。

1、a.html  http://www.domain1.com/a.html
   <iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
    <script>       
      var iframe = document.getElementById('iframe');
      iframe.onload = function() {
         var data = {name: 'aym'}; // 向domain2传送跨域数据
         iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
      }
      // 接受domain2返回数据  
      window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
      }, false);
   </script>
2、b.html  http://www.domain2.com/b.html
    <script>
     // 接收domain1的数据
     window.addEventListener('message', function(e) {
       alert('data from domain1 ---> ' + e.data);
       var data = JSON.parse(e.data);
       if (data) {
          data.number = 16; 
         // 处理后再发回domain1
         window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
      }, false);
    </script>

方案五、跨域资源共享(CORS)

 普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置;若要带cookie请求:前后端都需要设置。

 注意点:同源策略限制,所读取的cookie为跨域请求接口,不属于当前页。
一、前置设置:

 1、原生的ajax

var xhr = new XMLHttpRequest();
xhr.withCredentials = true; // 前端设置是否带cookie
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded') //设置请求头
xhr.send('user=admin')
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200){
    alert(xhr.responseText);
  }
}

 2、jQuery ajax

 $.ajax({
    ...
   xhrFields: { withCredentials: true }, // 前端设置是否带cookie
   crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
    ...
});

 3、vue框架

axios设置:axios.defaults.withCredentials = true
vue-resource设置:Vue.http.options.credentials = true

二、服务端设置:

 CORS需要前后端配合,当后端设置成功之后,前端浏览器控制台则不会出现跨域报错信息,反之,就是未成功。

 1、Java后台

/*
 * 导入包:import javax.servlet.http.HttpServletResponse;
 * 接口参数中定义:HttpServletResponse response
 */

// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); 

// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true"); 

// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

 2、Node.js后台

var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request', function(req, res) {
  var postData = '';

  // 数据块接收中
  req.addListener('data', function(chunk) {
     postData += chunk;
  });

  // 数据接收完毕
  req.addListener('end', function() {
     postData = qs.parse(postData);
     // 跨域后台设置
     res.writeHead(200, {
        'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie
        'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)
        /* 
         * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
         * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
         */
         'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly的作用是让js无法读取cookie
        });
        res.write(JSON.stringify(postData));
        res.end();
    });
});

server.listen('8080');
console.log('Server is running at port 8080...');

方案六、nginx代理跨域

 1、nginx配置解决iconfont跨域

  浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置

location / {
  add_header Access-Control-Allow-Origin *;
}

 2、nginx反向代理接口跨域

  跨域原理:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题

  实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。