如何在内容脚本中发出跨域请求(尽管 CORS 标头正确,但当前仍被 CORB 阻止)?

How to make a cross-origin request in a content script (currently blocked by CORB despite the correct CORS headers)?

提问人:Ceasar 提问时间:3/18/2019 最后编辑:wOxxOmCeasar 更新时间:3/21/2023 访问量:75522

问:

我正在开发一个 Chrome 扩展程序,该扩展程序从某些网站向我控制的 API 发出请求。在 Chrome 73 之前,该扩展程序可以正常工作。升级到 Chrome 73 后,我开始出现以下错误:

跨域读取阻止 (CORB) 阻止了 MIME 类型为 application/json 的跨域响应 http://localhost:3000/api/users/1

根据 Chrome 在 CORB 上的文档,如果满足以下所有条件,CORB 将阻止请求的响应:

  1. 资源是“数据资源”。具体来说,内容类型为 HTML、XML、JSON

  2. 服务器会以标头进行响应,如果省略此标头,Chrome 会通过检查文件检测到内容类型是 HTML、XML 或 JSON 之一X-Content-Type-Options: nosniff

  3. CORS 未显式允许访问资源

此外,根据“Lessons from Spectre and Meltdown”(Google I/O 2018),似乎添加到调用中可能很重要,即 .mode: corsfetchfetch(url, { mode: 'cors' })

为了解决这个问题,我进行了以下更改:

首先,我将以下标头添加到来自 API 的所有响应中:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Origin: https://www.example.com

其次,我更新了对扩展的调用,如下所示:fetch()

fetch(url, { credentials: 'include', mode: 'cors' })

但是,这些更改没有奏效。我可以更改哪些内容以使我的请求不被 CORB 阻止?

google-chrome-extension cors 跨域读取阻止

评论

2赞 wOxxOm 3/18/2019
请参阅特定于扩展程序的 Google 文章中的解决方案,该文章有所不同。
1赞 wOxxOm 3/18/2019
我认为如果你发布一个答案可能会更好 - 也许有一些你认为相关的其他细节 - 因为你对此了解得更多。我只知道这篇文章,不知道具体细节。
1赞 sideshowbarker 3/18/2019
另请参阅 stackoverflow.com/questions/55153960/... 和 stackoverflow.com/questions/55153888/...
3赞 Ceasar 3/19/2019
虽然使用后台页面足以解决问题,但我仍然感到困惑,为什么 Chrome 阻止了我来自扩展程序的请求。“Chrome 扩展内容脚本中对跨域请求的更改”一文写道,“为了缓解这些问题,Chrome 的未来版本会将内容脚本限制为与页面本身可以执行的相同抓取。这向我表明,扩展仍然可以进行跨域请求,但它们必须遵循 CORS。既然我将 CORS 标头添加到我的响应中,那么我的请求不应该成功吗?
1赞 Simon Woolf 3/19/2019
我也有兴趣得到这个问题的答案。启用了 NetworkService 的 Chrome 73 似乎只是没有对从内容脚本发出的 xhr 请求发出 CORS 预检请求,即使该请求需要 CORS,并且如果从主机页面发出,也会触发预检请求。有可能是Chrome错误吗?根据文档,他们的目的是使内容脚本“受制于与它们运行的页面相同的请求规则”。如果从页面发出的 x-origin 请求触发了预检,但来自内容脚本的请求没有触发,这似乎打破了这一意图

答:

26赞 Ceasar 3/18/2019 #1

根据“Chrome 扩展内容脚本中对跨域请求的更改”中的示例,我将所有调用替换为新方法,该方法具有类似的 API,但将调用委托给后台页面:fetchfetchResourcefetch

// contentScript.js
function fetchResource(input, init) {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage({input, init}, messageResponse => {
      const [response, error] = messageResponse;
      if (response === null) {
        reject(error);
      } else {
        // Use undefined on a 204 - No Content
        const body = response.body ? new Blob([response.body]) : undefined;
        resolve(new Response(body, {
          status: response.status,
          statusText: response.statusText,
        }));
      }
    });
  });
}

