跨域 【笔记整理】

180 阅读4分钟

跨域

同源的定义


如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。

目的


为了保证数据安全。防止网站信息被窃取。

为什么会出现跨域的问题

浏览器的同源限制策略


应用依赖的后端服务的服务可能分散在不同的工程中,往往这些工程的域名是不同的。

而浏览器由于同源政策会拒绝跨域请求。

注:严格的说,浏览器并不是拒绝所有的跨域请求,实际上拒绝的是跨域的读操作。浏览器的同源限制策略是这样执行的:

通常浏览器允许进行跨域写操作(Cross-origin writes),如链接,重定向;

通常浏览器允许跨域资源嵌入(Cross-origin embedding),如 img、script 标签;

通常浏览器不允许跨域读操作(Cross-origin reads)。


note: 为什么链接<a>是写操作呢,因为带了潜在的请求数据给网站,可以理解为写操作。0

跨域的三种常用方式

jsonp


使用js标签来请求对应的URL,来避免跨域请求数据被拦截。

通常数据交互的方式是把调用的函数名通过URL参数传给服务端,服务端返回的JS内容就是调函数,并把数据当作参数传递。

//请求
url?callback=func
//返回
func(data)

优点


浏览器支持多

缺点


只支持get请求

CORS


服务端Response headers设置 Access-Control-Allow-Origin 就可以开启 CORS

分为简单请求和非简单请求两种

非简单请求

非简单请求会触发预检请求

预检请求

是浏览器使用 OPTIONS 方法
首部会携带两个信息

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type //携带什么自定义请求头


预检请求的响应头会返回相应信息

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type //服务器允许请求中携带的首部字段
Access-Control-Max-Age: 86400 //表明该响应的有效时间为,单位是秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

简单请求

简单请求不会触发预检请求

  • 使用下列方法之一
    • GET
    • HEAD
    • POST
  • 没有人为设置对 CORS 安全的首部字段集合之外的其他首部字段

        对 CORS 安全的首部字段集合

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type (需要注意额外的限制)
  • [DPR](http://httpwg.org/http-extensions/client-hints.html#dpr)
  • [Downlink](http://httpwg.org/http-extensions/client-hints.html#downlink)
  • [Save-Data](http://httpwg.org/http-extensions/client-hints.html#save-data)
  • [Viewport-Width](http://httpwg.org/http-extensions/client-hints.html#viewport-width)
  • [Width](http://httpwg.org/http-extensions/client-hints.html#width)
  • Content-Type值等于下列之一
    - text/plain
    - multipart/form-data
    - application/x-www-form-urlencoded

note: HTTP HEAD 方法 请求资源的头部信息, 并且这些头部与 HTTP GET 方法请求时返回的一致. 该请求方法的一个使用场景是在下载一个大文件前先获取其大小再决定是否要下载, 以此可以节约带宽资源.

附带身份凭证的请求

如何发送


一般而言,对于跨域请求,浏览器不会发送身份凭证信息。

XMLHttpRequestwithCredentials 标志设置为 true,可发送cookies。

服务器端的响应携带 Access-Control-Allow-Credentials: true ,浏览器才会把响应内容返回给请求的发送者。

注意

可以使用它来向其它的window对象发送消息 // ??
对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为*。需要指定源。
适用一般性第三方 cookie 策略。

postMessage


受同源政策的下限制iframe不能访问非同源主窗口的window对象的大多属性,postMessage例外。

发送

domain1网页向不同源的iframe发消息

  const iframe = document.querySelector('iframe')
  iframe.contentWindow.postMessage('ahhhh !!! from out', 'http://domain2.com')


domain2 iframe向不同源的主窗口发消息

    window.parent.postMessage('wooo !!! from iframe', 'http://domain1.com')

接收消息


处理前注意验证来源。如比较origin。

window.addEventListener("message", event => {
        const origin = event.origin || event.originalEvent.origin
        console.log(origin)
        if (origin === 'http://xxxxx.com') {
            console.log('验证通过')
            console.log('ohh! data: ', event.data)
            //do somethong
        }

    })

其他

window.name + iframe跨域

窗口window.name值在不同的页面(不同域)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

  1. 调用函数加载其他域的iframe,
  2. onload:
    1. 得到其他域的数据,在其他域的脚本中,会把数据放在window.name
    2. iframe跳转回原域
    3. 拿到window.name, 如在iframe.onload中使用callback传递window.name。
  3. 再销毁iframe
var state = 0;
var iframe = document.createElement('iframe');

// 加载跨域页面
iframe.src = url;

// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
iframe.onload = function () {
    if (state === 1) {
        // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
        callback(iframe.contentWindow.name);
        destoryFrame();

    } else if (state === 0) {
        // 第1次onload(跨域页)成功后,切换到同域代理页面
        iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
        state = 1;
    }
};

降域


二级域名相同的情况下,比如a.test.com和 b.test.com,只需要给页面添加 document.domain = 'test.com'表示二级域名都相同就可以实现跨域。

参考

segmentfault.com/a/119000001…

developer.mozilla.org/zh-CN/docs/…