zoukankan      html  css  js  c++  java
  • BOM 浏览器对象模型_同源限制

    “同源政策”(same-origin policy)

    浏览器安全的基石

    • 协议相同
    • 域名相同
    • 端口相同

    1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策

    • A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”
    • 浏览器同时还规定,提交表单不受同源政策的限制

    目前,如果非同源,共有以下三项受到限制

    • 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。
    • 无法接触非同源网页的 DOM
    • 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)

    通过 JavaScript 脚本可以拿到其他窗口的 window 对象。如果是非同源的网页,目前允许一个窗口可以接触其他网页的window对象的九个属性和四个方法

    window.closed
    window.frames
    window.length
    window.location        唯一一个可以用读写的属性
    window.opener
    window.parent
    window.self
    window.top
    window.window
    window.blur()
    window.close()
    window.focus()
    window.postMessage()

    目的: 保证用户信息的安全,防止恶意的网站窃取数据

    Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。

    只适用于 Cookie 和 iframe 窗口

    如果两个网页一级域名相同,只是次级域名不同,浏览器允许通过设置 document.domain 共享 Cookie

    A 网页的网址是 http://w1.example.com/a.html

    B 网页的网址是http://w2.example.com/b.html

    那么只要设置相同的 document.domain ,两个网页就可以共享 Cookie。因为浏览器通过 document.domain 属性来检查是否同源

    • // 两个网页都需要设置
      document.domain = 'example.com';

    注意,A 和 B 两个网页都需要设置 document.domain 属性,才能达到同源的目的。

    因为设置 document.domain 的同时,会把端口重置为 null

    因此如果只设置一个网页的 document.domain,会导致两个网址的端口不同,还是达不到同源的目的

    现在,A 网页通过脚本设置一个 Cookie ---- document.cookie = "test1=hello";

    B 网页就可以读到这个 Cookie ---- var allCookie = document.cookie;

    另外,服务器也可以在设置 Cookie 的时候,指定 Cookie 的所属域名为一级域名,比如.example.com

    • Set-Cookie: key=value; domain=.example.com; path=/

      这样的话,二级域名 和 三级域名 不用做任何设置,都可以读取这个 Cookie

    对于完全不同源的网站,目前有两种方法,可以解决跨域窗口的通信问题

    1. 片段识别符(fragment identifier)

    片段标识符(fragment identifier)指的是,URL 的 # 号后面的部分

    比如 http://example.com/x.html#fragment 的 #fragment如果只是改变片段标识符,页面不会重新刷新

    • 父窗口可以把信息,写入子窗口的片段标识符 ---- 子窗口通过监听 hashchange 事件得到通知

    父窗口

    • var src = originURL + '#' + data;
      document.getElementById('myIFrame').src = src;

    子窗口 iframe

    • window.onhashchange = checkMessage;
      
      function checkMessage() {
          var message = window.location.hash;
          // ...
      }

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

    • parent.location.href = target + '#' + hash;

    上面的这种方法属于破解,HTML5 为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)

    2. 跨文档通信API(Cross-document messaging)

    这个 API 为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源

    举例来说,父窗口 aaa.com 向子窗口 bbb.com 发消息,调用 postMessage 方法就可以了

    • // 父窗口打开一个子窗口
      var popup = window.open('http://bbb.com', 'title');
      // 父窗口向子窗口发消息 popup.postMessage('Hello World!', 'http://bbb.com');

    子窗口向父窗口发送消息的写法类似

    • // 子窗口向父窗口发消息
      window.opener.postMessage('Nice to see you', 'http://aaa.com');

    父窗口和子窗口都可以通过 message 事件,监听对方的消息

    • // 父窗口和子窗口都可以用下面的代码,
      // 监听 message 消息
      window.addEventListener('message', function (e) {
        console.log(e.data);
      },false);

    message事件的参数是事件对象event,提供以下三个属性。

    • event.source        发送消息的窗口
    • event.origin        消息发向的网址
    • event.data        消息内容

    子窗口通过 event.source 属性引用父窗口,然后发送消息

    • window.addEventListener('message', receiveMessage);
      function receiveMessage(event) {
          event.source.postMessage('Nice to see you!', '*');
      }

    注意: 

    • receiveMessage 函数 里面没有过滤信息的来源,任意网址发来的信息都会被处理
    • postMessage 方法 中指定的目标窗口的网址是一个星号,表示该信息可以向任意网址发送

    通常来说,这两种做法是不推荐的,因为不够安全,可能会被恶意利用

    event.origin 属性可以过滤不是发给本窗口的消息

    • window.addEventListener('message', receiveMessage);
      function receiveMessage(event) {
          if (event.origin !== 'http://aaa.com') return;
          if (event.data === 'Hello World') {
              event.source.postMessage('Hello', event.origin);
          } else {
              console.log(event.data);
          } 
      }

    第一个参数 ---- 具体的信息内容

    第二个参数 ---- 接收消息的窗口的源(origin),即“协议 + 域名 + 端口”。也可以设为 * ,表示不限制域名,向所有窗口发送

    通过 window.postMessage,读写其他窗口的 LocalStorage 也成为了可能

    • 父窗口发送消息的代码如下
    • var win = document.getElementsByTagName('iframe')[0].contentWindow;
      var obj = { name: 'Jack' };
      win.postMessage(
          JSON.stringify({key: 'storage', data: obj}),
          'http://bbb.com'
      );
    • 子窗口将父窗口发来的消息,写入自己的 LocalStorage
    • window.onmessage = function(e) {
          if (e.origin !== 'http://bbb.com') {
              return;
          }
          var payload = JSON.parse(e.data);
          localStorage.setItem(payload.key, JSON.stringify(payload.data));
      };
    • 加强版的父窗口发送消息代码如下。
    • var win = document.getElementsByTagName('iframe')[0].contentWindow;
      var obj = { name: 'Jack' };
      
      // 存入对象
      win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://bbb.com');
      
      // 读取对象
      win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
      
      window.onmessage = function(e) {
          if (e.origin != 'http://aaa.com'){
              return;
          }
          console.log(JSON.parse(e.data).name);
      };
    • 加强版的子窗口接收消息的代码如下
    • window.onmessage = function(e) {
          if (e.origin !== 'http://bbb.com'){
              return;
          }
          
          var payload = JSON.parse(e.data);
          switch (payload.method) {
              case 'set':
                  localStorage.setItem(payload.key, JSON.stringify(payload.data));
                  break;
              case 'get':
                  var parent = window.parent;
                  var data = localStorage.getItem(payload.key);
                  parent.postMessage(data, 'http://aaa.com');
                  break;
              case 'remove':
                  localStorage.removeItem(payload.key);
                  break;
          }
      };

    解决 AJAX 请求(服务器与客户端 交互) 跨域的 三种方法

    JSONP 只能发 GET 请求

    • 特点: 

    简单适用,老式浏览器全部支持,服务端改造非常小

    • 基本思想: 

    网页通过动态插入一个 <script> 元素,向服务器请求 JSON 数据 ---- 这种 一般请求 的做法不受同源政策限制

    服务器收到请求后,将数据放在一个指定名字的回调函数里传回来 ---- foo({"ip": "192.168.3.31"})

    由于 <script> 元素请求的脚本,直接作为代码运行

    只要浏览器定义了 foo 函数,foo 函数就会立即调用。作为参数的 JSON 数据 被视为 JavaScript 对象

    使用:

    前端页面

    • function foo(data) {
          console.log('Your public IP address is: ' + data.ip);
      };
      
      function addScriptTag(src) {
          var script = document.createElement('script');
          script.setAttribute("type","text/javascript");
          script.src = src;
          document.body.appendChild(script);
      }
      
      window.onload = function () {
          addScriptTag('http://example.com/ip?callback=foo');
      }

    后台服务器

    • 服务器收到这个请求以后,会将数据放在回调函数的参数位置返回
    • foo({"ip": "192.168.3.31"})

    WebSocket 通信协议

    WebSocket 是一种通信协议,使用 ws://(非加密)和 wss://(加密)作为协议前缀

    该协议不实行同源政策,几乎所有的浏览器都支持,所以只要服务器支持,就可以通过它进行跨源通信

    • 浏览器发送请求实例
    • GET /chat HTTP/1.1
      Host:                          server.example.com
      Upgrade:                       websocket
      Connection:                    Upgrade
      Sec-WebSocket-Key:             x3JJHMbDL1EzLkh9GBhXDw==
      Sec-WebSocket-Protocol:        chat, superchat
      Sec-WebSocket-Version:         13
      Origin:                        http://example.com

    服务器可以根据 origin 这个字段,判断是否许可本次通信。

    如果该域名在白名单内,服务器就会做出如下回应

    • HTTP/1.1 101 Switching Protocols
      Upgrade: websocket
      Connection: Upgrade
      Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
      Sec-WebSocket-Protocol: chat

    CORS 跨源资源分享(Cross-Origin Resource Sharing) 运行任何类型的请求

    是一个W3C 标准,也是 跨源 AJAX 请求 的根本解决方法

    整个 CORS 通信过程,都是浏览器自动完成

    CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能

    实现 CORS 通信的关键是服务器。所以,只要服务器实现了 CORS 接口,就可以跨域通信

    • 作用: 允许浏览器向跨域的服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制
    • CORS 通信 与 普通的 AJAX 通信

    没有差别,代码完全一样

    浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知

    CORS 请求分成两类

    • 简单请求(simple request)---- 简单的 HTTP 方法与简单的 HTTP 头信息的结合

    同时满足以下两大条件

    请求方法是 GET、POST、HEAD 其中之一

    HTTP 的头信息不超出以下几种字段

    xAccept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type        只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

    这样划分的原因是,表单在历史上一直可以跨域发出请求。

    简单请求就是表单请求,浏览器沿袭了传统的处理方式,不把行为复杂化

    对于非简单请求,浏览器会采用新的处理方式

    • 基本流程

    浏览器直接发出 CORS 请求

    具体来说,就是在头信息之中,增加一个 Origin 字段

    Origin 字段用来说明,本次请求来自哪个域(协议 + 域名 + 端口)

    服务器根据这个值,决定是否同意这次请求

    • GET /cors HTTP/1.1
      Origin: http://api.bob.com
      Host: api.alice.com
      Accept-Language: en-US
      Connection: keep-alive
      User-Agent: Mozilla/5.0...

    如果 Origin 指定的源,不在许可范围内服务器会返回一个正常的 HTTP 回应

    浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段(详见下文)就知道出错了

    从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获

    注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是200

    如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段

    • Access-Control-Allow-Origin

    该字段是必须的

    值要么是请求时 Origin 字段的值;要么是一个*,表示接受任意域名的请求

    • Access-Control-Allow-Credentials

    该字段可选

    值是一个布尔值,表示是否允许发送 Cookie

    默认情况下 false,Cookie 不包括在 CORS 请求之中

    默认不包含 Cookie 信息(以及 HTTP 认证信息等),这是为了降低 CSRF 攻击的风险

    设为 true,即表示服务器明确许可,浏览器可以把 Cookie 包含在请求中,一起发给服务器

    某些场合,服务器可能需要拿到 Cookie

    1. 这时需要服务器显式指定 Access-Control-Allow-Credentials字段,告诉浏览器可以发送 Cookie

    • Access-Control-Allow-Credentials: true

    2. 同时,开发者必须在 AJAX 请求中打开 withCredentials 属性

    否则,即使服务器要求发送 Cookie,浏览器也不会发送

    或者说,服务器要求设置 Cookie,浏览器也不会处理

    • var xhr = new XMLHttpRequest();
      xhr.withCredentials = true;

    注意:

     如果服务器要求浏览器发送 Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名

    同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传

    其他域名的 Cookie 并不会上传,且(跨域)原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie

    这个值也只能设为 true,如果服务器不要浏览器发送 Cookie,不发送该字段即可

    • Access-Control-Expose-Headers

    该字段可选

    CORS 请求时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到6个服务器返回的基本字段

    Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。

    如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定

    下面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值

    • Access-Control-Allow-Origin: http://api.bob.com
      Access-Control-Allow-Credentials: true
      Access-Control-Expose-Headers: FooBar
      Content-Type: text/html; charset=utf-8
    • 非简单请求(not-so-simple request)---- 

    是那种对服务器提出特殊要求的请求

    比如 请求方法是 PUT 或 DELETE,或者 Content-Type 字段的类型是 application/json

    会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”请求 (preflight)

    浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段

    只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错

    这是为了防止这些新增的请求,对传统的没有 CORS 支持的服务器形成压力,给服务器一个提前拒绝的机会

    这样可以防止服务器收到大量 DELETE 和 PUT 请求,这些传统的表单不可能跨域发出的请求

    举个例子:

    • var url = 'http://api.alice.com/cors';
      var xhr = new XMLHttpRequest();
      xhr.open('PUT', url, true);
      xhr.setRequestHeader('X-Custom-Header', 'value');
      xhr.send();

    上面代码中,HTTP 请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header

    • 浏览器发现,这是一个非简单请求,就自动发出一个“预检”请求,要求服务器确认可以这样请求。
    • 下面是这个“预检”请求的 HTTP 头信息
    • “预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的
    • 头信息里面,关键字段是Origin,表示请求来自哪个源
    • Access-Control-Request-Method ---- 该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法,下例是PUT
    • Access-Control-Request-Headers ---- 该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段,下例是X-Custom-Header
    • OPTIONS /cors HTTP/1.1
      Origin: http://api.bob.com
      Access-Control-Request-Method: PUT
      Access-Control-Request-Headers: X-Custom-Header
      Host: api.alice.com
      Accept-Language: en-US
      Connection: keep-alive
      User-Agent: Mozilla/5.0...

    预检请求的回应

    服务器收到 “预检” 请求以后

    检查了 Origin、Access-Control-Request-Method Access-Control-Request-Headers 字段以后

    • 确认允许跨源请求,就可以做出回应
    • HTTP/1.1 200 OK
      Date: Mon, 01 Dec 2008 01:15:39 GMT
      Server: Apache/2.0.61 (Unix)
      Access-Control-Allow-Origin: http://api.bob.com
      Access-Control-Allow-Methods: GET, POST, PUT
      Access-Control-Allow-Headers: X-Custom-Header
      Content-Type: text/html; charset=utf-8
      Content-Encoding: gzip
      Content-Length: 0
      Keep-Alive: timeout=2, max=100
      Connection: Keep-Alive
      Content-Type: text/plain

    上面的 HTTP 回应中,关键的是 Access-Control-Allow-Origin 字段,表示http://api.bob.com可以请求数据

    该字段也可以设为星号,表示同意任意跨源请求

    如果服务器否定了“预检”请求

    会返回一个正常的 HTTP 回应

    但是没有任何 CORS 相关的头信息字段,或者明确表示请求不符合条件

    • OPTIONS http://api.bob.com HTTP/1.1
      Status: 200
      Access-Control-Allow-Origin: https://notyourdomain.com
      Access-Control-Allow-Method: POST

      这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被 XMLHttpRequest 对象的 onerror 回调函数捕获

    • 控制台会打印出如下的报错信息

    • Access-Control-Allow-Methods: GET, POST, PUT
      Access-Control-Allow-Headers: X-Custom-Header
      Access-Control-Allow-Credentials: true
      Access-Control-Max-Age: 1728000
      
      XMLHttpRequest cannot load http://api.alice.com.
      Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
    • Access-Control-Allow-Methods

    该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。

    注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次“预检”请求

    • Access-Control-Allow-Headers 

    如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的

    它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在“预检”中请求的字段

    • Access-Control-Allow-Credentials

    含义与同简单请求相同

    • Access-Control-Max-Age

    该字段可选,用来指定本次预检请求的有效期,单位为秒

    上面结果中,有效期是20天(1728000秒)即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求

    一旦服务器通过了“预检”请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样

    会有一个 Origin 头信息字段

    服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段

    JSONP 只支持GET请求,

    CORS 支持所有类型的 HTTP 请求。

    JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据

     

    --------小尾巴 ________一个人欣赏-最后一朵颜色的消逝-忠诚于我的是·一颗叫做野的心.决不受人奴役.怒火中生的那一刻·终将结束...
  • 相关阅读:
    【codecombat】 试玩全攻略 第二章 边远地区的森林 一步错
    【codecombat】 试玩全攻略 第十八关 最后的kithman族
    【codecombat】 试玩全攻略 第二章 边远地区的森林 woodlang cubbies
    【codecombat】 试玩全攻略 第二章 边远地区的森林 羊肠小道
    【codecombat】 试玩全攻略 第十七关 混乱的梦境
    【codecombat】 试玩全攻略 第二章 边远地区的森林 林中的死亡回避
    【codecombat】 试玩全攻略 特别关:kithguard斗殴
    【codecombat】 试玩全攻略 第二章 边远地区的森林 森林保卫战
    【codecombat】 试玩全攻略 第二章 边远地区的森林
    实验3 类和对象||
  • 原文地址:https://www.cnblogs.com/tianxiaxuange/p/10301151.html
Copyright © 2011-2022 走看看