1、什么是跨域?
浏览器遵循同源政策(scheme(协议)、host(主机)和port(端口)都相同则为同源)。
非同源站点有这样一些限制:
a、不能读取和修改对方的 DOM
b、不读访问对方的 Cookie、IndexDB 和 LocalStorage
c、限制 XMLHttpRequest 请求,AJAX 请求不能发送。
当浏览器向目标 URI 发 Ajax 请求时,只要当前 URL 和目标 URL 不同源,则产生跨域,被称为跨域请求。跨域请求的响应一般会被浏览器所拦截,注意,是被浏览器拦截,响应其实是成功到达客户端了。
2、跨域的解决方案
1)CORS
CORS 跨域资源共享(Cross-origin resource sharing)其实是 W3C 的一个标准,全称是跨域资源共享。它需要浏览器和服务器的共同支持。支持所有类型的HTTP请求。
1)若浏览器发送的是个跨域请求,http请求中就会携带一个名为Origin的头表明自己的“位置”,如Origin: http://localhost:5432
2)服务端接到请求后,就可以根据传过来的Origin头做逻辑,决定是否要将资源共享给这个源喽。而这个决定通过响应头Access-Control-Allow-Origin来承载,它的value值可以是任意值,有如下情况:
a、值为*,通配符,允许所有的Origin共享此资源。
b、值为http://localhost:5432(也就是和Origin相同),共享给此Origin。
c、值为非http://localhost:5432(也就是和Origin不相同),不共享给此Origin。
d、无此头:不共享给此origin;有此头:值有如下可能情况。
3)浏览器接收到Response响应后,会去提取Access-Control-Allow-Origin这个头。然后根据上述规则来决定要接收此响应内容还是拒绝
我们需要清楚两个概念: 简单请求和非简单请求。浏览器根据请求方法和请求头的特定字段,将请求做了一下分类,具体来说规则是这样,凡是满足下面条件的属于简单请求:
a、请求方法为 GET、POST 或者 HEAD
b、请求头的取值范围: Accept、Accept-Language、Content-Language、Content-Type(只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)。
剩下的则是非简单请求。
简单请求会自动在请求头当中,添加一个Origin字段,用来说明请求来自哪个源。服务器拿到请求之后,在回应时对应地添加Access-Control-Allow-Origin字段,如果Origin不在这个字段的范围中,那么浏览器就会将响应拦截。
Access-Control-Allow-Origin字段是服务器用来决定浏览器是否拦截这个响应,这是必需的字段。
Access-Control-Allow-Credentials表示是否允许发送 Cookie,对于跨域请求,浏览器对这个字段默认值设为 false,而如果需要拿到浏览器的 Cookie,需要添加这个响应头并设为true, 并且在前端也需要设置withCredentials属性:
Access-Control-Expose-Headers:可选
CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
非简单请求
非简单请求体现在两个方面: 预检请求和响应字段。
我们以 PUT 方法为例:
当这段代码执行后,非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为预检请求。这个预检请求的请求行和请求体是下面这个格式:
预检请求的方法是OPTIONS,同时会加上Origin源地址和Host目标地址,同时也会加上Access-Control-Request-Method(列出 CORS 请求用到哪个HTTP方法)、Access-Control-Request-Headers(指定 CORS 请求将要加上什么请求头)。
接下来是响应字段,响应字段也分为两部分,一部分是对于预检请求的响应,一部分是对于 CORS 请求的响应。
预检请求的响应。如下面的格式:
其中有这样几个关键的响应头字段:
a、Access-Control-Allow-Origin: 必选,表示可以允许请求的源,可以填具体的源名,也可以填*表示允许任意源请求。
b、Access-Control-Allow-Methods: 表示允许的请求方法列表。
c、Access-Control-Allow-Credentials: 它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。
d、Access-Control-Allow-Headers: 表示允许发送的请求头字段。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
e、Access-Control-Max-Age: 预检请求的有效期,在此期间,不用发出另外一条预检请求。
在预检请求的响应返回后,如果请求不满足响应头的条件,则触发XMLHttpRequest的onerror方法,当然后面真正的CORS请求也不会发出去。
CORS 请求的响应。现在它和简单请求的情况是一样的。浏览器自动加上Origin字段,服务端响应头返回Access-Control-Allow-Origin。
前端设置:
服务端设置:
服务器端对于CORS的支持,主要是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
2)JSONP
虽然XMLHttpRequest对象遵循同源政策,但是script标签不一样,它可以通过 src 填上目标地址从而发送带有callback参数的GET 请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据,实现跨域请求并拿到响应。这也就是 JSONP 的原理。
缺点是只支持get请求,不支持post请求。适合加载不同域名的js、css,img等静态资源。
3)postMessage() 方法
postMessage() 方法用于安全地实现跨源通信。
接收页面监听window的message事件就可以。为了安全起见,我们利用这时候的MessageEvent对象判断了一下消息源,它包括如下几个属性:
data:顾名思义,是传递来的message
source:发送消息的窗口对象
origin:发送消息窗口的源(协议+主机+端口号)
4)Nginx
Nginx 是一种高性能的反向代理服务器,可以用来轻松解决跨域问题。
正向代理帮助客户端访问客户端自己访问不到的服务器,然后将结果返回给客户端。
反向代理拿到客户端的请求,将请求转发给其他的服务器,主要的场景是维持服务器集群的负载均衡,换句话说,反向代理帮其它的服务器拿到请求,然后选择一个合适的服务器,将请求转交给它。
因此,两者的区别就很明显了,正向代理服务器是帮客户端做事情,而反向代理服务器是帮其它的服务器做事情。
比如说现在客户端的域名为client.com,服务器的域名为server.com,客户端向服务器发送 Ajax 请求,当然会跨域了,那这个时候让 Nginx 登场了,通过下面这个配置:
Nginx 相当于起了一个跳板机,这个跳板机的域名也是client.com,让客户端首先访问 client.com/api,这当然没有跨域,然后 Nginx 服务器作为反向代理,将请求转发给server.com,当响应返回时又将响应给到客户端,这就完成整个跨域请求的过程。
4)document.domain + iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
5)postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a、页面和其打开的新窗口的数据传递
b、多窗口之间消息传递
c、页面与嵌套的iframe消息传递
e、上面三个场景的跨域数据传递
用法:postMessage(data,origin)方法接受两个参数:
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。