跨域是什么
跨域,是指浏览器不能执行其他网站的脚本。
为什么会产生跨域
因为浏览器的同源策略(Same Origin Policy),对 JavaScript 实施了安全限制。非同一域名、协议、端口的请求,是不被浏览器允许的(浏览器会将该请求返回的响应内容拦截,并给出跨域警告)。
只要非同源的请求都会受限制么
跨域的限制行为是仅存在于浏览器的。这也就是为什么会出现通过 API 请求工具(e.g. postman)调用接口的时候没有问题,但通过浏览起发起请求时就会出现跨域警告。
跨域请求,浏览器会做什么
请求发出
1. 简单请求
请求方法:GET、HEAD、POST
请求头部字段:Accept、Accept-Language、Content-Language、Content-Type(只能是这三个值之一:application/x-www-form-urlencoded、multipart/form-data、text/plain)、Last-Event-ID、不包括自定义头部字段。(好长一堆 T.T,不用硬背,大概知道是这个意思就行了 8⃣️)
判定为简单请求的话,请求会被直接发出。
2. 非简单请求
请求方式:PUT、DELETE、PATCH 等
包含自定义头部字段等……
(我理解就是不满足简单请求的话,就是非简单请求)
在请求发起时,浏览器为了确认服务端是否支持客户端发起非简单请求,会先发出一次预检请求(preflight request),请求方法为 OPTIONS,确认服务端允许该请求后,浏览器才会发出真实的请求。
响应返回
在浏览器接收到响应后,会校验以下响应头中的字段,确认服务端是否允许本次跨域请求:
- Access-Control-Allow-Origin(服务端设置的允许共享资源的源): 是否包含该请求源或者设置为所有源。
- Access-Control-Allow-Methods(服务端设置的允许请求资源的方法): 是否包含本次请求方法。
- Access-Control-Allow-Headers(服务端设置的允许携带的请求头部字段): 该请求头字段是否超出了设置范围则。
- Access-Allow-Max-Age(本次预检请求的有效时长): 如果设置了且未超过有效时长,则不用重复发送预检请求。
满足服务器设置时,简单跨域请求返回响应数据,非简单跨域请求发送后续的真实请求(后续响应的处理和上述相同)。不满足服务器设置时,简单跨域请求返回的响应数据会直接被浏览器拦截,抛出跨域错误。非简单跨域请求发送的预检请求确认服务端不允许该请求,则会忽略后续请求,不发送真实请求。(划重点啦!!)
浏览器允许嵌入跨域资源的请求
<script src="...">嵌入跨域脚本<img>标签嵌入图片<video>、<audio>标签嵌入媒体资源<iframe>标签嵌入跨域资源<link rel="stylesheet" href="..." >标签嵌入 CSS- 字体跨域
如何解决跨域限制
1. JSONP
浏览器允许嵌入跨域资源的请求:
JSOP 根据 script 标签可以嵌入跨域脚本这一特性,在 script 标签里填入跨域资源 url,比较关键的一点是 url 末尾会带一个 callback(回调函数),用于接收返回的跨域资源。
具体一点就是客户端将 callback 写在 url 中传给服务端,普通响应返回的都是 JSON 字符串,但如果是 JSONP 的话,服务端返回响应时会返回一串可执行的 Javascript 字符串。通常是返回 执行约定好的 callback 的代码字符串,并且将响应数据作为参数传入,e.g. res.send('callback(' + data + ')') ),这样客户端接到请求返回的响应时,执行响应信息中返回的 JS 字符串(应该是用的 eval),就能得到跨域的响应数据。
JSONP 只是前后端约定好的一种 JSON 使用方式,且仅支持 GET 请求。
2. CORS(推荐)
服务端设置 Access-Control-Allow-Origin,将需要发送跨域请求的请求源设置到该字段中,便可支持跨域请求。
3. 服务器代理
服务器代理主要原理就是因为服务器不受同源限制,而让服务器做代理转发。
正向代理:正向代理就是客户端通过访问一个与它同源的服务器,而让这个服务器做代理,转发请求,拿到响应后返回给客户端。客户端是不知道自己真实访问了哪台服务器的
反向代理:反向代理的关键就是地址映射,i.e. 就是我们发送请求到同源服务器上,然后服务器根据请求地址映射出另一个请求地址,再由它请求这个地址得到响应后返回给客户端。最终被请求的服务器是不知道真实请求的是哪台客户端的
4. 基于 iframe 跨域
(这里仅介绍 window.name + iframe 处理跨域的原理,还有其他方式没有去了解就不做介绍了,应该也是大同小异的:-D)
首先什么是 window.name ? 其实就是字面意思,用来设置窗口的名字,但它有一个特点就是当窗口内容变化了,window.name 还是会保持不变。
利用这一特性加上 iframe 可以嵌入跨域资源,我们可以如下实现跨域:
在源 A页面,手动创建一个 iframe 标签,并嵌入源 B页面,这时我们虽然可以嵌入显示源 B页面内容,但受同源策略限制,我们是拿不到源 B页面内的资源的。
借助 window.name 的特性(load 过后不会改变),将我们需要得到的数据,设置在源 B页面的 window.name 中,接下来只需要将原来设置的非同源页面源 B,重新设置成与源 A同源的代理页面源 C,就可以名正言顺的拿到刚才设置在 window.name 里的跨域资源了。
到此本文就结束啦,感谢阅读/点赞,欢迎讨论~
👀 打个广告更多浏览器、网络相关知识可以参考这篇文章