最近做的一个项目中,前后端分离,前端使用ajax方式来请求接口获取的json数据,但这不可避免会出现跨域的问题,这个问题以前也遇到过,一般解决方案是后端修改一下请求头部的Allow-Origin。
但这两天研究HTTP协议时,就花了点时间研究了一下这个跨域问题的来龙去脉。对跨域不了解的同学可以先看看阮一峰老师的这篇博客:http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html,讲的很细很全面。
对同源策略有所了解的同学,要解决跨域问题,最重要的是要了解哪些情况会造成同源限制?阮一峰老师的博客中总结了下面三点,我也参考了《白帽子讲Web安全》这本书提到的同源策略,做一些补充。
浏览器会对以下几种情况产生跨域限制:
(1) 读取Cookie、LocalStorage 和 IndexDB(毕竟两个完全不同的客户端要是能互相读取cookie,就太不安全了)。
(2) 获取DOM (如<iframe>,需要注意的是<script>、<img>、<src>、<href>这些标签可以跨域访问资源,只是这种方式的访问只能加载资源,不能读写资源。)。
(3) AJAX 请求(主要是ajax依赖于XMLHttpRequest(XHR))
这里我就着重说明一下ajax请求。
为什么浏览器会对ajax请求产生跨域限制呢?我还隐约记得以前刚学ajax时,老师教我们用原生js的方式来实现ajax请求,代码如下:
//ajax发起get请求
function getMethod(){
var xhr= new XMLHttpRequest();//通过XMLHttpRequest创建ajax对象
xhr.open("get","/getdata");//创建监听
//监听ajax状态码
xhr.onreadystatechange= funtion(){
if(xhr.status==200 && xhr.readyState==4){
var result = xhr.responseText;
console.log(result); //获取请求的数据
}
}
xhr.send(); //发送请求
}
//ajax发起post请求
function postMehtod(){
var p1 = "a";
var p2 = "b";
var xhr= new XMLHttpRequest();
xhr.open("post","/login");
//设置请求头类型
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
//监听ajax状态码
xhr.onreadystatechange= function(){
if(xhr.status==200 && xhr.readyState==4){
var result = xhr.responseText;
console.log(result);
}
}
xhr.send("p1="+p1+"&p2="+p2);//带参发送请求
}
不难看出,ajax能实现http请求和响应,主要依赖于 XMLHttpRequest(XHR)
在通过查文档可以找到,XHR是受到了同源策略的限制,参考文档:https://developer.mozilla.org/zh-cn/docs/web/api/xmlhttprequest,文档对XHR跨域没有做明确描述,但有两个属性描述提到过。
XMLHttpRequest.withCredentials
一个布尔值,用来指定跨域 Access-Control 请求是否应当带有授权信息,如 cookie 或授权 header 头。
XMLHttpRequest.mozSystem 只读
一个布尔值,如果为真,则在请求时不会强制执行同源策略。
综上,不仅可以得到浏览器对XMLHttpRequest做出了同源策略限制的结论,而且了解到还可以通过修改头部信息的 Access-Control来规避这种限制。这就是为什么遇到ajax请求的跨域问题,我们通常都是在后端设置头部的Allow-Origin来解决。
那么问题来了,为什么通过ajax发起get请求去调用接口数据会造成跨域限制,而如果直接通过浏览器的地址栏访问接口的地址,却可以直接得到对应的json数据呢?
这个问题我在做开发时也琢磨了一段时间,后来才意识到,直接通过地址栏发起请求,对于服务器而言,这就是一个正常的浏览器请求,并不涉及到另一个域,而且也不存在一些背后的脚本攻击,所以浏览器并不会对这种请求加以限制。就像我们通过地址栏访问https://www.baidu.com一样,只是一个正常用户的请求,我们除了请求百度首页的数据外,并不能对百度做什么,所以浏览器默认这种方式是安全的。
当然了,做内部接口,安全起见,最好还是加上Token验证。