读完这篇跨域,大抵心下清楚其他跨域的不必看罢了。 ---鲁迅
目录
一、什么时候出现跨域
每个web开发者都会遇到跨域。他们或多或少会知道大概类似于下面这样的知识:
如果请求来源和请求目标的不在同一个服务器上会发生跨域。
但跨域的原因仅此于此吗?先看一个现象。
服务器就一个url
@RequestMapping("")
public String index (){
return "ok";
}
然后我们开始测试,1号测试,在postman里输入请求:localhost:8080。结果得到了一个ok,并没有报什么跨域的错误。
那么仅此一个例子就可以判定上面的那个知识是错误的,因为我在postman里直接输入的话,并没有请求来源,但是有请求目标,他们是不一致的。
但我觉得可以换个方案,2号测试,写一个html来访问试图去迎合上面那个知识,注意,这个html就直接放本地盘符啊,不要和服务器代码放一起,否则他们就是在同一个服务器上了,自然不好引起跨域。
<html>
<script src="jquery.js"></script>
<script>
$.post("http://localhost:8080",{},function(data){
console.log(data)
})
</script>
<html>
本地直接打开后,出现了跨域的问题。
竟然请求失败了,请求来源和目标不一致,出现了跨域问题。
为什么用postman可以请求,而使用html就出现跨域问题呢?
很简单。跨域问题出现的其中一个要素就是:跨域是浏览器里才会出现。跨域本身就是浏览器为了解决一些安全问题而设置的警告拦截。
那么浏览器里的来源目标不一致的请求都会产生跨域吗?
我们看下3号测试,在浏览器地址里输入:localhost:8080.
咦?竟然不跨域了?
4号测试,我们写个a标签试下,这个a标签的来源可以是任何地址。
<a href="http://localhost:8080">点我</a>
也不跨域。
跨域问题出现的第二个要素是:只有js的xhr才会发生跨域,html标签并不会,包括不仅限于a标签,form标签等。
js跨域,实际上就意味着ajax,vue等js框架同样会有跨域。
跨域问题还有一个要素,当然,就是上面那个不完整的常见知识,我们说的再精准一些:来源和目标的协议、主机、端口有任意一个不同
只有以上三点同时出现,才会引出跨域问题,再重申一遍:
跨域三要素:
浏览器、js、目标来源不一致。
二、跨域的时候究竟发生了什么?我们怎么解决?
我们在上面2号测试的时候,浏览器报错下面:
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
它意思是说,你的目标是http://localhost:8080/,但是来源是null.这不一致,说不定是偷渡,所以我要检查一番,看看服务器给你开发票了没用,结果发现返回的响应头里没“Access-Control-Allow-Origin”这张发票,所以违法。
Access-Control-Allow-Origin就是允许的来源。看来我要想解决它。必须在服务器设置下。
5号测试,服务器写个filter,加入这行代码
response.setHeader("Access-Control-Allow-Origin", "123");
运行一下,又报错跨域了。跟上次一样
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains the invalid value '123'.
仔细一看,还有点不一样,这段意思是,这张发票是开给123的,而你是“null”,所以也不合法。
那就6号测试:
response.setHeader("Access-Control-Allow-Origin", "null");
运行起来,这次没报错了,
那么解决跨域的一个操作就是:
服务端设置允许来源Access-Control-Allow-Origin,如果希望省事,那么可以允许任何来源
response.setHeader("Access-Control-Allow-Origin", "*");
如果只是这样就好了,但是我们最好加一些难度,比如,7号测试,添加一个token来看看会是怎么样的
<html>
<script src="jquery.js"></script>
<script>
$.ajax({
url:'http://localhost:8080',
type:'post',
headers:{
'token':'123456'
},
data:{},
success:function(data){console.log("sucess");},
});
</script>
<html>
它又报错跨域错误了,
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: Request header field token is not allowed by Access-Control-Allow-Headers in preflight response.
尽管我英语不是太好,但是对比着上次的错误我大概能猜出来,竟然还需要另一张发票Access-Control-Allow-Headers。
是的,如果你自定义了header,那么同时还需要另外一张发票。
我们修改下服务器返回发票,8号测试:
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "token,token1,token2");
看,我们开了header是token/token1/token2的发票,你的自定义header要是在其中,没问题。
那么解决跨域的第二个操作就是:
如果请求有自定义header,服务端设置允许来源Access-Control-Allow-Headers,如果希望省事,那么可以允许任何来源
response.setHeader("Access-Control-Allow-Headers", "*");
走到这里,其实大部分跨域问题都已经解决了。但是我们可以更进一步,这次我们设置下修改下method,由get/post方法改成put,9号测试:
<html>
<script src="jquery.js"></script>
<script>
$.ajax({
url:'http://localhost:8080',
type:'put',
data:{},
success:function(data){console.log("sucess");},
});
</script>
<html>
它又报错了。
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
我闭上眼睛都知道我要做什么了,
如果请求的method比较稀奇古怪,不是get也不是post啊,那也要设置,不过,干脆省事些吧。
response.setHeader("Access-Control-Allow-Methods", "*");
当然还有另一个设置,
如果你需要跨域验证cookie的话。
response.setHeader("Access-Control-Allow-Credentials", "true")
解决方案总结一下吧:
response.setHeader("Access-Control-Allow-Origin", "*");//允许任何请求来源
response.setHeader("Access-Control-Allow-Methods", "*");//允许任何method
response.setHeader("Access-Control-Allow-Headers", "*");//允许任何自定义header
response.setHeader("Access-Control-Allow-Credentials", "true");//允许跨域cookie
三、隐藏的秘密
发生原因和解决方案都已经列出,但跨域还有隐藏的一个秘密,这个就当了解一下吧。
如果我们去抓包2号测试和9号测试,对比就会发现他们的结果完全不同。
2号测试请求可以请求到数据,返回了ok。但是浏览器报错跨域。
9号测试请求,它的真实method竟然不是put,而是option,它无法请求到数据,浏览器报错跨域。
这究竟是为什么呢?
其实跨域的请求,在浏览器看来又分为简单请求和非简单请求。
简单请求浏览器就直接请求,和正常的没什么区别,然后根据返回发票判断是否跨域,如果跨域,就报错,返回来的数据就扔了。
非简单请求浏览器发起一个一模一样但是method是option的测试请求,先去嗅探返回发票,如果发票正确,那就再去请求一次真实数据,如果发票错误,就报错。
浏览器判断简单非简单请求的依据有:
以下简单请求,其他非简单请求:
1.请求方式:GET、POST、HEAD
2.HTTP头部信息不超过一下几种字段:
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
这意味着如果是有自定义header或者contextType=applcaition/json的算是非简单请求
四、jsonp
有网友给的解决方案是ajax请求时,类型改成jsonp,jsonp其实不能算是xhr请求,而是script请求,
localhost:8080/aaa相当于请求localhost:8080/aaa.js,然后再另行处理。
这种方式可以,但也意味着服务端要配合,修改默认返回方式等。也可以。