Js本身的跨域

Js跨域的跨域,这是个面试 工作都会遇到的坑。
那既然这玩意儿这么烦,那我就索性理清一下。

首先提一下同源策略

同源策略(same-origin policy)。简单来讲同源策略就是浏览器为了保证用户信息的安全,防止恶意的网站窃取数据,禁止不同域之间的JS进行交互。对于浏览器而言只要域名、协议、端口其中一个不同就会引发同源策略,从而限制他们之间如下的交互行为:
1.Cookie、LocalStorage和IndexDB无法读取;
2.DOM无法获得;
3.AJAX请求不能发送。

第一,如果是协议和端口造成的跨域问题“前台”是无能为力的,

第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。

“URL的首部”指window.location.protocol+window.location.host,也可以理解为“Domains, protocols and ports must match”。

解决方法

1.通过修改document.domain来跨子域 (只有在主域相同的时候才能使用该方法)
两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie或者处理iframe。比如A网页是http://www.baidu.com/a.html,B网页是http://www.baidu.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。

    document.domain = 'a.com';
    var ifr = document.createElement('iframe');
    ifr.src = 'http://www.baidu.a.com/b.html';
    ifr.display = none;
    document.body.appendChild(ifr);
    ifr.onload = function(){
        var doc = ifr.contentDocument || ifr.contentWindow.document;
        //在这里操作doc,也就是b.html
        ifr.onload = null;
    };

2.跨域资源共享(CORS)
CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

只需要在后台中加上响应头来允许域请求!在被请求的Response header中加入以下设置,就可以实现跨域访问了

    //指定允许其他域名访问
    'Access-Control-Allow-Origin:*'//或指定域
    //响应类型
    'Access-Control-Allow-Methods:GET,POST'
    //响应头设置
    'Access-Control-Allow-Headers:x-requested-with,content-type'

3.通过jsonp跨域
JSONP是JSON with Padding(填充式json)的简写,是应用JSON的一种新方法,只不过是被包含在函数调用中的JSON,例如:
callback({"name","trigkit4"});

JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。

JSONP的原理:通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。(即用JavaScript动态加载一个script文件,同时定义一个callback函数给script执行而已。)

    function handleResponse(response){
     console.log('The responsed data is: '+response.data);
    }
    var script = document.createElement('script');
    script.src = 'http://www.baidu.com/json/?callback=handleResponse';
    document.body.insertBefore(script, document.body.firstChild);
    /*handleResonse({"data": "zhe"})*/
    //原理如下:
    //当我们通过script标签请求时
    //后台就会根据相应的参数(json,handleResponse)
    //来生成相应的json数据(handleResponse({"data": "zhe"}))
    //最后这个返回的json数据(代码)就会被放在当前js文件中被执行
    //至此跨域通信完成

JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。

JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

4.使用window.name进行跨域
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。

  1. 创建a.com/cs1.html

  2. 创建a.com/proxy.html,并加入如下代码

    <head>
    <script>
    function proxy(url, func){
        var isFirst = true,
        ifr = document.createElement('iframe'),
        loadFunc = function(){
           if(isFirst){
           ifr.contentWindow.location = 'http://a.com/cs1.html';
           isFirst = false;
        }else{
           func(ifr.contentWindow.name);
           ifr.contentWindow.close();
           document.body.removeChild(ifr);
           ifr.src = '';
           ifr = null;
        }
      };
     ifr.src = url;
     ifr.style.display = 'none';
     if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc);
     else ifr.onload = loadFunc;
    
     document.body.appendChild(iframe);
     }
    </script>
    </head>
    <body>
    <script>
    proxy('http://www.baidu.com/', function(data){
    console.log(data);
    });
    </script>
    </body>

    3)在b.com/cs1.html中包含:

    <script>
    window.name = '要传送的内容';
    </script>

    5.使用HTML5的window.postMessage方法跨域
    HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。 
    这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。 

  3. a.com/index.html中的代码:

     <iframe id="ifr" src="b.com/index.html"></iframe>
     <script type="text/javascript">
     window.onload = function() {
          var ifr = document.getElementById('ifr');
          var targetOrigin = 'http://b.com'; // 若写成'http://b.com/c/proxy.html'效果一样
          // 若写成'http://c.com'就不会执行postMessage了
          ifr.contentWindow.postMessage('I was there!', targetOrigin);
     };
     </script>
  4. b.com/index.html中的代码:

     <script type="text/javascript">
     window.addEventListener('message', function(event){
     // 通过origin属性判断消息来源地址
          if (event.origin == 'http://a.com') {
          alert(event.data); // 弹出"I was there!"
          alert(event.source); // 对a.com、index.html中window对象的引用
          // 但由于同源策略,这里event.source不可以访问window对象
        } 
     }, false);
     </script>

    6.通过WebSocket进行跨域
    web sockets是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用)

web sockets原理:在js创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。

