65.9K
CodeProject 正在变化。 阅读更多。
Home

浏览器跨域解释

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2013 年 6 月 24 日

CPOL

8分钟阅读

viewsIcon

30781

整合我在网络世界中找到的关于跨域交互的所有信息

引言

这是一个关于网页中各种跨域问题的资源汇总。作为一名网页开发者,您经常需要研究这类问题:跨域 iframe 通信、JSONP、CORS 等等,但我希望有一个统一的资源来整合我找到的所有信息。请随时留下评论,纠正本文中的任何错误或补充我遗漏的内容。

页面中的框架交互

使用 JavaScript 访问另一域的 iframe 中的 DOM

在网页中,当父窗口包含一个指向另一个域的子 iFrame 时,您完全无法访问该子 iframe 的内容。反过来,子 iframe 也无法访问父窗口的任何内容。我所说的“内容”是指使用 document.getElementId('frameid').forms[0] 等方式来获取对表单等元素的引用。只有一个例外:如果两个不同域仅在子域名上有所不同。您可以设置框架的域为主域,然后框架就可以相互访问内容。

如果框架在同一域上,那么框架可以相互访问内容。要做到这一点,您通常需要通过 getElementId('frameid') 获取对 iframe 元素的引用。然后,有两种方法可以访问 iframe 中的实际窗口对象:IE8 之前的旧方法和标准方法。如下:

function getIframeWindow(iframeElement){
    return iframeElement.contentWindow || iframeElement.contentDocument.parentWindow;
} 

contentWindow 是 IE8 之前的浏览器提供的。contentDocument.parentWindow 是所有现代浏览器提供的。然后,您可以使用该引用来操作 iframe 的 DOM,就像操作主窗口一样。

不同域之间的框架通信

因此,在两个不同域的框架之间唯一有用的交互就是通信,一个框架向另一个框架发送消息。有很多方法可以实现这一点,但关键是两个域都必须了解通信协议。否则,一个框架会发送消息给另一个框架,但消息会被忽略;或者一个框架会监听一个永远不会发送的消息。您不能指示一个域中的 iframe 向一个任意域中可能不了解协议的 iframe 发送消息,并期望有什么会发生,它不会。

现代浏览器通过 HTML5 特有的 postMessage 轻松解决了这个问题。我不需要在这里详细描述,这是一个非常简单的概念,在这个 链接 中有清晰的定义。

