1. 跨域是什么?
跨域,是指浏览器不能执行其他网站的脚本。
2. 为什么会产生跨域?
因为浏览器的同源策略(Same Origin Policy),对 JavaScript 实施了安全限制。非同一域名、协议、端口的请求,是不被浏览器允许的(浏览器会将该请求返回的响应内容拦截,并给出跨域警告)。
3. 只要非同源的请求都会受限制么?
跨域的限制行为是仅存在于浏览器的。这也就是为什么会出现通过 API 请求工具调用接口的时候没有问题,但通过浏览起发起请求时就会出现跨域警告。
4. 跨域请求,浏览器会做什么?
- 请求发出
- 简单请求请求方法: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⃣️)表现: 判定为简单请求的话,请求会被直接发出。
- 非简单请求 请求方式:PUT、DELETE、PATCH等 包含自定义头部字段等…… (我理解就是不满足简单请求的话,就是非简单请求)表现: 在请求发起时,浏览器为了确认服务端是否支持客户端发起非简单请求,会先发出一次预检请求(preflight request),请求方法为 OPTIONS,确认服务端允许该请求后,浏览器才会发出真实的请求。
- 响应返回在浏览器接收到响应后,会校验以下响应头中的字段,确认服务端是否允许本次跨域请求:
- Access-Control-Allow-Origin(服务端设置的允许共享资源的源): 是否包含该请求源或者设置为所有源。
- Access-Control-Allow-Methods(服务端设置的允许请求资源的方法): 是否包含本次请求方法。
- Access-Control-Allow-Headers(服务端设置的允许携带的请求头部字段): 该请求头字段是否超出了设置范围则。
- Access-Allow-Max-Age(本次预检请求的有效时长): 如果设置了且未超过有效时长,则不用重复发送预检请求。
表现:
- 满足服务器设置时,简单跨域请求返回响应数据,非简单跨域请求发送后续的真实请求(后续响应的处理和上述相同)。
- 不满足服务器设置时,简单跨域请求返回的响应数据会直接被浏览器拦截,抛出跨域错误。非简单跨域请求发送的预检请求确认服务端不允许该请求,则会忽略后续请求,不发送真实请求。
5. 如何解决跨域限制
- JSONP浏览器允许嵌入跨域资源的请求:
<script src="...">
嵌入跨域脚本<img>
标签嵌入图片<video>
、<audio>
标签嵌入媒体资源<iframe>
标签嵌入跨域资源<link rel="stylesheet" href="..." >
标签嵌入CSS- 字体跨域
JSOP 就是根据 script 标签可以嵌入跨域脚本这一特性,在 script 标签里填入跨域资源 url,比较关键的一点是 url 末尾会带一个callback(回调函数),用于接收返回的跨域资源。具体一点就是客户端 callback 传给服务端,普通响应返回的都是 JSON 字符串,但如果是 JSONP 的话,服务端返回响应时会返回一串可执行的javascript 字符串。通常是返回 执行约定好的 callback 的代码字符串,并且将响应数据作为参数传入,e.x.
res.send('callback(' data ')')
),这样客户端拿到响应时执行返回的 js 字符串(应该是用的 eval),就能得到跨域的响应数据。(JSONP 只是前后端约定好的一种 JSON 使用方式,且仅支持 GET 请求。) - CORS(推荐)服务端设置 Access-Control-Allow-Origin,将需要发送跨域请求的请求源设置到该字段中,便可支持跨域请求。
- 服务器代理服务器代理主要原理就是因为服务器不受同源限制,而让服务器做代理转发。
- 正向代理 正向代理就是客户端通过访问一个与它同源的服务器,而让这个服务器做代理,转发请求,拿到响应后返回给客户端。 客户端是不知道自己真实访问了哪台服务器的
- 反向代理 反向代理的关键就是地址映射,i.e. 就是我们发送请求到同源服务器上,然后服务器根据请求地址映射出另一个请求地址,再由它请求这个地址得到响应后返回给客户端。 最终被请求的服务器是不知道真实请求的是哪台客户端的
- 基于 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 里的跨域资源了。