为了账号安全,请及时绑定邮箱和手机立即绑定

ajax跨域完全讲解

晓风轻 全栈工程师
难度中级
时长 1小时40分
学习人数
综合评分9.70
116人评价 查看评价
9.8 内容实用
9.6 简洁易懂
9.7 逻辑清晰
  • 为什么发生Ajax跨域?

    1、浏览器显示 

    2、访问的路径不是本地的

    3、发送的请求是XMLHttprequest的请求

  • 为什么会发生ajax跨域/ 跨域的场景

    总的来说, 产生跨域问题有3个原因:  浏览器的限制, 跨域, xhr(XMLHttpRequest) 请求; 这三个条件同时满足才会产生跨域安全问题

    1. 浏览器的限制

    一句话解释就是: 因为发送请求时跨域了所以浏览器报跨域问题

    跨域不成功和后台服务器没有关系, 不是因为后台不允许前台调用, 真正的原因是浏览器出于安全考虑, 会对发送的不同源跨域请求进行安全校验,校验不通过时就会报跨域错误

    这个时候可能你的请求已经发送成功并且返回了数据, 但控制台报了一条跨域错误, 说明服务器后台式没有任何限制的,  前台已经访问到了后台,  是浏览器报的错; 说白了就是浏览器多管闲事, 而不是后台不允许

    1. 跨域: 发出去的请求不是本域的.  前台发送的请求里面和后台的 协议, 域名, 端口任何一个不一样, 浏览器都会认为是跨域; 比如前台是localhost:8080端口, 请求的是localhost:8081端口 , 虽然域名相同, 但端口不一样, 所以跨域了;

    2. 发送的是 XHR(XMLHttpRequest) 请求 (也是最重要的原因); 只要发送的不是XHR请求, 比如发送的请求Type为jsonp, 就算是跨域了, 浏览器也不会报跨域安全问题


    <!--此处有图片-->


    跨域解决方案

    • 命令行启动参数修改

    • Cors, 在响应头里面添加字段

    • nodejs可以引入第三方包cors

    • nginx反向代理实现跨域(proxy_pass模块)

    • Vue.config.js里面配置devServer的proxy

    • 谷歌的插件allow cors可以解决谷歌浏览器里面的跨域

    • webSocket本身不存在跨域的问题,所以我们可以利用webSocket来进行非同源中间的通信

    <!--此处有图片-->

    1. 从浏览器出发, 让浏览器不去做跨域的限制;

      方法1: 浏览器禁止跨域安全检查

      思路: 通过命令行修改浏览器启动参数, 让浏览器不进行跨域安全校验, 从而允许跨域;

      做法: 命令行启动浏览器后添加参数  - -disable-web-security; 如 chrome --disable-web-security, 这个参数的作用是禁止浏览器进行跨域安全检查;

      <!--此处有图片-->

      缺陷:

       总结: 虽然可以解决跨域, 但是这种方式价值不大, 因为需要用户手动修改, 而且这种改动是客户端的改动, 不太实用, 而在实际项目中, 主要是对服务端进行修改来实现浏览器支持跨域;

      <!--此处有图片-->


      1. 每次都需要通过命令行启动参数来启动浏览器, 过于繁琐

      2. 该方法会导致稳定性和安全性有所下降

      3. 该方法是客户端方法的改动, 在实际使用中, 要每个客户端都禁止浏览器跨域检查是不太现实的, 实用性较低

    2. JSONP (json with padding; jsonp 是json的一种补充使用) 避免发生跨域,  因为只有xhr请求才会可能发生跨域,  而jsonp 的请求类型不是xhr, 而且主要是通过动态创建script脚本, 利用script标签向不同源接口请求资源可以跨域的特点, 在script里面发送跨域请求, 获取其他来源动态生成的json数据,  所以不会产生跨域问题;

      jsonp 与ajax 请求的区别


      思路: JSONP是一个非官方协议, 是一个约定, 它约定如果发送请求的时候在script标签里携带一个 callback 参数, callback 的值是一个页面中定义好的函数名,  后台检测到 callback 就知道这是一个jsonp请求, 这时服务器返回的数据就会由原来的json对象改为js代码;

      函数是浏览器定义的, 处理逻辑浏览器写;  数据是 服务器返回的,  到底是什么数据 服务器决定;


      实现原理: 前台使用 ajax 的 get 请求, 将 dataType 设为 'jsonp' , 服务器创建一个类并继承抽象类  AbstractJsonResponseBodyAdvice, 最后注解 @ControllerAdvice;


      具体实现流程:


      补充: jsonp请求里除了callback参数外, 还一个下划线 _   参数,参数值是一串随机数字, 它的作用主要是防止请求被缓存, 如果需要缓存的话,  可以在ajax请求里面加上 cache: true, 表示结果可以被缓存;

      <!--此处有图片--><!--此处有图片-->



      JSONP优缺点

      JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。

      缺点:


      <!--此处有图片-->


    • 不安全,可能会遭受XSS攻击;

    • 发送的不是xhr请求; xhr的一些事件, 异步功能jsonp都没有;

    • 仅支持get方法, 不能满足实际开发需求; jsonp是通过动态创建script发请求, script 只支持get

    • 服务器需要改动代码来支持跨域, 当接口不是我们自己的代码无法改动时, jsonp就无能为力了;

    • 声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为服务器返回的data。

    • 创建一个<script>标签,把跨域的API接口地址赋值给script的src 并且在这个地址中携带这个show 函数名(通过问号传参:?callback=show)。

    • 服务器接收到请求后,把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是show('你好么')

    • 服务器把数据返回给浏览器,  返回结果为js代码, js代码的内容是函数调用的形式,  函数名为callback 的值, 函数的参数为之前要返回的json对象, 浏览器就可以执行之前声明 的这个回调函数, 对返回的数据进行操作;

    • 相当于向http://localhost:3000/say?wd=Iloveyou&callback=show这个地址请求数据,然后后台返回show('你好么'),最后会运行show()这个函数,打印出'你好么'


      <!--此处有图片-->

    • ajax属于同源策略,  jsonp属于非同源策略;  

    • ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。

    • 普通的ajax请求发出去的类型是 xhr, jsonp 请求发出去的类型是script, ajax请求返回的数据类型是json,  jsonp返回类型是 js脚本;  

    产生跨域后解决

    解决思路:

    方式:

    一. 从被调用方考虑,有三种情况,分别是服务器实现 (重点),nginx配置apache配置

    原理: 在这种解决方案里面, 跨域请求是直接从调用方的浏览器发送到被调用方的http服务器, http服务器再把请求转发到应用服务器; 应用服务器处理完之后, 把响应返回给http服务器, http服务器再把响应转回到调用方的浏览器;

    最终的目标是要在响应头里添加字段, 所以这里有两个地方可以增加响应头; 一个是中间的http服务器上增加, 也就是Nginx/ Apache; 一个是应用服务器, 也就是Tomcat


    <!--此处有图片-->


    • 被调用方; 基于让被调用方支持跨域的思路 , 支持基于http协议关于跨域方面的要求;  也就是从A域名调用b域名的时候, 在b域名返回的响应头里添加指定字段, 告诉浏览器, 我允许A域名调用, 浏览器通过校验, 就不会报跨域错误;

    • 调用方; 如果被调用方不是公司的, 无法进行修改, 就可以从调用方进行修改, 这种做法是隐藏跨域, 通过一个代理, 从浏览器里发出的都是A域名的请求, 通过这个代理把指定的url转到B域名里, 在浏览器看来它们就是同一个域名, 就没有跨域问题

    1. 被调用方解决: CORS

      CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现

      浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端;

      服务端设置 Access-Control-Allow-Origin 就可以开启 CORS;

    浏览器在发送跨域请求时, 会先判断请求是简单请求和非简单请求。 简单请求是先执行请求再验证,非简单请求是先验证再请求。 非简单请求实际上会发送两个请求, 第一次会先发送一个预检请求, 这个预检请求是浏览器额外发送的 options 预检请求,  检查通过之后, 才会真正把跨域请求发送出去;

    预检命令的缓存: 非简单请求中添加的响应头, 用于缓存预检命令, 提高请求效率; 告诉浏览器在一个小时之内可以缓存预检命令的结果, 发送非简单请求时不用再发送预检命令  res.setHeader('Access-Control-Max-Age', 3600)

    <!--此处有图片-->

    简单请求(比较常见):

    • 方法为 get,head,post 中的一种;

    • 请求header里面没有自定义头;

    • Content-Type的值为限于以下3者之一:  text/plain, multipart/form-data, application/x-www-form-urlencoded


    非简单请求(比较常见):

    • put, delect方法的ajax请求;

    • 发送json格式的ajax请求;

    • 带自定义头的ajax请求。

    简单请求处理方案:在响应头中添加      

    **Access-Control-Allow-Origin=“允许跨域的url”**,即跨省域时,请求头Origin的值,所以一般是获取Origin的值。
    
    **Access-Control-Allow-Method=“*”**,允许的方法。

    非简单请求处理方案:响应头中添加

    "*" 表示允许所有内容或类型; 
    Access-Control-Allow-Origin=“允许跨域的url”,即跨域时,可以获取请求头Origin的值。
    
    Access-Control-Allow-Method=“*”,// 允许所有方法
    'Access-Control-Allow-Origin', * // 允许所有域     
    
    res.setHeader('Access-Control-Request-Headers', 'Content-Type')
    Access-Control-Request-Headers=“Content-Type" // 自定义的header的key 
    
    如: 
     // 设置哪个域/ 源可以跨域调用
        res.setHeader('Access-Control-Allow-Origin', origin)
        // 允许携带哪个头 可以跨域
        res.setHeader('Access-Control-Allow-Headers', 'name')
        // 允许哪个方法 可以跨域
        res.setHeader('Access-Control-Allow-Methods', 'PUT')
        // 允许携带cookie
        res.setHeader('Access-Control-Allow-Credentials', true)
        
    // 预检命令的缓存时间; 用于缓存预检命令, 提高请求效率; 告诉浏览器在一个小时之内可以缓存这段信息, 发送非简单请求时不用再发送预检命令
        res.setHeader('Access-Control-Max-Age', 3600)
    
        // 允许返回的头
        res.setHeader('Access-Control-Expose-Headers', 'name')

    带cookies的跨域解决:在响应头添加

    1) 带cookie的时候, origin 必须是全匹配, 不能使用*, 否则会报错, 并且要添加 Access-Control-Allow-Credentials="true" 的字段;

    res.setHeader('Access-Control-Allow-Origin', "http://locahost: 8081")
    
    Access-Control-Allow-Credentials="true"  //  允许发送请求时携带cookies

    http里的会话, 也就是session, 是依赖于cookie的, sessionid存放在cookie中, 在实际工作中, 带cookie的跨域很重要;

    2) 用 filter实现解决: 浏览器发现请求是跨域的时候, 会在请求头里添加origin字段, 这个字段有当前域的信息, 后台可以在filter里得到这个字段, 然后再把它写回到 Access-Control-Allow-Origin 里面, 这样就可以支持所有域的调用;

    <!--此处有图片-->

    <!--此处有图片-->

    带自定义头的跨域

    在jQuery里面, 设置自定义头有两种方法:

    1) 在headers里添加;

    2) XMLHttpRequest 对象提供了一个设置请求头的方法: setRequestHeader, 可以在 beforeSend 回调函数里用xhr.setRequestHeader("x-header2":, "aaa") 方法设置;

    <!--此处有图片-->

    <!--此处有图片-->

    请求头里的自定义属性:

    <!--此处有图片-->


    获取请求头的自定义属性 , 然后再把它写到响应头的 Access-Control-Allow-Origin 里面 , 就可以支持所有自定义头

    <!--此处有图片-->


    nodejs可以引入第三方包cors

    npm install --save cors
    //CORS middleware
    var allowCrossDomain = function(req, res, next) {
        res.header('Access-Control-Allow-Origin', 'http://example.com');
        res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
        res.header('Access-Control-Allow-Headers', 'Content-Type');
    
        next();
    }
    
    //...
    app.configure(function() {
        app.use(express.bodyParser());
        app.use(express.cookieParser());
        app.use(express.session({ secret: 'cool beans' }));
        app.use(express.methodOverride());
        app.use(allowCrossDomain);
        app.use(app.router);
        app.use(express.static(__dirname + '/public'));
    });


    1. 被调用方解决: ngnix反向代理配置 实现跨域(proxy_pass模块):

    虚拟主机: 多个域名指向同一个服务器, 服务器根据不同的域名把请求转到不同的应用服务器, 看上去好像有多个主机, 实际上只有一个主机;

    server{
      # 监听9099端口
      listen 9099;
      # 域名是localhost
      server_name localhost;
      #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
      location ^~ /api {
          proxy_pass http://localhost:9871;
      }
    }

    <!--此处有图片-->

    <!--此处有图片-->

    1. 被调用方- Apache解决

    和 Nginx作用是一样的,  做法就是把Nginx上的配置再实现一次;

    <!--此处有图片-->


    Vue中 webpack.config.js里面配置devServer的proxy

    利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。后台可以不做任何处理。

    module.exports = {
      entry: {},
      module: {},
      ...
      devServer: {
          historyApiFallback: true,
          proxy: [{
              context: '/login',
              target: 'http://www.daxihong.com:8080',  // 代理跨域目标接口
              changeOrigin: true,
              secure: false,  // 当代理某些https服务报错时用
              cookieDomainRewrite: 'www.daxihong.com'  // 可以为false,表示不修改
          }],
          noInfo: true
      }
    }

    谷歌的插件allow cors可以解决谷歌浏览器里面的跨域

    webSocket本身不存在跨域的问题,  所以我们可以利用webSocket来进行非同源中间的通信


  • <!--此处有图片-->

    <!--此处有图片-->

    经过创建的 过滤器的过滤操作后,filter 解决 了 浏览器对跨域请求的拦截,当有多个地址需要进行相同操作时,可以使用"*"代替具体的地址,从而实现大范围的跨域请求

  • 解决跨域问题①:

    针对该浏览器添加参数,禁止浏览器做校验,避免报跨域问题


    <!--此处有图片-->
  • 修改浏览器的设置后,没有再出现跨域问题

    由此可以认为,跨域问题主要还是在浏览器发生的校验失败等问题导致的,与后台没有任何关系

    <!--此处有图片-->

  • Ajax 跨域请求 限制原因:

    3、XHR(XMLHttpRequest)请求:

    <!--此处有图片-->

    测试: 新增加一个 <img>标签,重启启动后,由图可知,尽管新添加了一个 <img>标签,但是,浏览器所报的错误还是只有一个,而这一个便是之前的<a>标签的请求;而这是由于 <img> 和 <a> 两个请求发送时的 Type 不同,<img> 是json类型的,而<a>则是xhr类型的

  • Ajax 跨域请求 限制原因:

    2、跨域:

    <!--此处有图片-->

    由图中可知,server  和 client 的 port 端口是不同的,因此,浏览器检测到后认为其发生了跨域

  • Ajax 跨域请求 限制原因:

    1、浏览器限制:

    <!--此处有图片-->

    由图中可以知道,当发送请求时,尽管控制台报了跨域请求错误,但是IDE的服务器后台,依旧正常收到了请求,并打印相关 响应字段

  • Ajax 跨域请求 限制原因:

    1、浏览器限制:

    <!--此处有图片-->

    由截图可知,服务器的请求响应状态为200,这代表这客户端的请求被服务器正常的响应了,服务器后台是没有任何限制,而且正确处理 了;从侧面也就证明了是 浏览器进行了限制,从而报了错误给我们

  • <!--此处有图片-->

    引入jasmine 测试框架后,console 会生成一个界面用于提示用户测试结果及相关错误

  • <!--此处有图片-->

    <!--此处有图片-->

    <!--此处有图片-->

    引入前端测试框架 jasmine,提高开发效率,从官网进入到 Releases目录下下载 jasmine .zip 压缩文件,解压后将lib目录下的包复制到工程项目的static目录下

  • 创建 ajaxClient 工程并编写 前台页面代码

    02:59
    看视频

举报

0/150
提交
取消
课程须知
需要具备基本的前后台开发技术
老师告诉你能学到什么?
AJAX跨域产生的原因和解决思路 系统的基本部署架构和跨域的关系 http服务器nginx和apache的重要作用和跨域的2种解决思路 jsonp的工作机制和优缺点 前台测试框架Jasmine的使用

微信扫码,参与3人拼团

意见反馈 帮助中心 APP下载
官方微信
友情提示:

您好,此课程属于迁移课程,您已购买该课程,无需重复购买,感谢您对慕课网的支持!