AJAX跨域的前世今生

本文主要参考了如下两篇文章
再也不学AJAX了!(三)跨域获取资源 ① - 同源策略
浏览器同源政策及其规避方法
本文的重点内容是AJAX跨域的来龙去脉,如果想看直接加两行代码就搞定的方法,请直接移步文章末尾。本文主要内容包含:为啥不让跨域?是谁阻止了我的操作?解决的思路是怎样的?同时会涉及到简单请求和复杂请求的跨域解决是不一样的,跨域怎么携带cookie。
1.为什么不让AJAX跨域 在前后端分离的项目中,由于前端和后端不在同一个域,将会导致在发送ajax请求的时候,浏览器给我们报这样一个错:

XMLHttpRequest cannot load http://127.0.0.1:8080/CrossOriginServer/cross. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.
出现这个问题的原因就是浏览器的同源策略:限制不同源之间执行某些特定操作。
其中同源要求协议、端口、域名必须完全相同,来看wikipedia给出的示例:

AJAX跨域的前世今生
文章图片
同源
关于 特定操作有哪些,详细的可以参看开头的第一篇文章。
我也简单总结一下: 【AJAX跨域的前世今生】
  1. 浏览器只会把淘宝给浏览器颁发的cookie发给淘宝,而不会发给百度
  2. 我们无法获取和操作iframe中的dom元素
  3. 限制跨域AJAX请求,这也是我们要说的重点:
1.浏览器的"锅" 首先要明确的是同源策略是浏览器做的限制,也就是说我们之所以前后端分离发送AJAX失败了,全是浏览器的"锅",不关服务器的事。因为Server端只负责提供对外服务,Http请求是无状态的,它不管你是用浏览器还是其他客户端工具发起的请求,只要URL是存在的,就会响应,这点可以从浏览器的F12 Network中看出来,服务端其实已经返回了响应,状态码为200。
2.一切为了安全 那么浏览器为什么要对跨域AJAX请求做限制呢?答案就是为了保障用户数据的安全。以Tomcat为例,session机制绝大多数情况是通过cookie实现的,用户登录A网站后,服务端会为浏览器写入一个cookie,key为JSESSIONID,value为sessionid,用户下次访问该网站的时候,浏览器会自动将该cookie传给A网站,A网站就能识别到这是刚刚登录的那个用户,同时会在返回头中原封不动的把浏览器传上来的cookie再返回去,服务端和浏览器就是这样相互识别的。
假设跨域发送AJAX没有任何限制,那我可以部署一个自己的网站B,对于用户的任何请求,我都额外发一个AJAX到淘宝,如果某个用户的浏览器正好刚访问过淘宝,那么在我发AJAX请求到淘宝时,浏览器会自动将淘宝颁发给用户的cookie带给淘宝,淘宝会在响应中把该cookie原封不动的返给浏览器,那我在AJAX的回调中就能拿到该返回头信息,自然也就能拿到cookie,拿到cookie能干什么事,可以搜索CSRF了解。
2.解决的思路 虽然浏览器的同源策略出发点是好的,为了保障用户信息安全,但是前后端分离这种合法场景也不幸躺枪,我们既要遵循同源策略,又要实现跨域,该怎么办?
首先基于上面的分析,我们可以看出,浏览器之所以阻止了AJAX的响应,是因为浏览器担心A服务器在不知情的情况下把用户信息携带给客户端。所以问题的核心在于打消浏览器的顾虑,这就需要A来配合,告诉浏览器,你别担心,都是自己人,这样做是合法安全的,你不要再拦截响应了。于是你就会发现网络搜索的各种文章让你在服务端加入这句话:
resp.addHeader("Access-Control-Allow-Origin", "*");
就是用来告诉浏览器,第二个参数指定的域发起的请求是合法的,我们商量好的,你放心,不要拦截响应。通过这句话,我们AJAX发送的Get Post请求都能正常响应了。
3.Put Delete请求 但如果你用了Rest风格的API,你会发现在发Put Delete请求时,还是会报错,Put方法根本没进去,F12发现浏览器发送了一个OPTIONS请求,没发我们的Put请求。
这就是因为本次跨域是一个复杂请求,需要先发OPTIONS问问服务器,你支持哪些请求,如果服务器支持我要发的Put请求,才会真正发送。那什么是简单请求和复杂请求呢?
简单请求:
  • 请求方法只属于HEAD,GET,POST请求的其中一种
  • HTTP的头信息只限于以下字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
  • Content-Type只能为:application/x-www-form-urlencoded,multipart/form-data,text/plain其中一种
    不满足如上条件的就是复杂请求。
以上面的Put请求为例,以Servlet为例,我们需要重写doOptions方法,然后指明服务器允许哪些域,允许什么请求,允许哪些头等等。注意所有的这些指定都是针对跨域有效的,在同域中调用会完全忽略这些头的存在。
@Override protected void doOptions(HttpServletRequest arg0, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("Access-Control-Allow-Origin", "*"); resp.addHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS"); resp.addHeader("Access-Control-Allow-Headers", "Any-Name-Here"); }

然后在doPut()方法中,也要指明允许的域,然后就可以写正常的业务逻辑了。
@Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("Access-Control-Allow-Origin", "*"); resp.getWriter().write("这里就可以写你的业务逻辑了"); }

4.携带cookie信息 跨域AJAX默认是不会携带cookie信息的,如果想带cookie,就需要服务端和浏览器协作了。
浏览器端需要指明携带cookie:
var xhr = new XMLHttpRequest() xhr.withCredentials = true

后端需要指定明确的域,指明允许携带验证信息
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //这里写*,好像也能接收跨域的cookie resp.addHeader("Access-Control-Allow-Origin", "*"); //"http://localhost:8080"); //这句话好像也可以不要 resp.addHeader("Access-Control-Allow-Credential", "true"); if(req.getSession().getAttribute("user") != null){ resp.getWriter().write("合法用户才能看到的信息"); } }

在参考文章中说后端也需要做对应处理,指明具体的域,不能用*,要加一个Access-Control-Allow-Credential头,但我试了好像只要在Ajax指明withCredentials,就可以了,大家有兴趣可以试一下。
5.总结 当然我们用的可能是$.ajax()和springMVC,为了解决跨域,我们可以这样做:
前端
//发登录请求 $.ajax({ type: "post", url: "http://127.0.0.1:8080/CrossOriginServer/login", data: {param:"跨域登录"}, xhrFields: { withCredentials: true }, success: function (data, status) { console.log(data); } }); //其他请求 put delete等等 $.ajax({ type: "put", url: "http://127.0.0.1:8080/CrossOriginServer/cross", data: {param:"跨域携带cookie测试"}, xhrFields: { withCredentials: true }, success: function (data, status) { console.log(data); } });

后端
springmvc-servlet.xml 增加如下配置

springmvc的官方文档对跨域的方案有详细的说明,如果遇到问题还是最好根据自己用的版本先去官方文档找找,相比网上搜的资料,既清楚又权威。

    推荐阅读