一、跨域问题产生的原因

根本原因是由于浏览器的“同源政策”。

1.1.同源政策

同源政策由网景公司(Netscape)1995年引入浏览器。目前所有浏览器都实行这个政策。
所谓同源是指“三个相同”:

  • 协议相同
  • 域名相同
  • 端口相同
1.2.限制范围

目前,如果非同源,共有三种行为收到限制。

  1. Cookie、LocalStorage 和 IndexDB 无法读取。
  2. DOM无法获取
  3. 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
  1. Access-Control-Allow-Origin:该字段必须,它的值要么是请求是Origin的值,要么是一个*,表示接受任意域名的请求。
  2. Access-Control-Allow-Credentials:该字段可选。值是一个布尔值,表示是否允许发送Cookie,默认为true,表示可以发送。
  3. 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,标识请求来自哪个源。另外还包含两个特殊字段:

  1. Access-Control-Request-Method:该字段是必须的,用于流出浏览器的CORS请求会用到那些HTTP方法。
  2. 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相关的响应头字段:

  1. Access-Control-Allow-Methods:必须。表示服务器支持的跨域请求方法。注意会返回所有支持的方法,以此避免多次预检请求。
  2. Access-Control-Allow-Headers:如果请求头包含该字段,则服务器响应也是必须的。
  3. Access-Control-Allow-Credentials:是否支持cookie传输
  4. 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头信息字段。


未末辰降
32 声望0 粉丝

在这做一些学习记录