如何在内容脚本中进行跨源请求(即使使用正确的CORS头也会被CORB阻止)?

38

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

Cross-Origin Read Blocking (CORB) blocked cross origin response http://localhost:3000/api/users/1 with MIME type application/json

根据Chrome关于CORB的文档,如果以下所有条件都成立,则CORB将阻止请求的响应:

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

  2. 服务器响应带有X-Content-Type-Options: nosniff头部,或者如果省略此头部,Chrome通过检查文件确定内容类型为HTML、XML或JSON

  3. CORS未明确允许访问该资源

此外,根据"Spectre和Meltdown的教训"(Google I/O 2018),似乎将mode:cors添加到fetch调用中可能很重要,即fetch(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 阻止?


2
请在谷歌特定扩展文章中查看解决方案,该文章与其他文章不同。 - wOxxOm
1
我认为你发表一个答案可能会更好 - 也许包含一些你认为相关的额外细节 - 因为你比我更了解这个。我只知道这篇文章,而不是具体内容。 - wOxxOm
1
请参考以下链接:https://dev59.com/K7Lma4cB1Zd3GeqPb4OZ 和 https://dev59.com/JLLma4cB1Zd3GeqPb4OZ。 - sideshowbarker
3
虽然使用背景页就足以解决这个问题,但我还是不明白为什么Chrome会阻止我的扩展程序发送请求。 "Chrome扩展程序内容脚本中的跨域请求更改"文章写道,“为了缓解这些问题,未来版本的Chrome将限制内容脚本与页面本身可以执行的相同获取。” 这表明从扩展程序进行跨源请求仍然是可能的,但它们必须遵循CORS。既然我已经在响应中添加了CORS头,我的请求不应该成功吗? - Ceasar
1
我也对此很感兴趣。启用NetworkService的Chrome 73似乎不会为从内容脚本发出的xhr请求进行CORS预检请求,即使该请求需要CORS并且如果从主机页面发出将触发预检请求。这可能是Chrome的一个bug吗?根据文档,他们的意图是使内容脚本“受到与其运行的页面相同的请求规则的约束”。如果从页面发出的跨源请求会触发预检,但从内容脚本发出的请求却不会,那似乎就违反了这个意图。 - Simon Woolf
显示剩余3条评论
3个回答

26

基于"Chrome扩展程序内容脚本中跨域请求的更改"中的示例,我用一个新方法fetchResource替换了所有对fetch的调用,该方法具有类似的API,但将fetch调用委托给后台页面:

// 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
我不确定在重建Response对象时是否使用response.text()new Blob([response.body])是最忠实的方式,但在我的应用程序中它可以工作,因为我只处理JSON响应。我也不确定如何通过传递headers。 - Ceasar
3
针对第三方组件自己发起 API 调用,我们能做些什么呢? :/ - Roman Scher
1
很好,可复制粘贴的代码。顺便说一句,你应该接受这个答案。 - Michael Yaworski
不知道为什么,但自从Chrome 81版本以后似乎不再是这种情况了。背景脚本似乎被禁止使用CORS进行fetch调用。 - avalanche1
通过这个更改,我遇到了这个错误:未经检查的 runtime.lastError: 在接收到响应之前,消息端口已关闭。有什么想法如何解决吗? - Persistent Plants
你可以使用 Object.fromEntries(response.headers) 获取响应头。 - Bouh

12
请参见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.
    }
  });

请在manifest.json文件中允许URL (更多信息):

  • ManifestV2 (经典版): "permissions": ["https://another-site.com/"]
  • ManifestV3 (即将推出): "host_permissions": ["https://another-site.com/"]

-3

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

在Linux上的示例运行命令。

对于Chrome:

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

对于Chromium:

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

类似问题。

来源


请务必不要告诉用户这样做。你需要开启安全性。我只在其他人同时努力解决CORB问题的情况下进行开发测试时才使用它。 - justdan23

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接