一、跨域问题产生的原因
根本原因是由于浏览器的“同源政策”。
1.1.同源政策
同源政策由网景公司(Netscape)1995年引入浏览器。目前所有浏览器都实行这个政策。
所谓同源是指“三个相同”:
- 协议相同
- 域名相同
- 端口相同
1.2.限制范围
目前,如果非同源,共有三种行为收到限制。
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM无法获取
- AJAX请求不能发送
二、跨域问题处理
2.1 Cookie共享
Cookie是服务器写入浏览器的信息,只有同源的网页才能共享。但是如果是一级域名相同,二级(及以上)域名不同的网页可以通过设置document.domain来共享cookie。
比如,w1.example.com 和 w2.example.com是不同源的,w1.example.com/index.html (A网页) 和 w2.example.com/index.html (B网页)只要设置相同的document.domain就可以共享Cookie。
设置domain有两种方式:1.前端js脚本中设置 2.服务器接口设置cookie时指定Cookie所属的域名为一级域名。
2.1.1 js设置domain
在A网页设置
document.domain = “example.com”;
document.cookie = “userName = aaa”;
这样在B网页就可以读到这个Cookie
var cookie = document.cookie; // userName = aaa;
2.1.2 服务器设置domain
Set-Cookie: userName = aaa ; domain = .example.com ; path = /
2.2 iframe父子窗口共享DOM
如果两个网页不同源,就无法拿到对方的DOM。典型例子是iframe和window.open方法打开的窗口,它们与父窗口无法通信。
如果两个窗口只是二级域名不同,一级域名相同的话,那么设置document.domain属性,就可以规避同源政策。拿到DOM。对于完全不同源的网站,目前有三种方法可以解决跨域窗口通信问题:
- 片段标识符
- window.name
- 跨文档通信API
2.2.1 片段标识符
片段标识符指的是URL的#后面的部分,如http://example.com/index.html...。如果只是改变片段标识符部分,页面不会刷新。
父窗口可以把信息写入子窗口的片段标识符中,
var src = originURL + ‘#’ + data;
document.getElementById(‘#iframe’).src = src;
子窗口通过监听hashchange事件得到通知
window.onhashchange = checkMessage;
function checkMessage(){
var message = window.location.hash;
//...
}
同样子窗口也可以修改父窗口的片段标识符。
parent.location.href = target + ‘#' + hash;
2.2.2 window.name
浏览器窗口有window.name属性。这个属性最大的特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页就可以读取它。
2.2.3 window.postMessage
postMessage是HTML5引入的一个全新的API:跨文档通信API提供的一个全局方法,允许跨窗口通信,不论是否同源。
父窗口发送message
var popup = window.open(‘http://aaa.com’,'title');
popup.postMessage(‘Hello World’, ‘http://aaa.com');
子窗口监听message事件
window.addeventlistener(‘message’,function( e ){
console.log( e.data );
});
message事件的event对象提供三个属性
- event.source 发送消息的窗口
- event.origin 消息发向的网址
- event.data 消息内容
2.3 LocalStorage共享
用window.postMessage可解决。
2.4 跨域异步请求(AJAX)
同源政策规定,AJAX只能发给同源的网址,否则会报错。除了用服务器代理的方法(nginx反向代理等),有以下方法可以解决。
2.4.1 JSONP
原理是利用script标签的src属性,接受JSON数据。关键在于规定一个callback方法名,在url中以参数形式传给服务器,由服务器将数据以参数形式注入callback中,供js脚本使用。
缺点是只能发送get请求,优点是支持老式浏览器。
2.4.2 CORS
CORS即跨源资源分享,是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP,CORS允许任何类型的请求。
目前除了IE之外的所有浏览器都支持CORS,IE不能低于IE10版本。
浏览器将CORS请求分为两类,简单请求和非简单请求。
2.4.2.1 简单请求
简单请求条件:
1. 请求方法 GET、POST、HEAD;
2. http头信息不超过 Accept 、Accept-Language 、Content-Language、Last-Event-ID;
3. content-type只限于三个值: application/x-www-form-urlencoded、multipart/form-data、text/plain
对于简单请求,浏览器的处理是直接发出CORS请求,也就是在头信息中增加一个字段Origin,该字段说明本次请求是来自哪个源(协议+域名+端口)。服务器根据这个值,判断是否同意此次请求。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
如果Origin指定的域名在许可范围内,服务器返回的相应,会多几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
- Access-Control-Allow-Origin:该字段必须,它的值要么是请求是Origin的值,要么是一个*,表示接受任意域名的请求。
- Access-Control-Allow-Credentials:该字段可选。值是一个布尔值,表示是否允许发送Cookie,默认为true,表示可以发送。
- Access-Control-Expose-Headers:该字段可选。CORS请求是XHR对象的getResponseHeader( ) 方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expire、Last-Modified、Pragma。如果想要拿到其他字段,就要在Access-Control-Expose-Headers中指定。
注意,如果要将Cookie发送到服务器,除了服务器要设置Access-Control-Allow-Credentials字段以外,浏览器在发送AJAX请求是也要保证xhr.withCredentials属性为true,否则浏览器既不会向服务器发送cookie,服务器设置cookie的操作也不会成功。但是默认情况下,该属性是为true的。
另外需要注意的是,如果要操作Cookie,Access-Control-Allow-Origin就不能设置为*,必须明确是哪个网址过来的请求。
对于非简单请求:
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP请求,成为“预检”请求。
浏览器先询问服务器,当前网页所在域名是否在服务器的许可名单中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则报错。
预检请求的方法是OPTIONS,头信息中的关键字是Origin,标识请求来自哪个源。另外还包含两个特殊字段:
- Access-Control-Request-Method:该字段是必须的,用于流出浏览器的CORS请求会用到那些HTTP方法。
- Access-Control-Request-Headers:该字段用来指定CORS会额外发出的头信息字段。
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...
服务器检查过预检请求头之后,确认允许跨域请求,就可以做出回应。
响应头部信息的关键字段是Access-Control-Allow-Origin,为*或者一个完整域名,表示允许的请求来源。
如果服务器否定了“预检”请求,会返回一个正常的HTTP回应,不包含任何CORS相关的字段。这是浏览器会认定,服务器不同意预检请求,因此会触发一个错误,呗XHR对象的onerror函数捕获。并在控制台打印。
成功的话还有几个与CORS相关的响应头字段:
- Access-Control-Allow-Methods:必须。表示服务器支持的跨域请求方法。注意会返回所有支持的方法,以此避免多次预检请求。
- Access-Control-Allow-Headers:如果请求头包含该字段,则服务器响应也是必须的。
- Access-Control-Allow-Credentials:是否支持cookie传输
- Access-Control-Max-Age:可选。用来指定本次预检请求的有效期,单位为秒。在此期间不用发出另一条预检请求。
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
一旦服务器通过了预检请求,以后每次浏览器正常的CORS请求,就和简单请求一样,会有一个Origin字段,服务器回应也会有一个Access-Control-Allow-origin头信息字段。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。