只有在支持web socket协议的服务器上才能正常工作。

    var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss
    socket.send('hello WebSockt');
    socket.onmessage = function(event){
    var data = event.data;

7.图像ping(单向)
什么是图像ping:  图像ping是与服务器进行简单、单向的跨域通信的一种方式,请求的数据是通过查询字符串的形式发送的,而相应可以是任意内容,但通常是像素图或204相应(No Content)。 图像ping有两个主要缺点:首先就是只能发送get请求,其次就是无法访问服务器的响应文本。

使用方法:

    var img = new Image();
    img.onload = img.onerror = function(){
        alert("done!");
    };
    img.src = "https://raw.githubusercontent.com/zhangmengxue/Todo-List/master/me.jpg";
    document.body.insertBefore(img,document.body.firstChild);

类似的可以跨域内嵌资源的还有:

(1)<script src=""></script>标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。上面jsonp也用到了呢。
(2) <link src="">标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。
(3)<video> 和 <audio>嵌入多媒体资源。
(4)<object>, <embed> 和 <applet>的插件。
(5)@font-face引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
(6) <frame> 和 iframe载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

8.使用片段识别符来进行跨域
片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#flag的#flag。如果只是改变片段标识符,页面不会重新刷新。
父窗口可以把信息,写入子窗口的片段标识符。在父窗口写入:

    document.getElementById('frame').onload = function(){
        var src = "http://127.0.0.1/JSONP/b.html" + '#' + "data";
        this.src = src;
    }

子窗口通过监听hashchange事件得到通知:

    window.onload = function(){
        console.log("b.html加载完成")
        window.onhashchange = function(){
            var message = window.location.hash;
            console.log(message)//#data
        };  
    }

同样的,子窗口也可以改变父窗口的片段标识符:

    parent.location.href= target + "#" + hash;

webpack的跨域

需要使用一个webpack的插件:webpack-dev-server
如下:

    module.exports = {
      devServer: {
      host: "0.0.0.0",
      port: 8080,
      proxy: {
          "/api": {
            target: "http://localhost:8081/",
            ws: true,
            changeOrigin: true,
            pathRewrite: {
              "^/api": ""
            }
          },
          "/download": {
            target: "http://localhost:8081/",
            ws: true,
            changeOrigin: true,
            pathRewrite: {
              "^/download": ""
            }
          }
        }
      }
    };

参数说明

2.1 ‘/api’
捕获API的标志,如果API中有这个字符串,那么就开始匹配代理,
比如API请求/api/users, 会被代理到请求 http://www.baidu.com/api/users

2.2 target
代理的API地址,就是需要跨域的API地址。
地址可以是域名,如:http://www.baidu.com
也可以是IP地址:http://127.0.0.1:3000
如果是域名需要额外添加一个参数changeOrigin: true,否则会代理失败。

2.3 pathRewrite
路径重写,也就是说会修改最终请求的API路径。
比如访问的API路径:/api/users,
设置pathRewrite: {’^/api’ : ‘’},后,
最终代理访问的路径:http://www.baidu.com/users
这个参数的目的是给代理命名后,在访问时把命名删除掉。

2.4 changeOrigin
这个参数可以让target参数是域名。

2.5 secure
secure: false,不检查安全问题。
设置后,可以接受运行在 HTTPS 上,可以使用无效证书的后端服务器。

具体可以看文章尾部参考资料内的【http-proxy-middleware官方文档】

gulp的跨域

1.npm init初始化
2.全局安装gulp npm i gulp -g
3.本地下载gulp包 npm i gulp --save-dev
4.下载gulp-webserver包 npm i gulp-webserver --save-dev
5.新建gulpfile.js文件

    var gulp = require('gulp');
    var webserver = require('gulp-webserver');

    gulp.task('webserver', function () {
      gulp.src('./')
        .pipe(webserver({
          host: 'localhost',
          port: 8888,
          livereload: true, // 实时重新加载
          open: './index.html', // 启动时默认浏览器打开的文件
          directoryListing: {
            enable: true,
            path: './'
          },
          proxies: [{
            source: '/api',
            target: 'http://www.your.com' // 代理的域名
          }]
        }))
    });
    // gulp4.0
    gulp.task('default', gulp.series(['webserver'], function () {
      console.log('gulp webserver start successfully at localhost:8888');
    }));
    // 因为gulp升级到了4.0,所以使用gulp.series来启动,如果使用的是gulp3.*的话请将上面的代码替换为下面的
    // gulp 3.*
    gulp.task('default', ['webserver'], function () {
      console.log('gulp webserver start successfully at localhost:8888');
    });

在项目根目录打开命令窗口,使用命令gulp启动
使用:
如果接口是https://www.yoururl.com/login/index
则发起请求时使用/api/login/index

具体可以看文章尾部参考资料内的【http-proxy-middleware官方文档】

参考资料

js中的跨域问题
js前端解决跨域问题的8种方案(最新最全)
http-proxy-middleware官方文档

Last modification:January 21st, 2020 at 11:42 am
如果觉得我的文章对你有用,请随意赞赏