旧浏览器需要使用利用跨域框架限制的一些漏洞的技巧。这些技术基于不同域框架可以交互的唯一属性。

  1. URL 轮询 - 一个框架可以修改另一个域的 src 属性,但不能读取它。一个框架可以将其另一个框架的 src 修改为不同的哈希值 (#)。目标框架然后必须使用计时器轮询自己的 URL,以查看是否发生了更改,然后对该信息执行相应操作。
  2. Name (名称) - 一个框架可以修改框架的 Name 属性。
  3. Resizing (调整大小) - 您可以调整框架的大小,目标框架可以注册其 onresize 事件,以了解它应该检查其 URL 哈希值以获取消息。
  4. 代理 (Proxy) - 您可以拥有两个来自不同域的子框架,一个隐藏起来用于注册 resize 事件并读取其新哈希值作为消息。然后,它会将此信息发送到其同一域中的另一个可见框架。

自己实现任何这些解决方案都没有意义。市面上有许多库已经提供了这些功能。

使用 iframe 的目的是什么?
  1. 嵌入沙箱内容。父框架无法修改其他域框架的内容。
  2. 设置 Cookie。子框架是您可以在主页(域 A)中设置来自域 B 的 Cookie 的唯一方式。相比之下,在域 A 中包含的来自域 B 的 JavaScript 文件无法为域 B 设置 Cookie。在 Internet Explorer 中需要额外的处理。请阅读此文
  3. 发送 Cookie。框架默认会发送与其域相关的 Cookie,您无需像 Ajax 那样特别告知。
  4. 下载 PDF 或 Excel 电子表格等内容,而不会影响主窗口。

与不同域服务器的通信

当主网页中的一个域向另一个域的服务器发出 HTTP 请求时,规则仍然适用:两个域都必须了解通信协议。如果两个域不就发送和接受特定的消息格式达成一致,则什么也无法发生。例如:您无法使用以下任何技术向 http://www.google.com(主页)发出请求并使用收到的信息,因为 www.google.com 没有被编程来识别任何特定发出请求的客户端。

表单提交 (Form Posting)

从网站内部向服务器发送请求的最基本方法是通过表单提交。通过 JavaScript,您可以编程方式地向当前父窗口添加一个 iframe。在该 iframe 中,您可以添加一个指向另一个域的表单。在该表单中,您可以添加一系列隐藏输入字段,这些字段代表您希望发布到该域的信息。然后,您可以提交表单,这将向该域发出一个 POST 请求。

这有一个小问题。当 iframe 收到来自另一个域的响应时,跨站限制就会生效,您将无法再访问该框架以获取发送回的任何信息。如果您只需要发送信息,这不成问题。如果您需要使用收到的信息,您需要使用上面在“不同域之间的 iframe 通信”中描述的技术之一。但如果刚刚提交到的域不认识您作为客户端,这些技术都将无效,您就无能为力了。请阅读此文

JavaScript (JSONP)

图片标签允许进行跨域请求,但问题是您除了显示图片之外无法对响应做任何事情。脚本标签也是浏览器可以发出的、没有跨域限制的请求之一,但幸运的是您可以对响应做一些事情。那么它的优势是什么?首先,它在所有浏览器中都一致且可靠。此外,即使请求是针对另一个域的,Cookie 也会自动随请求一起发送。您可以通过访问任何网站并在 Webkit 网络选项卡中检查脚本标签的请求来观察这一点。这是当我请求 codeproject.com 时我看到的内容(您可能需要放大)。

Click to enlarge image

主请求是 http://codeproject.com。该页面包含一个指向 ad.doubleclick.net 域的脚本。您可以看到请求头包含了之前不知从哪里设置的 Cookie。

这有什么用?您可以利用它向其他域发出请求并从它们那里接收信息,类似于 Web 服务 API。同样,关键是两个域必须相互了解并就通信格式达成一致。这可以通过一种有时被称为 JSONP(P = padding,填充)的技术来实现。您可以在您的域中包含一个脚本标签,例如

<script src='http://www.otherdomain.com?getinformation=true&parameter=1&parameter=2></script>
<script>
function doSomethingWithResponse(data){
alert(data);
}
</script>   

同样,远程服务器域需要了解它将收到此类请求,以便它可以发送合适的响应,如 JavaScript 代码,例如(原始 JavaScript)

doSomethingWithResponse({thing:"stuff",information:"importantinformation"}); 

通常,您不需要自己实现这一点。大多数 JavaScript 库都提供了实现此功能的功能。例如,使用 Jquery,您可以这样做:

 $.ajax({
   type: 'GET',
    url: url,
    async: false,
    jsonpCallback: 'jsonCallback',
    contentType: "application/json",
    dataType: 'jsonp',
    success: function(json) {
       console.dir(json.sites);
    },
    error: function(e) {
       console.log(e.message);
    }
}); 

http://www.jquery4u.com/json/jsonp-examples/
JSONP 的限制在于它只能进行 GET 请求,而不能进行 POST 请求。无法让浏览器为脚本标签发出 POST 请求。另外,如上所述,来自另一个域的脚本文件无法为该域设置 Cookie。

Ajax

Ajax 允许您从浏览器发起异步 HTTP 请求。但这项技术在使用跨域通信时是最挑剔的。默认情况下,您不能向与发起请求的窗口不同的域发出 Ajax 请求。要做到这一点,您必须使用一种称为 CORS 的技术。这是一个官方标准,在所有浏览器中的一致性越来越高。我不会在此深入介绍,有很多文章对此进行了说明,例如这篇

同样,这里也适用同样的原则。客户端和服务器都必须彼此非常了解,才能建立通信通道。客户端必须获得服务器的批准,服务器可以全部批准所有客户端,或仅批准特定客户端(通过域)。服务器还必须提供客户端了解的某种结构化、一致的响应,以便客户端可以使用它。客户端不能任意地向服务器发出 CORS Ajax 请求并期望获得有意义的响应。Ajax CORS 相对于 JSONP 的优势在于它可以进行 POST 请求。此外,Ajax CORS 能够发送请求域的 Cookie,但这必须通过一个设置(在文章中描述)专门启用。

© . All rights reserved.