• 浏览器同源政策及其规避方法


    参考:http://www.cnblogs.com/digdeep/p/4170059.html

               http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html

               https://blog.csdn.net/letterTiger/article/details/79520375

    一、含义

    “同源”指的是三个相同:协议相同,域名相同,端口相同

    举例来说,http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。它的同源情况如下:

    • http://www.example.com/dir2/other.html:同源
    • http://example.com/dir/other.html:不同源(域名不同)
    • http://v2.www.example.com/dir/other.html:不同源(域名不同)
    • http://www.example.com:81/dir/other.html:不同源(端口不同)

    二、目的:

    同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

    设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?

    很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。

    由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

    三、限制范围

    如果非同源,会有三种行为受到限制

    1、cookie、localStorage和indexDB无法读取。

    2、DOM无法获得

    3、AJAX请求不能发送

    那么如何规避上面三种限制呢?

    1、hash

    因为hash的改变并不会引起页面的刷新同时可以通过window.onhashchange事件监听到hash的改变,所以通过hash来跨域传递数据。

    <!-- http://example.com/index.html -->
    <html>
        <head>
            <meta charset="utf8"/>
            <title>跨域DEMO --- hash</title>
        </head>
        <body>
            <iframe id="iframe" src="http://example2.com/index.html"></iframe>
            <script>
                var ifra = document.getElementById('iframe');
                ifra.onload = function(){
                    // ifra 加载完成了
                    ifra.src = ifra.src + '#data';
                }
            </script>
        </body>
    </html>
    <!-- http://example2.com/index.html -->
    <!-- 在iframe中的页面(example2.com)如果和iframe所在页面(example.com)不同域是不能获取所在页面的DOM,然后通过hash将数据传递回去的,也就是说如果同域就可以通过该方法向所在页面传递数据 -->
    <html>
        <head>
            <meta charset="utf8"/>
            <title>跨域DEMO --- hash</title>
        </head>
        <body>
            <script>
                window.onhashchange = function(){
                    // 打印通过hash传过来的数据
                    console.log( location.hash ); 
                }
            </script>
        </body>
    </html>

    该方法会直接暴露所传递的数据并且对所传数据有大小限制。

    2、document.domin

    若两个文档的域相同则可以获取对方的DOM对象,并且可以通过设置document.domain的值来让两个文档的域保持一致,但是document.domain并不是可以设置任何值,只能设置为当前域的超域,比如:

    m.example.com 设置为 example.com,并且不能 example.com 设置为 m.example.com 也不能将 m.example.com 设置为 example2.com。

    所以document.domain只可以在拥有相同的主域名的不同子域名之间跨域。

    <!--http://a.example.com-->
    <html>
        <head>
            <meta charset="utf8"/>
            <title>跨域DEMO --- document.domain</title>
        </head>
        <body>
            <p id="data">
                我在 a.example.com 下
            </p>
            <iframe id="ifra" src="http://b.example.com/index.html">
    
            </iframe>
            <script>
                domain.domain = 'example.com';
                var ifra = document.getElementById('ifra');
                ifra.onload = function(){
                    console.log( ifra.contentWindow.document.getElementById('data').innerHTML ); // 我是b.example.com下的
                }
            </script>
        </body>
    </html>
    <!--http://b.example.com-->
    <html>
        <head>
            <meta charset="utf8"/>
            <title>跨域DEMO --- document.domain</title>
        </head>
        <body>
            <p id="data">
                我是b.example.com下的
            </p>
            <script>
                domain.domain = 'example.com';
                console.log( parent.document.getElementById('data').innerHTML ); // 我在 a.example.com 下
            </script>
        </body>
    </html>

    3、window.name

    window.name有一个特性,即使当前窗口的地址改变了window.name的值也不会改变。可以利用这一特性来进行跨域,步骤如下:

    1. 通过iframe加载需要获取数据的地址
    2. 在加载的文件上将数据设置到window.name上
    3. 数据获取完成后将iframe的地址设置为当前文档同域
    4. 通过DOM操作拿到window.name上的数据
    <!--http://example.com-->
    <html>
        <head>
            <meta charset="utf8"/>
            <title>跨域DEMO --- window.name</title>
        </head>
        <body>
            <iframe id="ifra" src="http://example2.com/index.html"></iframe>
            <script>
                var retData = false;
                var ifra = document.getElementById('ifra');
                ifra.onload = function(){
                    if( !retData ){
                        ifra.src = 'http://example.com/index.html'
                        retData = true;
                    }else{
                        console.log( ifra.contentWindow.name ); // 我在example2.com下
                    }
                }
            </script>
        </body>
    </html>
    <!--http://example2.com-->
    <html>
        <head>
            <meta charset="utf8"/>
            <title>跨域DEMO --- window.name</title>
        </head>
        <body>
            <iframe src="http://example2.com/index.html"/>
            <script>
                window.name = '我在example2.com下';
            </script>
        </body>
    </html>

    4、window.postMessage

    以上几种跨域的方法都属于破解行为,而postMessage是H5为跨域提供的解决方法。

    <!--http://example.com-->
    <html>
        <head>
            <meta charset="utf8"/>
            <title>跨域DEMO --- window.postMessage</title>
        </head>
        <body>
            <iframe id="ifra" src="http://example3.com/index.html"/>
            <script>
                var ifra = document.getElementById('ifra');
                ifra.onload = function(){
                    ifra.contentWindow.postMessage('我来自example.com', 'http://example3.com')
                }
            </script>
        </body>
    </html>
    <!--http://example3.com-->
    <html>
        <head>
            <meta charset="utf8"/>
            <title>跨域DEMO --- window.postMessage</title>
        </head>
        <body>
            <iframe src="http://example2.com/index.html"/>
            <script>
                window.addEventListener('message', function(messageEvent){
                   // 我们能信任信息来源吗?
                   if (event.origin !== "http://example.com")
                       return;
    
                    // event.source 就当前弹出页的来源页面
                    // event.data 是 "我来自example.com"
                     console.log( messageEvent.data ); // 我来自example.com }, false)
    </script>
    </body>
    </html>

    messageEvent对象上的属性中有三个属性要注意,分别是:

    1. source 发送消息的窗体
    2. origin 发送消息的域名 (根据域名判断是否处理该消息)
    3. data 发送消息的内容 (获取发送的消息内容)

    5、Ajax同源策略的规避

    jsonp

    我们知道,在页面上有三种资源是可以与页面本身不同源的。他们是:js脚本,css样式文件,图片,

    像taobao等大型网站,很定会将这些静态资源放入cdn中,然后在页面上连接,如下所示,所以它们是可以链接访问到不同源的资源的。

    1)<script type="text/javascript" src="某个cdn地址" ></script>

    2)<link type="text/css" rel="stylesheet" href="某个cdn地址" />

    3)<img src="某个cdn地址" alt=""/>

    jsonp就是利用了<script>标签可以链接到不同源的js脚本,来到达跨域目的。当链接的资源到达浏览器时,浏览器会根据他们的类型来采取不同的处理方式,比如,如果是css文件,则会进行对页面 repaint,如果是img 则会将图片渲染出来,如果是script 脚本,则会进行执行,比如我们在页面引入了jquery库,为什么就可以使用 $ 了呢?就是因为 jquery 库被浏览器执行之后,会给全局对象window增加一个属性: $ ,所以我们才能使用 $ 来进行各种处理。(另外为什么要一般要加css放在头部,而js脚本放在body尾部呢,就是为了减少repaint的次数,另外因为js引擎是单线程执行,如果将js脚本放在头部,那么在js引擎在执行js代码时,会造成页面暂停。)

    利用 页面上 script 标签可以跨域,并且其 src 指定的js脚本到达浏览器会执行的特性,我们可以进行跨域取得数据。我们用一个例子来说明:

    8888端口的html4项目中的jsonp.html页面代码如下:

    <!doctype html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="keywords" content="jsonp">
        <meta name="description" content="jsonp">
        <title>jsonp</title>
    </head>
    <body>
     
    <script type="text/javascript" src="js/jquery-1.11.1.js"></script>
    <script type="text/javascript">
    var url = "http://localhost:8080/html5/jsonp_data.js";
    // 创建script标签,设置其属性
    var script = document.createElement('script');
    script.setAttribute('src', url);
    // 把script标签加入head,此时调用开始
    document.getElementsByTagName('head')[0].appendChild(script);
    function callbackFun(data)
    {
        console.log(data.age);
        console.log(data.name);
    }  
    </script>
    </body>
    </html>

    其访问的8080端口的html5项目中的jsonp_data.js代码如下:

    callbackFun({"age":100,"name":"yuanfang"})

    将两个tomcate启动,用浏览器访问8888端口的html4项目中的jsonp.html,结果如下:

    上面我们看到,我们从8888 端口的页面通过 script 标签成功 的访问到了8080 端口下的jsonp_data.js中的数据。这就是 jsonp 的基本原理,利用script标签的特性,将数据使用json格式用一个函数包裹起来,然后在进行访问的页面中定义一个相同函数名的函数,因为 script 标签src引用的js脚本到达浏览器时会执行,而我们有定义了一个同名的函数,所以json格式的数据,就做完参数传递给了我们定义的同名函数了。这样就完成了跨域数据交换。

    明白了原理之后,我们再看一个更加实用的例子:

    8080端口的html5项目中定义一个servlet:

    package com.tz.servlet;
    
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import com.alibaba.fastjson.JSON;
    
    @WebServlet("/JsonServlet")
    public class JsonServlet extends HttpServlet 
    {
        private static final long serialVersionUID = 4335775212856826743L;
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) 
                throws ServletException, IOException 
        {
            String callbackfun = request.getParameter("mycallback");
            System.out.println(callbackfun);    // callbackFun
            response.setContentType("text/json;charset=utf-8");
            
            User user = new User();
            user.setName("yuanfang");
            user.setAge(100);
            Object obj = JSON.toJSON(user);
            
            System.out.println(user);            // com.tz.servlet.User@164ff87
            System.out.println(obj);            // {"age":100,"name":"yuanfang"}
            callbackfun += "(" + obj + ")";    
            System.out.println(callbackfun);    // callbackFun({"age":100,"name":"yuanfang"})
            
            response.getWriter().println(callbackfun);
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                throws ServletException, IOException 
        {
            this.doPost(request, response);
        }
    
    }

    在8888端口的html4项目中的jsonp.html来如下的跨域访问他:

    <!doctype html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="keywords" content="jsonp">
        <meta name="description" content="jsonp">
        <title>jsonp</title>
        <style type="text/css">
            *{margin:0;padding:0;}
            div{600px;height:100px;margin:20px auto;}
        </style>
    </head>
    <body>
        <div>
            <a href="javascript:;">jsonp测试</a>
        </div>
         
    <script type="text/javascript" src="js/jquery-1.11.1.js"></script>
    <script type="text/javascript">
    function callbackFun(data)
    {
        console.log(111);
        console.log(data.name);
        //data.age = 10000000;
        //alert(0000);
    }
    $(function(){
        $("a").on("click", function(){     
            $.ajax({
                type:"post",
                url:"http://localhost:8080/html5/JsonServlet",
                dataType:'jsonp',
                jsonp:'mycallback',
                jsonpCallback:'callbackFun',
                success:function(data) {
                    console.log(2222);
                    console.log(data.age);
                }
            });
        })
    });
    </script>
    </body>
    </html>

    结果如下:

    我们看到,我们成功的跨域取到了servlet中的数据,而且在我们指定的回调函数jsonpCallback:'callbackFun' 和 sucess 指定的回调函数中都进行了执行。而且总是callbackFun先执行,如果我们打开注释://data.age = 10000000; //alert(0000);

    就会发现:在callbackFun中对 data 进行修改之后,success指定的回调函数的结果也会发生变化,而且通过alert(0000),我们确定了如果alert(000)没有执行完,success指定的函数就不会开始执行,就是说两个回调函数是先后同步执行的。

    结果如下:

    jsonp 跨域与 get/post 

    我们知道 script,link, img 等等标签引入外部资源,都是 get 请求的,那么就决定了 jsonp 一定是 get 的,那么为什么我们上面的代码中使用的 post 请求也成功了呢?这是因为当我们指定dataType:'jsonp',不论你指定:type:"post" 或者type:"get",其实质上进行的都是 get 请求!!!从两个方面可以证明这一点:

    1)如果我们将JsonServlet中的 doGet()方法注释掉,那么上面的跨域访问就不能进行,或者在 doPost() 和 doGet() 方法中进行调试,都可以证明这一点;

    2)我们看下firebug中的“网络”选项卡:

    我们看到,即使我们指定 type:"post",当dataType:"jsonp" 时,进行的也是 GET请求,而不是post请求,也就是说jsonp时,type参数始终是"get",而不论我们指定他的值是什么,jquery在里面将它设定为了get. 我们甚至可以将 type 参数注释掉,都可以跨域成功:

    $(function(){
        $("a").on("click", function(){     
            $.ajax({
                //type:"post",
                url:"http://localhost:8080/html5/JsonServlet",
                dataType:'jsonp',
                jsonp:'mycallback',
                jsonpCallback:'callbackFun',
                success:function(data) {
                    console.log(2222);
                    console.log(data.age);
                }
            });
        })
    });

    所以jsonp跨域只能是get,jquery在封装jsonp跨域时,不论我们指定的是get还是post,他统一换成了get请求,估计这样可以减少错误吧。其对应的query源码如下所示:

    // Handle cache's special case and global
    jQuery.ajaxPrefilter( "script", function( s ) {
        if ( s.cache === undefined ) {
            s.cache = false;
        }
        if ( s.crossDomain ) {
            s.type = "GET";
            s.global = false;
        }
    });
  • 相关阅读:
    npx小工具
    2015 Multi-University Training Contest 1
    字符串 --- KMP Eentend-Kmp 自动机 trie图 trie树 后缀树 后缀数组
    AC自动机
    AC自动机
    区间合并 --- Codeforces 558D : Gess Your Way Out ! II
    暴力 + 贪心 --- Codeforces 558C : Amr and Chemistry
    计数排序 + 线段树优化 --- Codeforces 558E : A Simple Task
    Ubuntu 16.04 安装mysql并设置远程访问
    数学 --- 高斯消元 POJ 1830
  • 原文地址:https://www.cnblogs.com/zhaixr/p/9076888.html
Copyright © 2020-2023  润新知