参考:HTTP访问控制(CORS)

当页面与页面请求的资源 不在 同一域/协议/端口 时,会发起一个 跨域HTTP请求

出于安全原因,浏览器会限制从脚本内发起的跨源HTTP请求或响应。

通过跨域资源共享 CORS(cross-origin share standard) 机制,可使跨域数据传输安全进行。

1.跨域的请求类型

HTTP请求头部字段

头部 名称 说明
Origin 源站域名 XMLHttpRequest跨域才发送
Image需标明 crossOrigin=anonymous/use-credentials
有些服务通过判断有没有Origin来决定是否展示授权相关的响应头部字段,例如阿里云OSS
Access-Control-Request-Method 实际请求的 HTTP 方法
Access-Control-Request-Headers 实际请求的携带的自定义头部字段名字

HTTP响应头部字段

头部 名称 说明
Access-Control-Allow-Origin 允许访问该资源的URI *http://example.com; 若为附带身份凭证的请求,此处不能为 *
Access-Control-Allow-Credentials 是否允许附带身份凭证的请求 true; 若允许则请求与响应资源互通cookie
Access-Control-Expose-Headers 允许客户端读取的响应头部 x-server-one,x-server-two
Access-Control-Max-Age 预检请求的结果缓存多少秒 3600; 缓存时间内不进行 options 请求
Access-Control-Allow-Methods 预检请求:实际请求允许的 HTTP 方法
Access-Control-Allow-Headers 预检请求:实际请求允许的自定义头部

1.简单请求

非预检请求都是简单请求。

2.预检请求

当请求满足下述任一条件时,将首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。

  • 使用了下面任一 HTTP 方法: PUT / DELETE / CONNECT / OPTIONS / TRACE / PATCH
  • 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:Accept / Accept-Language / Content-Language / Content-Type / DPR / Downlink / Save-Data / Viewport-Width / Width
  • Content-Type 的值不属于下列之一: application/x-www-form-urlencoded / multipart/form-data / text/plain

3.附带身份凭证的请求

2.可使用CORS的场景

1.XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求

例1:domain1.comrequest.js 请求 domain2.com 上的 1.php

// request.js
const req = new XMLHttpRequest();
// 跨域:需要返回响应头 Access-Control-Allow-Origin: *
req.open('POST', 'https://domain2.com/1.php', true);
// 客户端自定义请求头:需要返回响应头 Access-Control-Expose-Headers: X-Client-One,X-Client-Two
req.setRequestHeader('X-Client-One', 'pingpong1');
req.setRequestHeader('X-Client-Two', 'pingpong2');
// 需要附带身份凭证:需要返回响应头 Access-Control-Allow-Credentials: true
req.withCredentials = true;
req.onreadystatechange = () => {
  // 请求已完成,且响应已就绪
  if (req.readyState === 4) {
    console.log(req.getResponseHeader('x-server-one'));
    console.log(req.getResponseHeader('x-server-two'));
    console.log(req.response);
  }
};
req.send();

过程

# 请求1头
Origin: https://domain1.com
Access-Control-Request-Headers: x-client-one,x-client-two
Access-Control-Request-Method: POST

# 响应1头
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: X-Client-One,X-Client-Two
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS
Access-Control-Allow-Origin: https://domain1.com
Access-Control-Expose-Headers: x-server-one,x-server-two
x-server-one: hello
x-server-two: hihi

# 请求2头
Origin: https://domain1.com
X-Client-One: pingpong1
X-Client-Two: pingpong2

# 响应2头
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: X-Client-One,X-Client-Two
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS
Access-Control-Allow-Origin: https://domain1.com
Access-Control-Expose-Headers: x-server-one,x-server-two
x-server-one: hello
x-server-two: hihi

2.CSS 中通过 @font-face 使用跨域字体资源

例1:domain1.com 请求 domain2.com 上的 demo.woff2

<!-- index.html -->
<style>
  @font-face {
    font-family: 'Demo';
    src: url(http://diary8.com/demo.woff2) format('woff2');
  }
</style>
<i style="font-family: Demo;">hi</i>

处理

# 请求1头
Origin: https://domain1.com

# 响应1头
Access-Control-Allow-Origin: https://domain1.com

3.读取 因跨域图片被污染的canvas

尽管不通过 CORS 就可以在 <canvas> 中使用其他来源的图片,但是这会污染画布,并且不再认为是安全的画布,这将可能在 <canvas> 检索数据过程中引发异常

canvas 被污染 的情况下不能使用此方法

  • ctx.getImageData(sx, sy, sw, sh)
  • canvas.toBlob(callback, type, encoderOptions)
  • canvas.toDataURL(type, encoderOptions)

例1:domain1.comnew Image() 读取 domain2.com/demo.jpg 内容

const img = new Image();
img.crossOrigin = 'anonymous';
img.addEventListener('load', () => {
  // 1.绘制canvas,将图片覆盖在上面
  const domCanvas = document.createElement('canvas');
  const ctx = domCanvas.getContext('2d');
  domCanvas.width = img.width;
  domCanvas.height = img.height;
  ctx.drawImage(img, 0, 0);
  // 2.读取canvas内容(此处受限)
  console.log(domCanvas.toDataURL());
});
img.src = 'https://domain2.com/demo.jpg';

处理

# 请求1头
Origin: https://domain1.com

# 响应1头
Access-Control-Allow-Origin: https://domain1.com

4.跨域图片

<img src="https://domain2.com/demo.jpg" crossorigin="use-credentials">
const img = new Image();
img.crossOrigin = 'use-credentials';
img.addEventListener('load', () => {
  // 图片加载完成
});
img.src = 'https://domain2.com/demo.jpg';

3.附:读取image内容的2种方式

方法1: 通过canvas画图读取

const img = new Image();
img.crossOrigin = 'anonymous';
img.addEventListener('load', () => {
  // 1.绘制canvas,将图片覆盖在上面
  const domCanvas = document.createElement('canvas');
  const ctx = domCanvas.getContext('2d');
  domCanvas.width = img.width;
  domCanvas.height = img.height;
  ctx.drawImage(img, 0, 0);
  // 2.读取canvas内容(此处受限)
  console.log(domCanvas.toDataURL());
});
img.src = 'https://domain2.com/demo.jpg';

方法2:File读取input file内容

<input id="img" type="file">
<script>
  const domImg = document.getElementById('img');
  domImg.addEventListener('change', () => {
    const reader = new FileReader();
    reader.addEventListener('load', () => {
      console.log(reader.result);
    });
    reader.readAsDataURL(domImg.files[0]);
  });
</script>

王二傻
36 声望0 粉丝

Front-end, PHPer