美文网首页
AJAX跨域的前世今生

AJAX跨域的前世今生

作者: xialedoucaicai | 来源:发表于2018-06-27 15:42 被阅读0次

本文主要参考了如下两篇文章
再也不学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给出的示例:

同源
关于特定操作有哪些,详细的可以参看开头的第一篇文章。
我也简单总结一下:
  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 增加如下配置
<mvc:cors>
    <mvc:mapping 
        path="/**"
        allowed-origins="http://localhost:8080"
        allowed-methods="GET, POST, PATCH, PUT, DELETE, OPTIONS"
        allow-credentials="true"
        />
</mvc:cors>

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

相关文章

  • AJAX跨域的前世今生

    本文主要参考了如下两篇文章再也不学AJAX了!(三)跨域获取资源 ① - 同源策略浏览器同源政策及其规避方法本文的...

  • API-Server构建指南(4)-跨域

    跨域的前世今生系列。核心: 跨域是浏览器的安全策略跨域的具体规则 可参考https://blog.csdn.net...

  • 解决ajax跨域问题

    Jsonp解决ajax跨域问题 CORS解决ajax跨域问题

  • 前端跨域

    什么是ajax跨域 ajax跨域的原理 ajax出现请求跨域错误问题,主要原因就是因为浏览器的“同源策略”,可以参...

  • 跨域

    跨域:ajax 不能跨域img css script 标签 可以跨域例如:《img src="images/...

  • 交互那些事(二)

    说完ajax我想必须说说jsonp了,谈到jsonp就必须先说说跨域,首先ajax是不能跨域的,除非后台允许跨域或...

  • Http浅析【2】——ajax跨域问题

    视频参考:ajax跨域完全讲解 本文精华版:【综合】ajax跨域问题 什么是跨域问题 简单来讲,当前台调用后台,如...

  • ajax跨域请求

    ajax跨域请求(jsonp) 利用JSONP解决AJAX跨域问题的原理与jQuery解决方案JSONP jQue...

  • 珠峰 AJAX --- JSONP跨域

    珠峰 AJAX --- JSONP跨域AJAX(异步 javascript and XMLHTTPReq...

  • 彻底让你明白跨域(服务器端为node)

    跨域只是针对ajax请求的。 如果产生跨域,那么将请求不到数据。 发起ajax请求所依赖的html页面的url地址...

网友评论

      本文标题:AJAX跨域的前世今生

      本文链接:https://www.haomeiwen.com/subject/htkdyftx.html