使用子域时防止预检选项
Posted
技术标签:
【中文标题】使用子域时防止预检选项【英文标题】:Prevent preflight OPTIONS when using sub domains 【发布时间】:2015-10-06 17:24:59 【问题描述】:给定两个子域:
web.mysite.com
和api.mysite.com
当前从web.
向api.
发出任何请求都会导致发出预检OPTIONS 请求。如果它不在中国的请求中增加额外的 600 毫秒,这将不是什么大问题。
有人告诉我,在 JS 中设置 document.domain = 'mysite.com';
可以解决问题,但这根本没有帮助。
是否可以/如何在发送到不同的子域时禁用 OPTIONS 请求。
【问题讨论】:
执行 JSONP 请求会解决这个问题吗?我现在无法尝试,但可能值得研究? jsonp 将删除预检,但是,如果它不支持 JSONP,则可能会导致 api 本身出现问题。用 jsonp 做 PUT 和 POST 请求有点困难。 我们主要是想解决这个问题,只针对 GET。我想知道如果发送带有凭据而不是在标头中的 cookie 是否会有所帮助,那么就不会有自定义标头。 我认为 cookie 仍然会触发它(但无论如何值得一试)。如果只有 GET,jsonp 可能是最好的选择,如果你的 api 可以支持的话。 @KevinB 我通过创建一个 iframe 并为两者加载相同的域,然后将窗口从 iframe 拉回父级并使用它来创建 XMLHttpRequest 对象,从而使其工作。然后我可以在父节点上调用它,所有请求不再包含 OPTIONS 请求。有机会我会详细写出来的。 【参考方案1】:使用 iframe
技术解决了这个问题,这似乎是 Facebook / Twitter 所做的。
以下步骤:
1) 将document.domain
设置为根域。所以给定 url http://site.mysite.com/
我在 javascript 中设置域,如 document.domain = 'mysite.com';
2) 设置一个 iframe,从 API 域中提取 html 文件。
<iframe id="receiver" src="http://api.mysite.com/receiver" style="position:absolute;left:-9999px"></iframe>
这是设置为不可见的位置。
3)设置接收页面的HTML设置域:
<!DOCTYPE html><body><script>document.domain='mysite.com'</script></body></html>
4) 向 iframe 添加了 onload
事件,以便在加载后捕获窗口。
onload="window.tempIframeCallback()"
5) 将子窗口分配给一个变量。
window.tempIframeCallback = function()
window.childWindow = window.receiver.contentWindow;
6) 从 childWindow 而不是主窗口创建XMLHttpRequest()
。
var xhr = new window.childWindow.XMLHttpRequest();
现在所有请求都将在没有预检 OPTIONS
请求的情况下发送。
7) 使用jQuery时,也可以在设置中设置xhr的来源:
$.ajax(
...
xhr: function()
return new window.childWindow.XMLHttpRequest();
);
【讨论】:
非常感谢您提出这个想法!对于未来的读者,这也适用于fetch
API,使用window.childWindow.fetch()
而不是window.fetch()
。如果没有像这样的骇人听闻的变通方法,Web 标准无法适应这种情况,真是太疯狂了……
目前(2016 年 12 月)尝试此解决方案的任何人都应该知道,如果 API 域不是网站本身所在域的子域,这将不起作用。例如。如果您的网站托管在 www.example.org 上,则 api.example.org 之类的内容将不起作用,并且浏览器将拒绝设置 document.domain
。
@AdamReis 它工作正常。您需要同时设置 iframe 和网站以使用 example.org
当您不使用***域,但使用子域(例如 app.example.org)然后尝试连接到另一个子域(例如 api.example .org),那么这将是不允许的。 Chrome 会拒绝让你更改document.domain
。相反,您可以使用允许的 api.app.example.org。在我们的应用中,我们使用子域来识别租户,所以我们不能简单地使用顶点域。
@adamreis 我不相信它曾经与跨子域的设置一起工作。它必须是下面的域。无论如何,我现在使用 AWS api 网关并将 /api 路由到 api 并将其他所有内容路由到 s3 并且根本不再处理这个问题。【参考方案2】:
作为对@Phill 的回答值得称赞的补充,这里是最终的 html 代码,它还公开了 iframe 的 fetch
函数:
<!DOCTYPE html>
<html><body>
<script>
document.domain = 'mysite.com';
window.setupAPI = function()
var receiverWindow = window.receiver.contentWindow;
// you may also want to replace window.fetch here
window.APIfetch = receiverWindow.fetch;
// same thing, you may as well replace window.XMLHttpRequest
window.APIXMLHttpRequest = receiverWindow.XMLHttpRequest;
</script>
<iframe id="receiver"
src="http://api.mysite.com/receiver"
style="position:absolute;left:-9999px"
onload="window.setupAPI()"></iframe>
</body></html>
当然 HTML "http://api.mysite.com/receiver" 应该检索:
<!DOCTYPE html>
<html><body><script>
document.domain='mysite.com';
</script></body></html>
然后,在您的 JS 代码中,您现在可以像使用 fetch
和 XMLHttpRequest
一样使用 APIfetch
和 APIXMLHttpRequest
...等等,无论方法和内容类型如何,都不再需要预检请求用过!
【讨论】:
我认为这不再起作用(或永远不会)因为这个错误(Chrome):“Uncaught DOMException: Blocked a frame with origin 'www.example.org' from access a cross-origin框架。”我认为这是一个 CSP (***.com/a/25098153/10088259) 问题,不是吗?【参考方案3】:这是一个全 javascript 方法:
document.domain = 'mysite.net';
var apiIframe = document.createElement('iframe');
apiIframe.onload = function()
window.XMLHttpRequest = this.contentWindow.XMLHttpRequest;
;
apiIframe.setAttribute('src', API_URL + '/iframe');
apiIframe.style.display = 'none';
document.body.appendChild(apiIframe);
其中 API_URL + '/iframe' 返回:
<!DOCTYPE html><body><script>document.domain = 'mysite.net'</script></body></html>
【讨论】:
我认为这不再起作用(或永远不会)因为这个错误(Chrome):“Uncaught DOMException: Blocked a frame with origin 'www.example.org' from access a cross-origin框架。”我认为这是一个 CSP (***.com/a/25098153/10088259) 问题,不是吗?以上是关于使用子域时防止预检选项的主要内容,如果未能解决你的问题,请参考以下文章
防止在 asp.net 5 (vNext) 中对预检 OPTIONS 请求进行基于令牌的授权