Blog
Blog copied to clipboard
同源策略与跨域请求
概述
跨域请求的存在是因为浏览器的同源策略。
同源策略
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。若地址里面的协议、域名和端口号均相同则属于同源。
同源策略要求协议、主机名、端口号要一致。当然我们可以通过document.domain
来修改同源要求,不过只能修改为它的父域。
比如:http://store.company.com/dir/other.html
的同源要求为http://store.company.com:80
,可以修改为document.domain = "company.com";
。
至于浏览器为什么会有同源策略,无非就是安全问题。为了保证用户信息的安全,防止恶意的网站窃取数据。
现代浏览器在安全性和可用性之间选择了一个平衡点。在遵循同源策略的基础上,选择性地为同源策略“开放了后门”。在浏览器中,
- 标签嵌入跨域脚本。语法错误信息只能被同源脚本中捕捉到。
- 标签嵌入CSS。
- 通过
展示的图片。
- 通过
- 通过 @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
- 通过
同源策略会影响下面情况:
- 脚本不可以获取和操作另一个源的DOM、Cookie、LocalStorage 和 IndexDB,比如不同源的iframe
- 不能通过Web API:
Fetch
、XMLHttpRequest
请求另一个源的数据
跨域请求
但是我们的很多业务场景都需要访问获取另一个源的数据。 这里还是有很多技巧的。
1. 借助img标签和script标签的src
仅支持http的get方法
2. JSONP
原理是借助script标签的src,需要服务器配合
3. CORS,需要客户端和服务器同时支持。
跨域资源共享,Cross-Origin Resource Sharing (CORS)。
跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站有权限访问哪些资源。 另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
简单请求
若请求满足所有下述条件,则该请求可视为“简单请求”:
使用下列方法之一:
GET
HEAD
POST
Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。该集合为:
Accept
Accept-Language
Content-Language
Content-Type (需要注意额外的限制)
DPR
Downlink
Save-Data
Viewport-Width
Width
Content-Type 的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
请求中没有使用 ReadableStream 对象。
简单请求的请求头:
客户端:Origin
服务端:Access-Control-Allow-Origin
post 和 put的区别
post是非幂等的,使用POST来创建资源,使用POST更新全部或一部分值。
put是幂等的,使用PUT更新某一资源,则必须要更新资源的全部属性。
预检请求
当请求满足下述任一条件时,即应首先发送预检请求:
使用了下面任一 HTTP 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
Content-Type 的值不属于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。
请求中使用了ReadableStream对象。
预检阶段交互的请求头:
客户端:Origin、Access-Control-Request-Method、Access-Control-Request-Headers
服务器:Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Max-Age
预检请求完成之后,发送实际请求。
附带身份凭证的请求
一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。将 XMLHttpRequest 的 withCredentials 标志设置为 true,从而向服务器发送 Cookies。
但是,如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true ,浏览器将不会把响应内容返回给请求的发送者。
对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。
相关的请求头部
下面这些首部字段无须手动设置。 当开发者使用 XMLHttpRequest 对象发起跨域请求时,它们已经被设置就绪。
Origin,表明预检请求或实际请求的源站。
Access-Control-Request-Method,将实际请求所使用的 HTTP 方法告诉服务器。
Access-Control-Request-Headers,将实际请求所携带的首部字段告诉服务器。
相关的响应头部
Access-Control-Allow-Origin:<origin> | *
Access-Control-Expose-Headers,让服务器把允许浏览器访问的头放入白名单。
Access-Control-Max-Age,指定了preflight请求的结果能够被缓存多久
Access-Control-Allow-Credentials,指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。
Access-Control-Allow-Methods,用于预检请求的响应。
Access-Control-Allow-Headers,用于预检请求的响应。
4. iframe系
- iframe+window.name。window对象有个name属性,该属性有个牛逼的地方就是:在同一个窗口中,我不管你页面怎么变,我window.name的值是一直存在的,在同一个窗口任意读写,并且支持非常长的name值(2MB)。
- iframe+location.hash。
location.hash
改变了,但是不会引起页面刷新。 - iframe+window.postMessage。HTML5新增了个window.postMessage方法,允许跨窗口通信,且不必同源。
5. document.domain
跨子域。
6. 服务器代理
这也是最后的办法。
参考:
浏览器的同源策略 八种方式实现跨域请求 跨域的那些事儿 iframe跨域 HTTP访问控制(CORS) 对于浏览器的同源策略你是怎样理解的呢? 浏览器同源政策及其规避方法 POST 还是 PUT
console.log(document.getElementById('test'));
document.getElementById('test').innerHTML = '<h1>hello</h1>'
localStorage.setItem('test', 'testStr');
console.log(document.cookie);
document.cookie = '';
console.log(document.cookie);
fetch('http://0.0.0.0:4321/test.js')
.then(res => {
console.log(res);
})
模拟了非同源,但是只有ajax 不能发送,没有出现: Cookie、LocalStorage 和 IndexDB 非同源不可读取 脚本不可以获取和操作另一个源的DOM,比如不同源的iframe
是否是以讹传讹