前端解决跨域问题

612 阅读4分钟

1 跨域产生的原因

  • 跨域浏览器同源策略限制的一类请求场景
  • 什么是同源策略?
    同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制以下几种行为:

1.) CookieLocalStorageIndexDB 无法读取
2.) DOMJs对象无法获得
3.) AJAX 请求不能发送

2 跨域解决方案

1、 通过jsonp跨域
2、 document.domain + iframe跨域
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS)
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域

3 jsonp跨域实现

  • JSONP:利用在页面中创建
  • JSONP跨域的基本原理:由于script 标签不受浏览器同源策略的影响,允许跨域引用资源。因此,通过动态创建 script 标签,然后利用 src 属性进行跨域。
  • JSONP的优点:不受同源策略的限制;兼容性好,在老的浏览器中可以运行;请求完毕后可以通过调用 callback 的方式回传结果。
  • JSONP的缺点:只支持 GET 这种HTTP请求;jsonp在调用失败的时候不会返回各种HTTP状态码;安全性:callback传入的参数是在后端进行了一次拼接,这即代表存在注入的可能,如果后端设计不当,是有可能出现安全风险的。
// jsonp的封装
function jsonp(params) {
    // 创建script标签并加入到head中
    var callbackName = params.jsonp;
    var head = document.getElementsByTagName('head')[0];
    // 设置传递给后台的回调参数名
    params.data['callback'] = callbackName;
    var data = formatParams(params.data);
    var script = document.createElement('script');
    // 发送请求
    script.src = params.url + '?' + data;
    // 创建jsonp回调函数
    window[callbackName] = function (res) {
        head.removeChild(script);
        clearTimeout(script.timer);
        window[callbackName] = null;
        params.success && params.success(res);
    };
    head.appendChild(script);
    // 为了得知此次请求是否成功,设置超时处理
    if (params.time) {
        script.timer = setTimeout(function () {
            window[callbackName] = null;
            head.removeChild(script);
            params.error && params.error({
                message: '超时'
            });
        }, 500);
    }
}

// 格式化参数
function formatParams(data) {
    var arr = [];
    for (var name in data) {
        arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
    };
    // 添加一个随机数,防止缓存
    arr.push('v=' + random());
    return arr.join('&');
}
        
// 获取随机数
function random() {
    return Math.floor(Math.random() * 10000 + 500);
}
jsonp({
    url: url,
    jsonp: 'jsonpCallback',
    data: dataParams,
    success: function (res) {
        params.success && params.success();
    },
    error: function (error) {}
})

4 为什么要用img标签来进行跨域?

凡带src属性的,必能跨域,所以你可以看到还有种方案叫JSONP。但是src对这个事应该也挺无奈的,算是个历史遗留问题吧,不属于跨域问题的原始设计,就像修了堵墙还挖了个洞出来一样。

5 ping图片跨域

图片Ping是客户端向服务器的单向通信,因为src请求资源不属于同源策略,所以一般可以用来做埋点,比如监听网页的PV(Page View),UV(Unique Visitor)。通俗点讲就是曝光率。

6 canvas跨域图片转base64

/**
 * 在线图片转换base64
 * @param imgUrl 图片的url
 *
 *  
 * */ 

export function dealImage(imgUrl) {
  // 一定要设置为let,不然图片不显示
  let image = new Image();
  // 解决跨域问题 必须后端设置支持跨域,后端不配合白搭
  image.setAttribute('crossOrigin', 'anonymous');
  const imageUrl = imgUrl;
  // 加随机数,防止图片请求到缓存
  image.src = imageUrl + '?v=' + Math.random()
  // image.onload为异步加载
  image.onload = () => {
    var canvas = document.createElement('canvas');
    canvas.width = image.width;
    canvas.height = image.height;
    var context = canvas.getContext('2d');
    context.drawImage(image, 0, 0, image.width, image.height);
    var quality = 0.8;
    // 这里的dataurl就是base64类型  
    // 使用toDataUrl将图片转换成jpeg的格式,不要把图片压缩成png,因为压缩成png后base64的字符串可能比不转换前的长!
    const dataurl = canvas.toDataURL('image/jpeg', quality);
    console.log('wbk dataurl :>> ', dataurl );
  }
}

跨域时,如果资源服务器不允许的话,前端是不能获取到该资源的,也是为了安全考虑,可以尝试做一层代理,但有些服务器也会做防爬处理。

canvas 中的图片可能来自一些第三方网站。在资源服务器不允许的情况下,使用跨域的图片绘制时会污染画布,这是出于安全考虑。在“被污染”的画布中调用 toBlob() toDataURL() getImageData() 会抛出安全警告。

7 尝试使用img标签进行jsonp跨域请求获取在线图片操作(失败)

猜想原因,jsonp只支持get请求跨域,没法操作图片

猜想原因2,jsonp 需要后端进行配合

// jsonp的封装
function jsonp(params) {
  // 创建script标签并加入到head中
  var callbackName = params.jsonp;
  var head = document.getElementsByTagName('head')[0];
  // 设置传递给后台的回调参数名
  params.data['callback'] = callbackName;
  var data = formatParams(params.data);
  var img = document.createElement('img');
  // 发送请求
  img.src = params.url + '?' + data;
  // 创建jsonp回调函数
  window[callbackName] = function (res) {
      head.removeChild(img);
      clearTimeout(img.timer);
      window[callbackName] = null;
      params.success && params.success(res);
  };
  head.appendChild(img);
  // 为了得知此次请求是否成功,设置超时处理
  if (params.time) {
      img.timer = setTimeout(function () {
          window[callbackName] = null;
          head.removeChild(img);
          params.error && params.error({
              message: '超时'
          });
      }, 500);
  }
}

// 格式化参数
function formatParams(data) {
  var arr = [];
  for (var name in data) {
      arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
  };
  // 添加一个随机数,防止缓存
  arr.push('v=' + random());
  return arr.join('&');
}
      
// 获取随机数
function random() {
  return Math.floor(Math.random() * 10000 + 500);
}