参考: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.com
上 request.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.com
上 new 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>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。