同源策略
相信大多数前端Coder都听说过同源策略,此处简单的回忆一下。
所谓同源,是指三个相同,即
- 协议相同
- 域名相同
- 端口相同
对于两个网址,如果能同时满足这三个相同,则我们称之为同源。有一点值得注意的是,即使是协议相同,端口相同,如果一个是域名访问,一个是IP访问,那么他们依然是非同源的。
同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
如果非同源,那么将会有以下几种情况的限制。
-
Cookie、LocalStorage和IdnexDB无法读取
-
无法操作非同源的DOM
-
Ajax请求的数据不能获得
跨域解决方案
由于同源策略,一般情况下,我们对非同源的资源请求都会受到限制,但是依然还是有解决方案的。
JSONP
JSONP作为客户端与服务端通信的一种常用方式,其特点是简单,对浏览器的兼容性也比较好。
其基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源策略限制;服务器收到请求后,将数据作为一个指定名字的回调函数的参数传回来。
function loadJSON(url){
let script = document.createElement("script");
script.src = url;
document.body.append(script);
}
window.onload = function(){
loadJSON("http://www.localhost.com/res/userinfo?callback=getUserInfo")
}
function getUserInfo(date){
console.log(date);
}
当添加<script>标签后,浏览器就会请求该URL,并自动运行该脚本,该脚本的内容是对已知函数的调用(就像下面这样),从而我们就可以取到我们的数据。
// JSONP请求获取到的脚本内容,我们需要的数据作为参数传入回调函数中
getUserInfo({name:"xiaohe",age:18})
CORS跨域资源共享
JSONP是一个不错的跨域解决方案,但是其仍然存在的一定的缺点,JSONP只能发送GET请求。
CORS跨域资源共享是一种标准的跨域解决方案,解决了Ajax请求只能同源使用的限制,并适用于所有的请求方式。
一般的,Ajax请求分为两种,一种是简单请求,一种是复杂请求。如果一个请求是简单请求,则其必须满足以下两个条件:
- 请求方式是
HEAD,GET,POST的其中一种。 - http请求头信息不超过以下几个字段:
- Accept
- Accept-Language
- Content-Language
- Content-Type:只限于三个值
application/x-www-form-urlencoded、multipart/form-data、text/plain
若不能同时满足以上两个条件,则为复杂请求。
我们知道,当我们发送ajax跨域请求的时候,浏览器一般会先发送一个预检请求来判断是否支持跨域。预检请求是一个OPTIONS请求,没有请求体,在该请求中,请求头会包含几个比较重要的字段:
- Origin: 该字段表示发送跨域请求的源。
- Access-Control-Request-Headers: 该字段表示跨域请求所需要发送的请求头字段。
- Access-Control-Request-Method: 该字段表示发送跨域请求的请求方式。
如果服务器同意此次跨域请求,那么就会设置对应的响应头字段:
- Access-Control-Allow-Origin: 可以设置为
*,表示所有的源都可以跨域请求该资源;也可以设置为请求头的Origin字段值,表示接受该源的跨域请求。 - Access-Control-Allow-Methods: 该字段值必须包含请求头的
Access-Control-Request-Method字段值,否则无法跨域。 - Access-Control-Allow-Headers: 该字段值必须包含请求头的
Access-Control-Reques-Headers字段值,否则无法跨域。
同时,这个真正的请求的响应头,也必须设置Access-Control-Allow-Origin字段,否则跨域依然不成功。
预检OPTIONS请求,响应头中有Access-Control-Allow-Origin字段
真正的请求,响应头无Access-Control-Allow-Origin字段,跨域不成功
一点题外话,其实同源策略并不是限制了非同源的请求,而是限制获取数据。
从上面的图我们可以看到,服务器已经返回了数据,但是由于浏览器的限制,并不能获取到数据。
其实,当ajax发送跨域请求的时候,并不一定会先发送预检请求:如果是简单请求,则会直接发送请求;只有复杂请求才会先发送预检请求。
对于复杂请求,如果在预检请求的响应头设置了Access-Control-Max-Age字段,那么也不必每一次跨域请求前都发送一次预检请求。其值是一个数,单位为s。
该字段表示 preflight request (预检请求)的返回结果(即
Access-Control-Allow-Methods和Access-Control-Allow-Headers提供的信息) 可以被缓存多久。
如果在测试的时候发现你的Access-Control-Max-Age并没有生效,那么你可能勾选了禁用缓存,取消勾选即可。
跨域请求可以携带Cookie吗?当然也是可以的。但是我不准备现在就说,因为我想去睡觉了。。。😴😴😴
挖个坑,以后回来补。。。🤷♂️🤷♂️🤷♂️
总结
来自前端菜鸟的总结,欢迎评论、指正、交流。
- 同源主要指三个相同:协议相同,域名相同,端口相同;
- 非同源将会受到行为上的限制:
- Cookie,LocalStorage和IndexDB的读取限制
- Dom的操作限制
- Ajax跨域请求限制
- 跨域请求的解决方案有JSONP和CORS(跨域资源共享)
- Ajax跨域请求分为简单请求和复杂请求,简单请求并不会先发送预检请求;复杂请求可以通过设置预检请求的响应头
Access-Control-Max-Age字段来缓存预检信息,不必每一次请求都发送预检请求。 - 允许跨域请求的接口,其响应头必须得设置
Access-Control-Allow-Origin。