搞懂:前端跨域问题JS解决跨域问题VUE代理解决跨域问题原理

什么是跨域

跨域一个域下的文档或脚本试图去请求另一个域下的资源

广义的跨域包含一下内容

1.资源跳转(链接跳转,重定向跳转,表单提交)
2.资源请求(内部的引用,脚本script,图片img,frame)
3.script内部发起的请求(ajax,dom请求,和js跨域调用

跨域问题出现

只有浏览器端出现,直接用终端请求,是不会出现跨域拦截,是属于浏览器端的安全策略,浏览器将不同源的请求进行了拦截,限制了跨域资源访问

什么是同源

同源策略:same origin policy,如果两个资源(页面)`协议`,`域名`,`端口`都相同,那么就是同源。

即使:两个不同域名指向同一个ip,也算非同源

非同源(跨域)有哪些限制

* cookie,localstorage,indexDB无法读取
* Dom和JS对象无法互通
* Ajax请求不能发送

常见的跨域demo

URL                                      说明                    是否允许通信
//www.domain.com/a.js
//www.domain.com/b.js         同一域名,不同文件或路径           允许
//www.domain.com/lab/c.js

//www.domain.com:8000/a.js
//www.domain.com/b.js         同一域名,不同端口                不允许
 
//www.domain.com/a.js
//www.domain.com/b.js        同一域名,不同协议                不允许
 
//www.domain.com/a.js
//192.168.4.12/b.js           域名和域名对应相同ip              不允许
 
//www.domain.com/a.js
//x.domain.com/b.js           主域相同,子域不同                不允许
//domain.com/c.js
 
//www.domain1.com/a.js
//www.domain2.com/b.js        不同域名                         不允许

前端解决跨域的方法

  • JSONP

    • 原理:由于在一个页面内部js如果要访问另一个非同源域的数据是被浏览器限制的,但是浏览器在解析和加载script标签的时候,是允许一个页面加载多个域的js标签,而js标签其实又是类似一种get请求的方式,只是返回的数据是一个JSON格式的脚本对象。
    • 用法:将get请求封装到script标签中,利用script标签做get请求,最后解析script标签数据
    • 代码:
      var  script = document.createElement('script')
      script.style = 'text/javascript'
    
      //将要请求的get地址传到script的src属性,并且添加回调函数
      script.setAttribute('scr','//www.domain2.com:8080/login?user=admin&callback=handleCallback')
      //将script标签放入文档
      document.head.appendChild(script)
      //执行回调函数
      function handleCallback(res){
          //处理JSON格式数据
          alert(JSON.stringify(res))
      }
      
    
    • 由于请求script只能是get方法,所以JSONP这种方式只能解决get请求,post或者其他http方法无法解决
  • 跨域资源共享

    • 原理:
      • cors:cross-origin -resouce sharing跨域资源共享,允许浏览器向跨源服务器发出http请求,需要浏览器和服务器同时支持
      • 浏览器在发出跨域请求时:1。简单请求,直接在头部信息中带上origin信息,标识属于哪个源,服务端配置access-control-allow-origin接受哪些域名跨域访问,可以是*允许所有
      • 如果是非简单请求会在正式请求之前,发送预检请求
    • 简单来说(简单请求):
      • 是一个双端配合,允许打卡跨域资源限制的一种手段
      • 浏览器端:请求头携带origin信息
      • 服务端:配置access-control-allow-origin允许哪些源跨域
    • 代码:
      • 原生:xmlHttpRequest不需要配置请求头,简单请求会自动带上origin属性
        var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
        
        
            xhr.open('post', '//www.domain2.com:8080/login', true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.send('user=admin');
        
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    alert(xhr.responseText);
                }
            };
        
        
      • AJAX:有一个属性crossDomain,配置为true之后会让请求头携带orgin跨域额外信息,但是不会自动包含cookie
        $.ajax({
            ...
        xhrFields: {
            withCredentials: true    // 前端设置是否带cookie
        },
        crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
            ...
        });
        
      • VUE中的axios,默认也会把origin放到请求头,不需要额外配置
    • 服务端配置
        /*
         * 导入包:import javax.servlet.http.HttpServletResponse;
         * 接口参数中定义:HttpServletResponse response
         */
      
        // 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
        response.setHeader("Access-Control-Allow-Origin", "//www.domain1.com"); 
      
        // 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
        response.setHeader("Access-Control-Allow-Credentials", "true"); 
      
        // 提示OPTIONS预检时,后端需要设置的两个常用自定义头
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
      
      
  • 反向代理(NodeJs中间件代理跨域)(Vue代理跨域)(nginx转发代理)

    • 场景:当服务器端无法修改cors,并且client全部换成jsonp方式比较繁琐时,只能通过中间代理服务器,将代理服务器设置为支持CORS或者代理服务器和请求页面同源,使页面可以直接请求代理服务器,在通过代理服务器进行接口代理,代理请求目标接口地址,返回数据

    • 原理:跨域限制仅是浏览器端的安全策略,并不是http协议的固有限制,所以中间服务器在参数和cookie有效的情况下是可以正常的请求目标服务器的,Vue(node+webpack+webpack-dev-server)中配置proxy就是启动一个同源的服务进行接口代理

    • 注意:

      • 1.非vue ,webpack,dev-serve服务,单独启的nginx或者node express服务做反向代理时,一般是跟页面非同源,非同源要访问代理服务器也存在跨域问题,需要配置cors允许跨域访问,配置access-control-allow-header
      • 在webpack-dev-server服务,默认应该是基本与测试环境页面同源,不需要配置请求头允许
    • 代码:

      • webpack-dev-server代理
              module.exports = {
              entry: {},
              module: {},
              ...
              devServer: {
                  historyApiFallback: true,
                  proxy: [{
                      context: '/login',
                      target: '//www.domain2.com:8080',  // 代理跨域目标接口
                      changeOrigin: true,
                      secure: false,  // 当代理某些https服务报错时用
                      cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
                  }],
                  noInfo: true
              }
          }
      
      • node express反向代理
      var express = require('express');
      var proxy = require('http-proxy-middleware');
      var app = express();
      
      app.use('/', proxy({
          // 代理跨域目标接口
          target: '//www.domain2.com:8080',
          changeOrigin: true,
      
          // 修改响应头信息,实现跨域并允许带cookie
          onProxyRes: function(proxyRes, req, res) {
              res.header('Access-Control-Allow-Origin', '//www.domain1.com');
              res.header('Access-Control-Allow-Credentials', 'true');
          },
      
          // 修改响应信息中的cookie域名
          cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
      }));
      
      app.listen(3000);
      console.log('Proxy server is listen at port 3000...');
      
      • nginx转发代理
          #proxy服务器
          server {
              listen       81;
              server_name  www.domain1.com;
      
              location / {
                  proxy_pass   //www.domain2.com:8080;  #反向代理
                  proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
                  index  index.html index.htm;
      
                  # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
                  add_header Access-Control-Allow-Origin //www.domain1.com;  #当前端只跨域不带cookie时,可为*
                  add_header Access-Control-Allow-Credentials true;
              }
          }
      
  • 其他js HACK方法

    • 主域相同子域不同场景:docment.domain+iframe

    • 三个页面跨域互传 : location.hash + iframe

    • window.name + iframe

    • H5新增的postMessage(跨窗口消息互通方法)

    • 关于iframe:

      • 其实相当于在当前页面下新开了个子页面。遵循一切页面与页面之间资源访问原则,例如
        • 1.当主页面和iframe页面不同源时,无法互相访问DOM
        • 2.当主域一样,二级域名不同时,document.domain的设置,可以规避同源策略,可以互相访问DOM
    • location.hash:

      • url 末尾#后面跟随的时页面片段标识符,用来表示浏览器渲染哪部分页面信息,但是改变这个值页面不会重新刷新, 并且设置之后,所有页面可以通过document对象访问该窗口的location 信息进行获取,可以实现巧妙跨域
      • 代码:
          //父窗口可以把信息,写入子窗口的片段标识符。
      
          var src = originURL + ‘#’ + data;
          document.getElementById(‘myIFrame’).src = src;
      
          //子窗口通过监听hashchange事件得到通知。
      
          window.onhashchange = checkMessage;
      
          function checkMessage() {
          var message = window.location.hash;
          // …
          }
      
    • 同理,window.name也是一种hack方法实现跨域的方式

      • 浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。

        父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。

        window.name = data;

        接着,子窗口跳回一个与主窗口同域的网址。

        location = ‘//parent.url.com/xxx.html’;

        然后,主窗口就可以读取子窗口的window.name了。

        var data = document.getElementById(‘myFrame’).contentWindow.name;

        这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

    • postMessage

      • window.name和location.hash都属于取巧的方式,利用了浏览器页面的非数据属性或者是窗口的固有属性达到跨页面的目的,但是在H5新增了postMessage API,实现跨tab跨窗口的数据通信,允许窗口通信,并且不论是否同源

总结:

常用的,还是代理,还有CORS,或者是JSONP这三种方式,禁止跨域本来就是浏览器的一种安全策略,虽然有一定限制开发者的手脚,但是在一些特殊的网站或者安全性要求比较高的网站,网络安全还是不可忽视的

非常感谢:下面的文章给了我很多的帮助,感谢各位前行者的辛苦付出,可以点击查阅更多信息

前端常见解决跨域问题方案

9种常见的跨域解决方案

前端跨域问题