• 彻底搞清浏览器和服务器跨域


    彻底搞清浏览器跨域问题

    这里说的跨域是浏览器特有的,服务器则不存在

    同源策略

    最初,它的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。所谓“同源”指的是“三个相同”:

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

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


    设想这样一种情况:A 网站是一家银行,用户登录以后,A 网站在用户的机器上设置了一个 Cookie,包含了一些隐私信息(比如存款总额)。用户离开 A 网站以后,又去访问 B 网站,如果没有同源限制,B 网站可以读取 A 网站的 Cookie,那么隐私信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。

    随着互联网的发展,同源政策越来越严格。目前,如果非同源,共有三种行为受到限制

    1. 无法获取非同源网页的 cookie、localstorage 和 indexedDB。
    2. 无法访问非同源网页的 DOM (iframe)。
    3. 无法向非同源地址发送 AJAX 请求 或 fetch 请求(可以发送,但浏览器拒绝接受响应)。

    Ajax 跨域

    浏览器的同源策略会导致跨域,也就是说,如果协议、域名或者端口有一个不同,都被当作是不同的域,就不能使用 Ajax 向不同源的服务器发送 HTTP 请求。首先我们要明确一个问题,请求跨域了,请求到底发出去没有?答案是肯定发出去了,但是浏览器拦截了响应。

    为什么要有跨域

    Ajax 的同源策略主要是为了防止 CSRF(跨站请求伪造) 攻击,如果没有 AJAX 同源策略,相当危险,我们发起的每一次 HTTP 请求都会带上请求地址对应的 cookie,那么可以做如下攻击:

    1. 用户登录了自己的银行页面 mybank.com,mybank.com向用户的cookie中添加用户标识。

    2. 用户浏览了恶意页面 evil.com。执行了页面中的恶意AJAX请求代码。

    3. evil.com向http://mybank.com发起AJAX HTTP请求,请求会默认把http://mybank.com对应cookie也同时发送过去。

    4. 银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。

    5. 而且由于Ajax在后台执行,用户无法感知这一过程。

    DOM同源策略也一样,如果 iframe 之间可以跨域访问,可以这样攻击:

    1. 做一个假网站,里面用iframe嵌套一个银行网站 mybank.com。

    2. 把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。

    3. 这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。


    跨域的解决方式

    CORS

    CORS 是一个 W3C 标准,全称是跨域资源共享(Cross-origin resource sharing),它允许浏览器向跨源服务器,发出XMLHttpRequest请求。

    整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

    服务器端配置如下:

    • Access-Control-Allow-Origin(必含) – 允许的域名,只能填 *(通配符)或者单域名。
    • Access-Control-Allow-Methods(必含) – 这允许跨域请求的 http 方法(常见有 POST、GET、OPTIONS)。
    • Access-Control-Allow-Headers(当预请求中包含 Access-Control-Request-Headers 时必须包含) – 这是对预请求当中 Access-Control-Request-Headers 的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。
    • Access-Control-Allow-Credentials(可选) – 表示是否允许发送Cookie,只有一个可选值:true(必为小写)。如果不包含cookies,请略去该项,而不是填写false。这一项与 XmlHttpRequest 对象当中的 withCredentials 属性应保持一致,即 withCredentials 为true时该项也为true;withCredentials 为false时,省略该项不写。反之则导致请求失败。
    • Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。在有效时间内,浏览器无须为同一请求再次发起预检请求。

    CORS 跨域的判定流程:

    1. 浏览器先根据同源策略对前端页面和后台交互地址做匹配,若同源,则直接发送数据请求;若不同源,则发送跨域请求。
    2. 服务器收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何允许跨域,则文件头里不包含 Access-Control-Allow-origin 字段,若配置过域名,则返回 Access-Control-Allow-origin + 对应配置规则里的域名的方式。
    3. 浏览器根据接受到的 响应头里的 Access-Control-Allow-origin 字段做匹配,若无该字段,说明不允许跨域,从而抛出一个错误;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器接受该响应;若不同源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。
    • 上面说到的两种类型的报错,控制台输出是不一样的:
    1. 服务器允许跨域请求,但是 Origin 指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest的onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是200。
    <!--控制台返回结果-->
     XMLHttpRequest cannot load http://localhost/city.json.
     The 'Access-Control-Allow-Origin' header has a value 'http://segmentfault.com' that is not equal to the supplied origin. 
     Origin 'http://www.zhihu.com' is therefore notallowed access.
    
    1. 服务器不允许任何跨域请求
    <!--控制台返回结果-->
    XMLHttpRequest cannot load http://localhost/city.json.
    No 'Access-Control-Allow-Origin' header is present on the requested resource. 
    Origin 'http://www.zhihu.com' is therefore not allowed access.
    
    

    实际上浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

    • 简单请求是指满足以下条件的(一般只考虑前面两个条件即可):
    1. 使用 GET、POST、HEAD 其中一种请求方法。
    2. HTTP的头信息不超出以下几种字段:
      • Accept
      • Accept-Language
      • Content-Language
      • Last-Event-ID
      • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
    3. 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;
    4. XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。 请求中没有使用 ReadableStream 对象。

    对于简单请求,浏览器直接发起 CORS 请求,具体来说就是服务器端会根据请求头信息中的 origin 字段(包括了协议 + 域名 + 端口),来决定是否同意这次请求。如果 origin 指定的源在许可范围内,服务器返回的响应,会多出几个头信息字段:

    Access-Control-Allow-Origin: http://xxx.xxx.com
    Access-Control-Allow-Credentials: true
    Access-Control-Expose-Headers: FooBar
    Content-Type: text/html; charset=utf-8
    
    • 非简单请求

    非简单请求时指那些对服务器有特殊要求的请求,比如请求方法是 put 或 delete,或者 content-type 的类型是 application/json。其实简单请求之外的都是非简单请求了。

    非简单请求的 CORS 请求,会在正式通信之前,使用 OPTIONS 方法发起一个预检(preflight)请求到服务器,浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错。

    预检请求的头部:
    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...
    

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

    JSONP

    JSONP 的原理就是利用 <script> 标签的 src 属性没有跨域的限制,通过指向一个需要访问的地址,由服务端返回一个预先定义好的 Javascript 函数的调用,并且将服务器数据以该函数参数的形式传递过来,此方法需要前后端配合完成。

     // 定义获取数据的回调方法
    function getData(data) {
      console.log(data);
    }
    
     // 创建一个script标签,并且告诉后端回调函数名叫 getData
    var body = document.getElementsByTagName('body')[0];
    var script = document.gerElement('script');
    script.type = 'text/javasctipt';
    script.src = 'demo.js?callback=getData';
    body.appendChild(script);
    
     // script 加载完毕之后从页面中删除,否则每次点击生成许多script标签
    script.onload = function () {
      document.body.removeChild(script);
    }
    JSONP 使用简单且兼容性不错,但是只限于 get 请求。
    

    服务器代理

    • 浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。
    • 一般我们在本地环境开发时,就是使用 webpack-dev-server 在本地开启一个服务进行代理访问的。

    • document.domain

      该方式只能用于二级域名相同的情况下,比如 a.test.com b.test.com 适用于该方式。
      只需要给两个页面都添加 document.domain = 'test.com',通过在 a.test.com 创建一个 iframe,去控制 iframe 的 window,从而进行交互。

    • postMessage

    window.postMessage 是一个 HTML5 的 api,允许两个窗口之间进行跨域发送消息。
    这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息

    跨域资源共享 CORS 详解

    更多参考连接


    后面详解 vue-cli 里面 如何配置代理 和 如何使用 nginx 配置代理

  • 相关阅读:
    Mock工具Mockito教程
    基于Jmeter的自动化测试实施方案
    基于敏捷的测试交付物通用设计
    Jmeter 冒烟测试设计
    Jmockit之mock特性详解
    Sonar 常用代码规则(二)
    从Hg迁移到Git
    使用谷歌colab训练crnn模型
    使用AIstudio训练YOLOv3模型
    阿里云服务器部署Djano+Nginx+uWSGI+conda+Https
  • 原文地址:https://www.cnblogs.com/kgwei520blog/p/13667378.html
Copyright © 2020-2023  润新知