浏览器跨域解释
整合我在网络世界中找到的关于跨域交互的所有信息
引言
这是一个关于网页中各种跨域问题的资源汇总。作为一名网页开发者,您经常需要研究这类问题:跨域 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,就像操作主窗口一样。
- https://w3schools.org.cn/jsref/prop_frame_contentdocument.asp
- http://www.nczonline.net/blog/2009/09/15/iframes-onload-and-documentdomain/
不同域之间的框架通信
因此,在两个不同域的框架之间唯一有用的交互就是通信,一个框架向另一个框架发送消息。有很多方法可以实现这一点,但关键是两个域都必须了解通信协议。否则,一个框架会发送消息给另一个框架,但消息会被忽略;或者一个框架会监听一个永远不会发送的消息。您不能指示一个域中的 iframe
向一个任意域中可能不了解协议的 iframe
发送消息,并期望有什么会发生,它不会。
现代浏览器通过 HTML5 特有的 postMessage
轻松解决了这个问题。我不需要在这里详细描述,这是一个非常简单的概念,在这个 链接 中有清晰的定义。
旧浏览器需要使用利用跨域框架限制的一些漏洞的技巧。这些技术基于不同域框架可以交互的唯一属性。
- http://softwareas.com/cross-domain-communication-with-iframes
- http://ajaxian.com/archives/cross-domain-iframe-communication-without-location-polling
- http://www.shouldersofgiants.co.uk/Examples/CrossDomain/ParentPage.html
- URL 轮询 - 一个框架可以修改另一个域的
src
属性,但不能读取它。一个框架可以将其另一个框架的src
修改为不同的哈希值 (#)。目标框架然后必须使用计时器轮询自己的 URL,以查看是否发生了更改,然后对该信息执行相应操作。 - Name (名称) - 一个框架可以修改框架的
Name
属性。 - Resizing (调整大小) - 您可以调整框架的大小,目标框架可以注册其
onresize
事件,以了解它应该检查其 URL 哈希值以获取消息。 - 代理 (Proxy) - 您可以拥有两个来自不同域的子框架,一个隐藏起来用于注册
resize
事件并读取其新哈希值作为消息。然后,它会将此信息发送到其同一域中的另一个可见框架。
自己实现任何这些解决方案都没有意义。市面上有许多库已经提供了这些功能。
- http://ternarylabs.github.io/porthole/
- http://easyxdm.net/wp/
- https://code.google.com/p/xssinterface/
- http://benalman.com/projects/jquery-postmessage-plugin/
使用 iframe 的目的是什么?
- 嵌入沙箱内容。父框架无法修改其他域框架的内容。
- 设置 Cookie。子框架是您可以在主页(域 A)中设置来自域 B 的 Cookie 的唯一方式。相比之下,在域 A 中包含的来自域 B 的 JavaScript 文件无法为域 B 设置 Cookie。在 Internet Explorer 中需要额外的处理。请阅读此文。
- 发送 Cookie。框架默认会发送与其域相关的 Cookie,您无需像 Ajax 那样特别告知。
- 下载 PDF 或 Excel 电子表格等内容,而不会影响主窗口。
与不同域服务器的通信
当主网页中的一个域向另一个域的服务器发出 HTTP 请求时,规则仍然适用:两个域都必须了解通信协议。如果两个域不就发送和接受特定的消息格式达成一致,则什么也无法发生。例如:您无法使用以下任何技术向 http://www.google.com(主页)发出请求并使用收到的信息,因为 www.google.com 没有被编程来识别任何特定发出请求的客户端。
表单提交 (Form Posting)
从网站内部向服务器发送请求的最基本方法是通过表单提交。通过 JavaScript,您可以编程方式地向当前父窗口添加一个 iframe
。在该 iframe
中,您可以添加一个指向另一个域的表单。在该表单中,您可以添加一系列隐藏输入字段,这些字段代表您希望发布到该域的信息。然后,您可以提交表单,这将向该域发出一个 POST 请求。
这有一个小问题。当 iframe
收到来自另一个域的响应时,跨站限制就会生效,您将无法再访问该框架以获取发送回的任何信息。如果您只需要发送信息,这不成问题。如果您需要使用收到的信息,您需要使用上面在“不同域之间的 iframe 通信”中描述的技术之一。但如果刚刚提交到的域不认识您作为客户端,这些技术都将无效,您就无能为力了。请阅读此文。
JavaScript (JSONP)
图片标签允许进行跨域请求,但问题是您除了显示图片之外无法对响应做任何事情。脚本标签也是浏览器可以发出的、没有跨域限制的请求之一,但幸运的是您可以对响应做一些事情。那么它的优势是什么?首先,它在所有浏览器中都一致且可靠。此外,即使请求是针对另一个域的,Cookie 也会自动随请求一起发送。您可以通过访问任何网站并在 Webkit 网络选项卡中检查脚本标签的请求来观察这一点。这是当我请求 codeproject.com 时我看到的内容(您可能需要放大)。
主请求是 http://codeproject.com。该页面包含一个指向 ad.doubleclick.net 域的脚本。您可以看到请求头包含了之前不知从哪里设置的 Cookie。
这有什么用?您可以利用它向其他域发出请求并从它们那里接收信息,类似于 Web 服务 API。同样,关键是两个域必须相互了解并就通信格式达成一致。这可以通过一种有时被称为 JSONP(P = padding,填充)的技术来实现。您可以在您的域中包含一个脚本标签,例如
<script src='http://www.otherdomain.com?getinformation=true¶meter=1¶meter=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,但这必须通过一个设置(在文章中描述)专门启用。