// background.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  fetch(request.input, request.init).then(function(response) {
    return response.text().then(function(text) {
      sendResponse([{
        body: text,
        status: response.status,
        statusText: response.statusText,
      }, null]);
    });
  }, function(error) {
    sendResponse([null, error]);
  });
  return true;
});

这是我能够对我的应用程序进行修复该问题的最小更改集。(请注意,扩展和后台页面只能在它们之间传递 JSON 可序列化对象,因此我们不能简单地将 Fetch API 响应对象从后台页面传递到扩展。

后台页面不受 CORS 或 CORB 的影响,因此浏览器不再阻止来自 API 的响应。

警告!必须在 manifest.json 中添加 URL:

  • 对于 ManifestV3"host_permissions": ["*://*.example.com/"]
  • 对于 ManifestV2 中"permissions": ["*://*.example.com/"]

评论

3赞 Ceasar 3/18/2019
我不确定使用 是否是重建 Response 对象的最忠实方法,但它适用于我的应用程序,我只处理 JSON 响应。我也不确定如何传递标头。response.text()new Blob([response.body])
3赞 Roman Scher 3/22/2019
对于进行自己的 API 调用的第三方组件,我们能做些什么:/
1赞 Michael Yaworski 5/31/2019
漂亮的、可复制和粘贴的代码。顺便说一句,你应该接受这个作为答案。
0赞 avalanche1 4/23/2020
不知道为什么,但自 Chrome 81 以来,情况似乎并非如此。CORS 似乎禁止后台脚本进行调用fetch
0赞 Persistent Plants 9/2/2020
通过此更改,我收到此错误:未经检查的runtime.lastError:消息端口在收到响应之前关闭。有什么想法可以解决这个问题吗?
12赞 李方郑 3/22/2019 #2

查看 https://www.chromium.org/Home/chromium-security/extension-content-script-fetches

为了提高安全性,自 Chrome 85 起,Chrome 扩展程序中不允许从内容脚本进行跨域提取。此类请求可以从扩展后台脚本发出,并在需要时中继到内容脚本。

您可以这样做来避免跨源。

旧内容脚本,进行跨域提取:

var itemId = 12345;
var url = "https://another-site.com/price-query?itemId=" +
         encodeURIComponent(request.itemId);
fetch(url)
  .then(response => response.text())
  .then(text => parsePrice(text))
  .then(price => ...)
  .catch(error => ...)

新的内容脚本,要求其后台页面获取数据:

chrome.runtime.sendMessage(
    {contentScriptQuery: "queryPrice", itemId: 12345},
    price => ...);

新的扩展后台页面,从已知 URL 获取并中继数据:

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.contentScriptQuery == "queryPrice") {
      var url = "https://another-site.com/price-query?itemId=" +
              encodeURIComponent(request.itemId);
      fetch(url)
          .then(response => response.text())
          .then(text => parsePrice(text))
          .then(price => sendResponse(price))
          .catch(error => ...)
      return true;  // Will respond asynchronously.
    }
  });

允许 URL in manifest.json更多信息):

  • ManifestV2(经典):"permissions": ["https://another-site.com/"]
  • ManifestV3(即将推出):"host_permissions": ["https://another-site.com/"]
-3赞 Epexa 3/25/2019 #3

临时解决方案:使用运行命令浏览器禁用 CORB --disable-features=CrossSiteDocumentBlockingAlways,CrossSiteDocumentBlockingIfIsolating

Linux 上的示例运行命令。

对于 Chrome:

chrome %U --disable-features=CrossSiteDocumentBlockingAlways,CrossSiteDocumentBlockingIfIsolating

对于 Chromium:

chromium-browser %U --disable-features=CrossSiteDocumentBlockingAlways,CrossSiteDocumentBlockingIfIsolating

类似的问题。

来源

评论

0赞 justdan23 7/27/2021
只要确保你没有告诉用户这样做。您希望开启安全性。我唯一一次使用它是为了开发测试,而其他人同时在努力解决 CORB 问题。