1995年,同源政策由 Netscape 公司引入浏览器。目前,全部浏览器都实行这个政策。html
最初,它的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。所谓“同源”指的是“三个相同”:前端
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。java
设想这样一种状况:A 网站是一家银行,用户登陆之后,A 网站在用户的机器上设置了一个 Cookie,包含了一些隐私信息(好比存款总额)。用户离开 A 网站之后,又去访问 B 网站,若是没有同源限制,B 网站能够读取 A 网站的 Cookie,那么隐私信息就会泄漏。更可怕的是,Cookie 每每用来保存用户的登陆状态,若是用户没有退出登陆,其余网站就能够冒充用户,随心所欲。由于浏览器同时还规定,提交表单不受同源政策的限制。webpack
因而可知,同源政策是必需的,不然 Cookie 能够共享,互联网就毫无安全可言了。web
随着互联网的发展,同源政策愈来愈严格。目前,若是非同源,共有三种行为受到限制。json
浏览器的同源策略会致使跨域,也就是说,若是协议、域名或者端口有一个不一样,都被看成是不一样的域,就不能使用 Ajax 向不一样源的服务器发送 HTTP 请求。首先咱们要明确一个问题,请求跨域了,请求到底发出去没有?答案是确定发出去了,可是浏览器拦截了响应。segmentfault
Ajax 的同源策略主要是为了防止 CSRF
(跨站请求伪造) 攻击,若是没有 AJAX 同源策略,至关危险,咱们发起的每一次 HTTP 请求都会带上请求地址对应的 cookie,那么能够作以下攻击:后端
DOM同源策略也同样,若是 iframe
之间能够跨域访问,能够这样攻击:api
因此说有了跨域跨域限制以后,咱们才能更安全的上网了。跨域
CORS 是一个 W3C 标准,全称是跨域资源共享(Cross-origin resource sharing),它容许浏览器向跨源服务器,发出XMLHttpRequest请求。
整个 CORS 通讯过程,都是浏览器自动完成,不须要用户参与。对于开发者来讲,CORS 通讯与普通的 AJAX 通讯没有差异,代码彻底同样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。所以,实现 CORS 通讯的关键是服务器。只要服务器实现了 CORS 接口,就能够跨域通讯。
CORS经常使用的配置项有如下几个:
Access-Control-Allow-Origin(必含) – 容许的域名,只能填 *
(通配符)或者单域名。
Access-Control-Allow-Methods(必含) – 这容许跨域请求的 http 方法(常见有 POST、GET、OPTIONS
)。
Access-Control-Allow-Headers(当预请求中包含 Access-Control-Request-Headers
时必须包含) – 这是对预请求当中 Access-Control-Request-Headers
的回复,和上面同样是以逗号分隔的列表,能够返回全部支持的头部。
Access-Control-Allow-Credentials(可选) – 表示是否容许发送Cookie,只有一个可选值:true(必为小写)。若是不包含cookies,请略去该项,而不是填写false。这一项与 XmlHttpRequest 对象当中的 withCredentials
属性应保持一致,即 withCredentials 为true时该项也为true;withCredentials 为false时,省略该项不写。反之则致使请求失败。
Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。在有效时间内,浏览器无须为同一请求再次发起预检请求。
浏览器先根据同源策略对前端页面和后台交互地址作匹配,若同源,则直接发送数据请求;若不一样源,则发送跨域请求。
服务器收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何容许跨域,则文件头里不包含 Access-Control-Allow-origin
字段,若配置过域名,则返回 Access-Control-Allow-origin + 对应配置规则里的域名的方式
。
浏览器根据接受到的 响应头里的 Access-Control-Allow-origin
字段作匹配,若无该字段,说明不容许跨域,从而抛出一个错误;如有该字段,则对字段内容和当前域名作比对,若是同源,则说明能够跨域,浏览器接受该响应;若不一样源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。
上面说到的两种类型的报错,控制台输出是不同的:
Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest的onerror 回调函数捕获。注意,这种错误没法经过状态码识别,由于 HTTP 回应的状态码有多是200。<!--控制台返回结果-->
XMLHttpRequest cannot load http://localhost/city.json.
The 'Access-Control-Allow-Origin' header has a value 'http://segmentfault.com' that is not equal to the supplied origin.
Origin 'http://www.zhihu.com' is therefore notallowed access.
复制代码
<!--控制台返回结果-->
XMLHttpRequest cannot load http://localhost/city.json.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://www.zhihu.com' is therefore not allowed access.
复制代码
实际上浏览器将CORS请求分红两类:简单请求(simple request
)和非简单请求(not-so-simple request
)。
简单请求是指知足如下条件的(通常只考虑前面两个条件便可):
GET、POST、HEAD
其中一种请求方法。application/x-www-form-urlencoded、multipart/form-data、text/plain
对于简单请求,浏览器直接发起 CORS 请求,具体来讲就是服务器端会根据请求头信息中的 origin
字段(包括了协议 + 域名 + 端口),来决定是否赞成此次请求。
若是 origin
指定的源在许可范围内,服务器返回的响应,会多出几个头信息字段:
Access-Control-Allow-Origin: http://xxx.xxx.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
复制代码
非简单请求时指那些对服务器有特殊要求的请求,好比请求方法是 put
或 delete
,或者 content-type
的类型是 application/json
。其实简单请求以外的都是非简单请求了。
非简单请求的 CORS 请求,会在正式通讯以前,使用 OPTIONS
方法发起一个预检(preflight)请求到服务器,浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可使用哪些 HTTP 动词和头信息字段。只有获得确定答复,浏览器才会发出正式的 XMLHttpRequest 请求,不然就报错。
下面是一个预检请求的头部:
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
复制代码
一旦服务器经过了"预检"请求,之后每次浏览器正常的CORS请求,就都跟简单请求同样了。
关于为何要有简单请求和非简单请求,可参考知乎上的一个回答 为何跨域的post请求区分为简单请求和非简单请求和content-type相关?
JSONP 的原理就是利用 <script>
标签的 src 属性没有跨域的限制,经过指向一个须要访问的地址,由服务端返回一个预先定义好的 Javascript 函数的调用,而且将服务器数据以该函数参数的形式传递过来,此方法须要先后端配合完成。
//定义获取数据的回调方法
function getData(data) {
console.log(data);
}
// 建立一个script标签,而且告诉后端回调函数名叫 getData
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'demo.js?callback=getData';
body.appendChild(script);
//script 加载完毕以后从页面中删除,不然每次点击生成许多script标签
script.onload = function () {
document.body.removeChild(script);
}
复制代码
JSONP 使用简单且兼容性不错,可是只限于 get
请求。
浏览器有跨域限制,可是服务器不存在跨域问题,因此能够由服务器请求所要域的资源再返回给客户端。
通常咱们在本地环境开发时,就是使用 webpack-dev-server
在本地开启一个服务进行代理访问的。
该方式只能用于二级域名相同的状况下,好比 a.test.com
和 b.test.com
适用于该方式。
只须要给两个页面都添加 document.domain = 'test.com'
,经过在 a.test.com
建立一个 iframe
,去控制 iframe
的 window
,从而进行交互。
window.postMessage 是一个 HTML5 的 api,容许两个窗口之间进行跨域发送消息。
这种方式一般用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另外一个页面判断来源并接收消息
// 发送消息端
var receiver = document.getElementById('receiver').contentWindow;
var btn = document.getElementById('send');
btn.addEventListener('click', function (e) {
e.preventDefault();
var val = document.getElementById('text').value;
receiver.postMessage("Hello "+val+"!", "http://res.42du.cn");
});
// 接收消息端
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event){
if (event.origin !== "http://www.42du.cn")
return;
}
复制代码
详情可参考 MDN | window.postMessage
还有一些方法,好比window.name和location.hash。都比较适用于 iframe 的跨域,不过 iframe 用的比较少了,因此这些方法也就有点过期了。