使用子域时防止预检选项

Posted

技术标签:

【中文标题】使用子域时防止预检选项【英文标题】:Prevent preflight OPTIONS when using sub domains 【发布时间】:2015-10-06 17:24:59 【问题描述】:

给定两个子域:

web.mysite.comapi.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 代码中,您现在可以像使用 fetchXMLHttpRequest 一样使用 APIfetchAPIXMLHttpRequest ...等等,无论方法和内容类型如何,都不再需要预检请求用过!

【讨论】:

我认为这不再起作用(或永远不会)因为这个错误(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) 问题,不是吗?

以上是关于使用子域时防止预检选项的主要内容,如果未能解决你的问题,请参考以下文章

防止机器人索引受限访问子域

Chrome中的慢速cors预检选项请求[关闭]

防止在 asp.net 5 (vNext) 中对预检 OPTIONS 请求进行基于令牌的授权

使用 JustValidate 远程选项时如何防止显示远程错误消息?

关闭选项卡时如何防止调试器停止

防止JavaFX TabPane在滑动时切换选项卡