同源战略和跨域

在前端开辟的过程当中,我们常常遇到”跨域”的题目,以下的文章将枚举一下我在工作中遇到的跨域题目。
以及稍稍的讨论一下为何会有”跨域”题目的涌现,和所谓的”同源战略”

同源战略

1. 汗青

1995 年由 Netscape 公司提出,以后被其他阅读器厂商采用。

同源战略只是一个范例,并没有指定其详细的运用局限和完成体式格局,各个阅读器厂商都针对同源战略做了本身的完成。

一些 web 手艺都默许采取了同源战略,这些手艺局限包含但不限于Silverlight, Adobe Flash, Adobe Acrobat, Dom, XMLHttpRequest

2. 定义

Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of
URI scheme,
hostname, and
port number.

推断同源的三个要素:

  • 雷同的协定
  • 雷同的域名
  • 雷同的端口号

3. 存在的意义

为了保证运用者信息的平安,防备歹意网站改动用户数据

举个例子:

假定没有同源战略,那末我在A网站下的cookie就可以够被任何一个网站拿到;那末这个网站的一切者,就可以够运用我的cookie(也就是我的身份)在A网站下举行操纵。

同源战略能够算是 web 前端平安的基石,假如缺乏同源战略,阅读器也就没有了平安性可言。

4. 限定局限

非同源的网站之间

  • 没法同享 cookie, localStorage, indexDB
  • 没法操纵相互的 dom 元素
  • 没法发送 ajax 要求
  • 没法经由过程 flash 发送 http 要求
  • 其他

跨域

同源战略做了很严厉的限定,然则在实际的场景中,又确切有许多处所需要打破同源战略的限定,也就是我们常说的跨域

1. cookie

同源战略最早被提出的时刻,为的就是防备差别域名的网页之间同享 cookie,然则假如两个网页的一级域名是雷同的,能够经由过程设置 document.domain来同享 cookie。

举个例子,
https://market.douban.comhttps://book.douban.com,这两个网页的一级域名都是 douban.com,假如我在 market.douban.com中实行了

    document.domain = 'douban.com'
    document.cookie = 'cross=yes'
    或
    document.cookie = 'cross=yes;path=/;domain=douban.com'

如许设置了 cookie 以后,在 book.douban.com 中是能够取到这个 cookie 的。

除了在前端设置以外,也能够直接在 response 里将 cookie 的 domain 设置成 .douban.com

2. Ajax

在运用 ajax 的过程当中,我们遇到的同源限定的题目是最多的。

针对 ajax ,我们有三种体式格局能够绕过同源战略的限定:

2.1 设置 CORS

设置 cross-domain 是现在在 ajax 中最经常使用的一种跨域的体式格局,比拟jsonpwebsoket也是最平安的一种体式格局。

唯一美中不足的是低版本的阅读器支撑的不是很好

IE ✘ 5.5+ ◒ 8+² ◒ 10+¹ ✔ 11

Edge ✔

Firefox ✘ 2+ ✔ 3.5+

Chrome ◒ 4+¹ ✔ 13+

Safari ✘ 3.1+ ◒ 4+¹ ✔ 6+³

Opera ✘ 9+ ✔ 12+

¹Does not support CORS for images in <canvas>

²Supported somewhat in IE8 and IE9 using the XDomainRequest object (but has limitations)

³Does not support CORS for <video> in <canvas>: https://bugs.webkit.org/show_…

2.1.1 CORS 的运作

CROS 的设置,大部份是需要在效劳端举行设置,在效劳端设置之前,先来看一下 CROS 在阅读器中是怎样运作的:

起首,在阅读器中,http 要求将被分为两种 简朴要求(simple request)非简朴要求(not-so-simple request)

简朴要求的推断包含两个前提:

  1. 要求要领必需是一下几种:

    • HEAD
    • GET
    • POST
  2. HTTP 头只能包含以下信息:

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type: 只限于[application/x-www-form-urlencoded, multipart/form-data, text/plain]

不能同时满足以上两个前提的,就都视作非简朴要求

2.1.2 简朴要求(simple request)

阅读器端

阅读器在处置惩罚简朴要求时,会在 Header 中加上一个 origin(protocal + host + path + port) 字段,来标明这个要求是来自那里。

在 CROS 要求中,默许是不会照顾 cookie之类的用户信息的,然则不照顾用户信息的话,是没办法推断用户身份的,所以,能够在要求时将withCredentials设置为 true, 比方:

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

设置了这个值以后,在效劳端会将 response 中的 Access-Control-Allow-Credentials 也设置为 true,如许阅读器才会相应 cookie

效劳端

在效劳端拿到这个要求以后,会对 origin 举行推断,假如是在许可局限内的要求,将会在 respones 返回的 Header 中加上:

    Access-Control-Allow-Origin: origin
    Access-Control-Allow-Credentials: true
    Access-Control-Expose-Headers: something

