zoukankan      html  css  js  c++  java
  • js中几种实用的跨域方法原理详解

    什么是同源策略?
    同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

    同源策略限制以下几种行为:

    1、Cookie、LocalStorage 和 IndexDB 无法读取
    2、 DOM 和 Js对象无法获得
    3、 AJAX 请求不能发送

    这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据。只要协议、域名、端口有任何一个不同,都被当作是不同的域。

    下表给出了相对http://store.company.com/dir/page.html同源检测的结果:

    QQ截图20130613230631

    要解决跨域的问题,我们可以使用以下几种方法:

    一、通过jsonp跨域

    在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。

    比如,有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是http://example.com/data.php,那么a.html中的代码就可以这样:

    QQ截图20130613230631

    我们看到获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,但是你用其他的也一样。当然如果获取数据的jsonp地址页面不是你自己能控制的,就得按照提供数据的那一方的规定格式来操作了。

    因为是当做一个js文件来引入的,所以http://example.com/data.php返回的必须是一个能执行的js文件,所以这个页面的php代码可能是这样的:

    QQ截图20130613230631

    最终那个页面输出的结果是:

    QQ截图20130613230631

    所以通过http://example.com/data.php?callback=dosomething得到的js文件,就是我们之前定义的dosomething函数,并且它的参数就是我们需要的json数据,这样我们就跨域获得了我们需要的数据。

    这样jsonp的原理就很清楚了,通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。

    知道jsonp跨域的原理后我们就可以用js动态生成script标签来进行跨域操作了,而不用特意的手动的书写那些script标签。如果你的页面使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。

    QQ截图20130613230631

    原理是一样的,只不过我们不需要手动的插入script标签以及定义回掉函数。jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

    2、通过修改document.domain来跨子域

    浏览器都有一个同源策略,其限制之一就是第一种方法中我们说的不能通过ajax的方法去请求不同源中的文档。 它的第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。有一点需要说明,不同的框架之间(父子或同辈),是能够获取到彼此的window对象的,但蛋疼的是你却不能使用获取到的window对象的属性和方法(html5中的postMessage方法是一个例外,还有些浏览器比如ie6也可以使用top、parent等少数几个属性),总之,你可以当做是只能获取到一个几乎无用的window对象。比如,有一个页面,它的地址是http://www.example.com/a.html  , 在这个页面里面有一个iframe,它的src是http://example.com/b.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的:

      QQ截图20130613230631

    这个时候,document.domain就可以派上用场了,我们只要把http://www.example.com/a.html 和 http://example.com/b.html这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。例如:a.b.example.com 中某个文档的document.domain 可以设成a.b.example.com、b.example.com 、example.com中的任意一个,但是不可以设成 c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。

    在页面 http://www.example.com/a.html 中设置document.domain:

    QQ截图20130613230631

    在页面 http://example.com/b.html 中也设置document.domain,而且这也是必须的,虽然这个文档的domain就是example.com,但是还是必须显示的设置document.domain的值:

    QQ截图20130613230631

    这样我们就可以通过js访问到iframe中的各种属性和对象了。

    不过如果你想在http://www.example.com/a.html 页面中通过ajax直接请求http://example.com/b.html 页面,即使你设置了相同的document.domain也还是不行的,所以修改document.domain的方法只适用于不同子域的框架间的交互。如果你想通过ajax的方法去与不同子域的页面交互,除了使用jsonp的方法外,还可以用一个隐藏的iframe来做一个代理。原理就是让这个iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe,这样我们就可以让iframe去发送ajax请求,然后收到的数据我们也可以获得了。

    3、使用window.name来进行跨域

    window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

    比如:有一个页面a.html,它里面有这样的代码:

    QQ截图20130613230631

    再看看b.html页面的代码:

    QQ截图20130613230631

    a.html页面载入后3秒,跳转到了b.html页面,结果为:

    QQ截图20130613230631

    我们看到在b.html页面上成功获取到了它的上一个页面a.html给window.name设置的值。如果在之后所有载入的页面都没对window.name进行修改的话,那么所有这些页面获取到的window.name的值都是a.html页面设置的那个值。当然,如果有需要,其中的任何一个页面都可以对window.name的值进行修改。注意,window.name的值只能是字符串的形式,这个字符串的大小最大能允许2M左右甚至更大的一个容量,具体取决于不同的浏览器,但一般是够用了。

    上面的例子中,我们用到的页面a.html和b.html是处于同一个域的,但是即使a.html与b.html处于不同的域中,上述结论同样是适用的,这也正是利用window.name进行跨域的原理。

    下面就来看一看具体是怎么样通过window.name来跨域获取数据的。还是举例说明。

    比如有一个www.example.com/a.html页面,需要通过a.html页面里的js来获取另一个位于不同域上的页面www.cnblogs.com/data.html里的数据。

    data.html页面里的代码很简单,就是给当前的window.name设置一个a.html页面想要得到的数据值。data.html里的代码:

    QQ截图20130613230631

    那么在a.html页面中,我们怎么把data.html页面载入进来呢?显然我们不能直接在a.html页面中通过改变window.location来载入data.html页面,因为我们想要即使a.html页面不跳转也能得到data.html里的数据。答案就是在a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取data.html的数据,然后a.html再去得到iframe获取到的数据。

    充当中间人的iframe想要获取到data.html的通过window.name设置的数据,只需要把这个iframe的src设为www.cnblogs.com/data.html就行了。然后a.html想要得到iframe所获取到的数据,也就是想要得到iframe的window.name的值,还必须把这个iframe的src设成跟a.html页面同一个域才行,不然根据前面讲的同源策略,a.html是不能访问到iframe里的window.name属性的。这就是整个跨域过程。

    看下a.html页面的代码:

    上面的代码只是最简单的原理演示代码,你可以对使用js封装上面的过程,比如动态的创建iframe,动态的注册各种事件等等,当然为了安全,获取完数据后,还可以销毁作为代理的iframe。网上也有很多类似的现成代码,有兴趣的可以去找一下。

    通过window.name来进行跨域,就是这样子的。

    4、使用HTML5中新引进的window.postMessage方法来跨域传送数据

    window.postMessage(message,targetOrigin)  方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。

    调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 *  。

    需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。

    上面所说的向其他window对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。在讨论第二种方法的时候,我们说过,不同域的框架间是可以获取到对方的window对象的,而且也可以使用window.postMessage这个方法。下面看一个简单的示例,有两个页面

    QQ截图20130613230631

    QQ截图20130613230631

    我们运行a页面后得到的结果:

    QQ截图20130613230631

    我们看到b页面成功的收到了消息。

    使用postMessage来跨域传送数据还是比较直观和方便的,但是缺点是IE6、IE7不支持,所以用不用还得根据实际需要来决定。

    5.CORS

    Cross-Origin Resource Sharing(CORS)跨域资源共享是一份浏览器技术的规范,提供了 Web 服务从不同域传来沙盒脚本的方法,以避开浏览器的同源策略,确保安全的跨域数据传输。现代浏览器使用CORS在API容器如XMLHttpRequest来减少HTTP请求的风险来源。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。服务器一般需要增加如下响应头的一种或几种:

    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: POST, GET, OPTIONS
    Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
    Access-Control-Max-Age: 86400

    跨域请求默认不会携带Cookie信息,如果需要携带,请配置下述参数:

    "Access-Control-Allow-Credentials": true
    // Ajax设置
    "withCredentials": true

    6、 Nodejs中间件代理跨域

    node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

    1、 非vue框架的跨域(2次跨域)

    利用node + express + http-proxy-middleware搭建一个proxy服务器。

    1.)前端代码示例:

    var xhr = new XMLHttpRequest();
    
    // 前端开关:浏览器是否读写cookie
    xhr.withCredentials = true;
    
    // 访问http-proxy-middleware代理服务器
    xhr.open('get', 'http://www.demo1.com:3000/login?user=admin', true);
    xhr.send();

    2.)中间件服务器:

    var express = require('express');
    var proxy = require('http-proxy-middleware');
    var app = express();
    
    app.use('/', proxy({
        // 代理跨域目标接口
        target: 'http://www.demo2.com:8080',
        changeOrigin: true,
    
        // 修改响应头信息,实现跨域并允许带cookie
        onProxyRes: function(proxyRes, req, res) {
            res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
            res.header('Access-Control-Allow-Credentials', 'true');
        },
    
        // 修改响应信息中的cookie域名
        cookieDomainRewrite: 'www.demo1.com'  // 可以为false,表示不修改
    }));
    
    app.listen(3000);
    console.log('Proxy server is listen at port 3000...');

    3.)Nodejs后台

    ar http = require('http');
    var server = http.createServer();
    var qs = require('querystring');
    
    server.on('request', function(req, res) {
        var params = qs.parse(req.url.substring(2));
    
        // 向前台写cookie
        res.writeHead(200, {
            'Set-Cookie': 'l=a123456;Path=/;Domain=www.demo2.com;HttpOnly'   // HttpOnly:脚本无法读取
        });
    
        res.write(JSON.stringify(params));
        res.end();
    });
    
    server.listen('8080');
    console.log('Server is running at port 8080...');
    2、 vue框架的跨域(1次跨域)

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

    webpack.config.js部分配置:

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

    7、 WebSocket协议跨域

    WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
    原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

    1.)前端代码:

    <div>user input:<input type="text"></div>
    <script src="./socket.io.js"></script>
    <script>
    var socket = io('http://www.demo2.com:8080');
    
    // 连接成功处理
    socket.on('connect', function() {
        // 监听服务端消息
        socket.on('message', function(msg) {
            console.log('data from server: ---> ' + msg); 
        });
    
        // 监听服务端关闭
        socket.on('disconnect', function() { 
            console.log('Server socket has closed.'); 
        });
    });
    
    document.getElementsByTagName('input')[0].onblur = function() {
        socket.send(this.value);
    };
    </script>

    2.)Nodejs socket后台:

    // 启http服务
    var server = http.createServer(function(req, res) {
        res.writeHead(200, {
            'Content-type': 'text/html'
        });
        res.end();
    });
    
    server.listen('8080');
    console.log('Server is running at port 8080...');
    
    // 监听socket连接
    socket.listen(server).on('connection', function(client) {
        // 接收信息
        client.on('message', function(msg) {
            client.send('hello:' + msg);
            console.log('data from client: ---> ' + msg);
        });
    
        // 断开处理
        client.on('disconnect', function() {
            console.log('Client socket has closed.'); 
        });
    });

    参考博文:

    https://www.cnblogs.com/2050/p/3191744.html

    https://www.cnblogs.com/sdcs/p/8484905.html

  • 相关阅读:
    HttpWebRequest、HttpWebResponse简单Demo
    向虚拟机发短信(android SMS 调试)
    双系统时间相关8小时
    电脑开机报警声
    Windows Live Writer 代码插件
    配置Linux防火墙
    配置yum源方法,以及失效时的处理
    xp系统下硬盘安装centos6.5
    Android 四大组件 与 MVC 架构模式
    elasticsearch GIS空间查询问题解决
  • 原文地址:https://www.cnblogs.com/web-record/p/9909895.html
Copyright © 2011-2022 走看看