一、跨域
为什么跨域?同源指的是:两个 url 的协议、域名和端口都相同,否则就是跨域。
// 协议是http,域名是www.example.com,端口是80(默认端口可以省略)
http://www.example.com/dir/page.html
http://www.example.com/dir2/other.html // 同源
http://example.com/dir/other.html // 不同源(域名不同)
http://v2.www.example.com/dir/other.html // 不同源(域名不同)
-
服务端向服务端请求会产生跨域吗?
跨域是对前端的浏览器做出的限制,服务器之间不存在跨域。
-
跨域请求会到达服务端吗?
不论请求是否跨域,只要发出去了就一定会到达服务端并被执行。
-
跨域请求是谁在处理?
服务端会处理跨域请求并返回响应,只不过响应被浏览器拦截了,浏览器隐藏了请求的返回值。
1. 同源策略的限制
跨域请求会带来一些安全问题,不法分子可能会利用它来发动 csrf 攻击。因此,浏览器采用同源策略对跨域请求作出限制,同源策略的限制主要体现为 3 个方面:
- 无法访问非同源网页的cookie,localStorage 和 IndexedDB。
- 无法读取和修改非同源网页的DOM。
- ajax请求只能发给同源网站。
二、规避同源策略
1. cookie
如果两个网页的一级域名相同,二级域名不同,那么可以通过设置相同的ducument.domain来共享cookie,这种方法只适用于 cookie 和 iframe 窗口。
iframe 窗口:可以在当前网页嵌入其他网页。只有在同源情况下,父子窗口才能通信,可以拿到 DOM。
2. 跨文档通信 postMessage
对于完全不同源的网站,可以通过h5新增的API:postMessage() 来解决跨域窗口的通信问题,适用于localStorage、IndexedDB、iframe窗口。
我们假设父窗口 http://fawin.com,子窗口http://chwin.com
var openWindow = window.open('http://chwin.com', 'title'); //父窗口打开一个子窗口
openWindow.postMessage('Welcome!', 'http://chwin.com'); //父窗口向子窗口发消息
3. 对于ajax请求不能发送的情况
3.1 JSONP
jsonp 利用了<script>没有跨域限制的漏洞来获得数据,简单易用、老式浏览器全部支持、没有兼容性问题。它的做法是:
- 在网页中添加一个
<script>标签,由它向跨域网页发出请求。请求的 url 中有一个 callback 参数?callback=bar,用来告诉服务器,客户端的回调函数名称是bar。
<script src="http://api.foo.com?callback=bar"></script>
-
服务器收到请求后,会将相关的 json 数据放在回调函数名里拼成一个字符串,并返回
bar({json数据})。 -
客户端接收字符串后(script 标签请求的脚本内容会作为代码解析。因为已经定义了
bar函数),就能在函数体内拿到返回的json数据。
3.2 WebSocket
WebSocket 是一种 h5 的通信协议,使用 ws://(非加密)和wss://(加密)作为协议前缀,该协议不实行同源政策。
浏览器发出的 ws 请求的请求头中有一个Origin字段,表示该请求来自哪个域。服务器根据该字段判断是否许可本次通信,如果该域名在白名单中,服务器就会允许。
3.3 CORS (跨域资源共享):自动
CORS 请求是浏览器自动发起的请求,是解决跨域 ajax 请求的根本办法。CORS 请求分为简单请求和复杂请求,简单请求需要同时满足两个条件:
- 请求方法是
get/head/post; - 限制请求的头信息不超出以下字段:
Accept // 通知服务器可以发回的数据类型
Accept-Language // 预期返回的人类语言
Content-Language // 希望采用的语言
Content-Type:只限于三个值`application/x-www-form-urlencoded`、
`multipart/form-data`、`text/plain`。
3.3.1 简单请求流程
浏览器会直接发出 CORS 请求,请求头中增加Origin字段说明该请求来自哪个源 (协议 + 域名 + 端口)。
如果Origin不在服务端的许可范围内,那么服务端返回的响应中不包含Access-Control-Allow-Origin字段。反之就会多出几个头信息字段:
Access-Control-Allow-Origin: a.com // 必须!白名单,表示哪些 url 可以访问该资源
Access-Control-Allow-Credentials: true // 可选!是否允许跨域请求携带cookie
Access-Control-Expose-Headers: FooBar // 可选!
Content-Type: text/html; charset=utf-8
CORS 请求默认不包含 cookie,如果跨域需要携带 cookie,那么分为3步:
- 服务端在响应中需要设置
Access-Control-Allow-Credentials: true。 - 服务端的
Access-Control-Allow-Origin不能使用通配符,必须设置成和请求网页一致的域名。
Access-Control-Allow-Origin: a.com // 表示信任 a.com,允许a.com 跨域请求
Access-Control-Allow-Credentials: true
- 客户端必须在 ajax 请求中打开
withCredentials。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true; // 可以让跨域请求携带cookie
3.3.2 复杂请求流程
复杂请求会先进行一次预检请求,请求方法 OPTIONS,头信息会有两个特殊字段:
Access-Control-Request-Method:说明浏览器的CORS请求会用到哪些请求方法。Access-Control-Request-Headers:说明浏览器CORS请求会额外发送的头部字段。
服务端接收到预检请求后会检查这些字段(包括Origin),检查通过后就会返回有Access-Control-Allow-Origin字段的响应。
预检请求通过后,之后的 CORS 请求就会和简单请求一样。
补充:复杂请求 = 预检请求 + CORS 简单请求,相当于给服务器一个提前拒绝的机会。
3.3.3 jsonp 和 cors 区别
- 请求方式:get请求;所有类型。
- jsonp 支持老式浏览器,不存在兼容性问题。