下面来说说这几个字段都代表什么:

  • Access-Control-Allow-Origin

    看名字也许就可以猜出来,这个就是通知阅读器,效劳端接收那些域名的接见。值能够是 request 中的 origin,也能够是 *,也能够是originA | originB 如许的情势,然则现在看来,在阅读器中只支撑单一值和*两种体式格局。详细能够参考这里:access-control-allow-origin-response-header

  • Access-Control-Allow-Credentials

    从名字上来看,这个字段标清楚明了是不是具有用户相干的权限。

    在阅读器中,详细表现为是不是能够发送 cookie。这个值能够选择性返回,假如不返回的话,默许就 是不许可发送 cookie,假如返回,则只能返回 true。

    别的,假如这个值被设为了true,那末Access-Control-Allow-Origin就不能被设置为 *,必需要显现指定为origin的值;而且返回的cookie由于是在被跨域接见的域名下,由于恪守同 源战略,所以在origin网页中是不能被读取到的。

  • Access-Control-Expose-Headers

    从字面意义上来看,这个字段返回的就是其他可被返回的数据。

    之所以会有这个字段,是由于在简朴要求中,response返回的头信息中,阅读器只能拿到以下几个基础字段:Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma

    假如想要拿到更多的分外信息,只能在Access-Control-Expose-Headers里设置,比方:

         Access-Control-Expose-Headers: "Foo=foo"

    如许的话,在阅读中,就可以够猎取 Foo 这个字段所照顾的信息了

2.1.3 非简朴要求(not-so-simple request)

简朴要求最大的差别在于,非简朴要求实际上是发送了两个要求。

预要求

起首,在正式要求之前,会先发送一个预要求(preflight-request),这个要求的作用是尽量少的照顾信息,供效劳端推断是不是相应当要求。

阅读器

阅读器发送预要求,要求的 Request Method 会设置为 options

别的,还会带上这几个字段:

  • Origin: 同简朴要求origin
  • Access-Control-Request-Method: 要求将要运用的要领
  • Access-Control-Request-Headers: 阅读器会分外发送哪些头信息
效劳端

效劳端收到预要求以后会依据request中的origin,Access-Control-Request-MethodAccess-Control-Request-Headers推断是不是相应当要求。

假如推断相应这个要求,返回的response中将会照顾:

  • Access-Control-Allow-Origin: origin
  • Access-Control-Allow-Methods: like request
  • Access-Control-Allow-Headers: like request

假如否认这个要求,直接返回不带这三个字段的response就可以够,阅读器将会把这类返回推断为失利的返回,触发onerror要领

正式相应

假如预要求被准确相应,接下来就会发送正式要求,正式要求的request和一般的 ajax 要求基础没有区分,只是会照顾 origin 字段;response简朴要求一样,会照顾上Access-Control-*这些字段

2.2 websocket

websocket 不遵照同源战略。

然则在 websocket 要求头中会带上 origin 这个字段,效劳端能够经由过程这个字段来推断是不是需要相应,在阅读器端并没有做任何限定。

2.3 jsonp

jsonp 实在算是一种 hack 情势的要求。

jsonp 的实质实际上是要求一段 js 代码,是对静态文件资本的要求,所以并不遵照同源战略。然则由于是对静态文件资本的要求,所以只能支撑 GET 要求,关于其他要领没有办法支撑。

3. iframe

3.1 iframe 中的同源战略

依据同源战略的划定,假如两个页面差别源,那末相互之间实际上是断绝的。

在运用 iframe 的页面中,虽然我们能够经由过程iframe.contentWindow,window.parent,window.top等要领拿到window对象,然则依据同源战略,阅读器将对非同源的页面之间的windowlocation对象增加限定

差别源的两个网页将不能:

  • 操纵相互的 dom
  • 猎取/挪用相互 window 对象中的属性/要领

差别源的两个网页能够:

  • 转变父/子级的 url

详细的划定规矩能够参考这里:integration-with-idl

然则在实际天下中,有许多场景下,实际上是需要两个非同源的 iframe 之间举行“跨域”操纵的。为了完成这类“跨域”,我们借用了以下几种要领:

  • 片断标识符(fragment identifier)
  • 运用 window.name
  • 跨文档通讯

3.2 运用片断标识符(fragment identifier)

片断标识符指的就是 url 中 # 以后的部份,也就是我们常说的 location.hash
运用片断标识符依托于以下几个症结点:

  1. 转变 url 里的这个部份,是不会触发页面的革新的
  2. 父级页面虽然不能操纵 iframe 中的 windowdom,然则能够转变 iframe 的 url
  3. window 对象能够监听 hashchange 事宜

经由过程这几个症结点,能够完成基于 hashchange 来操纵页面

3.3 运用 window.name

window.name这个属性最厉害的处所在于,window对象没有转变的话,这个 window 跳转的网页,都读取 window.name 这个值。

比方,A 网页设置了 window.name,然后跳转到了 B 网页,然则 B 网页中,依旧能够读取到 A 设置的 window.name

经由过程这个特征,在 iframe 中,子页面能够先设置 window.name

然后跳转到一个跟父页面同级的地点,这个 window.name 依旧存在,由于已调到了跟父级页面同源的地点中,所以父页面能够猎取到 iframe.contentWindow中属性,也就是能够读取到 window.name

这类要领最大的长处就是window.name能够传一个很长的字符串,然则瑕玷也比较显著,就是需要在父级页面不断的去搜检子页面的window.name是不是被转变

3.4 跨文档通讯API(Cross-document messaging)

虽然上面的两种要领都能够完成差别源页面之间的通讯,然则总归是属于hack的要领,眼看着人人对非同源页面的通讯都有需求,所以在 HTML5 范例中,增加了一个window.postMessage的要领。

经由过程这个要领,能够轻易的完成差别源的页面之间的通讯。

看一个简朴的例子:


// Page Foo
iframe.contentWindow.postMessage('Hello from foo', '/path/to/bar')


// Page Bar
window.parent.addEventListener('message', function (e) {
    console.log(e.source)    // 发送音讯的窗口
    console.log(e.origin)  // 音讯发向的网址
    console.log(e.data)    // 音讯内容
})

2.6 canvas

canvas 的运用过程当中,也会遇到同源战略的限定。

以下的几种操纵,都邑遭到同源战略的限定:

  • canvas.toDataURL
  • canvas.toBlob
  • canvas.getContent(‘2d’).getImageData(x,y,w,h)

比方:


// 这段 JS 运转在 a.com 这个域名下
var canvas = document.createElement('canvas')
var ctx = canvas.getContent('2d')
var src = 'http://b.com/path/to/a/image'
var img = new Image()
img.onload = function () {
    canvas.with = img.style.width
    canvas.height = img.style.height
    ctx.drawImage(img)
    // 以下的这这三种操纵都邑报错
    canvas.toDataURL('image/jpg')
    canvas.toBlob(function () {})
    ctx.getImageData(0, 0, 10, 10)
}
img.src = src

运转时会报错

Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

能够看到是toDataURL的时刻,由于 a.comb.com是差别源的两个网页,触发了同源战略的限定。换成toBlobgetImageData会报一样的毛病。

我们来探讨以下报这个毛病的缘由:

起首,一切bitmaps范例的对象,在被canvasImageBitmap运用时,都邑先搜检当前这对象,是不是是处在origin clean的状况。

然后,一切bitmaps范例的对象,默许情况下,这个origin clean都是true,然则假如这个bitmaps被跨域挪用,那末,这个origin clean将会被设置成 false

再然后,在运用toDataURL,toBlobgetImageData时,都邑先搜检origin clean,假如为 false 的话,就会抛出SecurityError如许的非常。

那末,这个origin clean的状况,是怎样设置的呢?

能够经由过程crossOrigin来设置,看代码:

var canvas = document.createElement('canvas')
var ctx = canvas.getContent('2d')
var src = 'http://b.com/path/to/a/image'
var img = new Image()
img.onload = function () {
    canvas.with = img.style.width
    canvas.height = img.style.height
    ctx.drawImage(img)
    canvas.toDataURL('image/jpg')
}
img.crossOrigin = '*'
img.src = src

加上了crossOrigin这个属性,然后实行,发明还会报个错:

Image from origin 'http://b.com' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access

看报错信息也许能够晓得,是Access-Control-Allow-Origin这里出了题目,只需要把Access-Control-Allow-Origin设置成对应的值就可以够了。

更详细的缘由能够参考这里:Security with canvas elements

2.7 flash

flash在举行 HTTP 要求时,也遵照同源战略。

然则比拟较以上的种种场景和绕过同源战略的要领,flash 的跨域要求设置很轻易,只需要在目的效劳的根目录下设置一个crossdomain.xml文件即可。

这个文件中会划定哪些域能够接见当前效劳,看一个实在天下里的例子:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy
 SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <site-control permitted-cross-domain-policies="master-only"/>
  <allow-access-from domain="t.simple.com"/>
  <allow-access-from domain="img1.simple.com"/>
  <allow-access-from domain="img2.simple.com"/>
  <allow-access-from domain="img3.simple.com"/>
  <allow-access-from domain="img4.simple.com"/>
  <allow-access-from domain="img5.simple.com"/>
  <allow-access-from domain="*.simple.com.cn"/>
  <allow-access-from domain="all.vic.sina.com.cn"/>
</cross-domain-policy>

参考文章:

    原文作者:tobeyouth
    原文地址: https://segmentfault.com/a/1190000007366644
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