使用 Reporting API 监控 Web 应用

使用 Reporting API 监控安全违规行为、已弃用的 API 调用等。

某些错误仅在生产环境中发生。您在本地或开发期间不会看到这些问题,因为真实用户、真实网络和真实设备会改变游戏。Reporting API 可帮助捕获其中一些错误,例如您网站中的安全违规行为或已弃用及即将弃用的 API 调用,并将其传输到您指定的端点。

它允许您使用 HTTP 标头声明要监控的内容,并由浏览器运行。

设置 Reporting API 后,您就可以高枕无忧了,因为当用户遇到这些类型的错误时,您会收到通知,以便及时修复。

本文将介绍此 API 的功能和使用方法。让我们开始吧!

概览

总结以下步骤的图表,从报告生成到开发者访问报告
报告的生成和发送方式。

假设您的网站 site.example 具有 Content-Security-Policy 和 Document-Policy。不知道这些功能有什么用?没关系,您仍然可以理解此示例。

您决定监控自己的网站,以便了解何时违反了这些政策,同时也因为您想关注代码库可能正在使用的已弃用或即将弃用的 API。

为此,您需要配置 Reporting-Endpoints 标头,并根据需要在政策中使用 report-to 指令映射这些端点名称。

Reporting-Endpoints: main-endpoint="https://reports.example/main", default="https://reports.example/default"
# Content-Security-Policy violations and Document-Policy violations
# will be sent to main-endpoint
Content-Security-Policy: script-src 'self'; object-src 'none'; report-to main-endpoint;
Document-Policy: document-write=?0; report-to=main-endpoint;
# Deprecation reports don't need an explicit endpoint because
# these reports are always sent to the `default` endpoint

发生了不可预见的情况,导致部分用户违反了这些政策。

违规行为示例

index.html

<script src="script.js"></script>
<!-- CSP VIOLATION: Try to load a script that's forbidden as per the Content-Security-Policy -->
<script src="https://example.com/script.js"></script>

script.js,由 index.html 加载

// DOCUMENT-POLICY VIOLATION: Attempt to use document.write despite the document policy
try {
  document.write('<h1>hi</h1>');
} catch (e) {
  console.log(e);
}
// DEPRECATION: Call a deprecated API
const webkitStorageInfo = window.webkitStorageInfo;

浏览器会生成 CSP 违规报告、Document-Policy 违规报告和弃用报告,以捕获这些问题。

浏览器随后会以较短的延迟时间(不超过 1 分钟)将报告发送到为此违规类型配置的端点。报告由浏览器本身(而不是您的服务器或网站)以带外方式发送。

端点会收到这些报告。

您现在可以访问这些端点上的报告,并监控出现的问题。 您已准备好开始排查影响用户的问题。

示例报告

{
  "age": 2,
  "body": {
    "blockedURL": "https://site2.example/script.js",
    "disposition": "enforce",
    "documentURL": "https://site.example",
    "effectiveDirective": "script-src-elem",
    "originalPolicy": "script-src 'self'; object-src 'none'; report-to main-endpoint;",
    "referrer": "https://site.example",
    "sample": "",
    "statusCode": 200
  },
  "type": "csp-violation",
  "url": "https://site.example",
  "user_agent": "Mozilla/5.0... Chrome/92.0.4504.0"
}

应用场景和报告类型

您可以配置 Reporting API,以帮助您监控整个网站中发生的许多类型的有趣警告或问题:

报告类型 生成报告的情况示例
CSP 违规(仅限 3 级) 您在某个网页上设置了 Content-Security-Policy (CSP),但该网页尝试加载 CSP 不允许的脚本。
COOP 违规 您在页面上设置了 Cross-Origin-Opener-Policy,但跨域窗口正尝试直接与文档互动。
COEP 违规 您已在网页上设置 Cross-Origin-Embedder-Policy,但相应文档包含一个跨域 iframe,该 iframe 尚未选择由跨域文档加载。
违反文档政策 该网页具有禁止使用 document.write 的文档政策,但某个脚本尝试调用 document.write。
违反“权限”政策 网页具有阻止使用麦克风的权限政策,以及请求音频输入的脚本。
弃用警告 该网页正在使用已弃用或即将弃用的 API;它直接调用该 API 或使用顶级第三方脚本调用该 API。
干预 网页尝试执行的操作被浏览器出于安全、性能或用户体验方面的考虑而拒绝。Chrome 中的示例:网页在慢速网络上使用 document.write,或在用户尚未与之互动的跨域框架中调用 navigator.vibrate。
交通事故 浏览器在打开您的网站时崩溃。

报告

浏览器会将报告发送到您配置的端点。它会发送如下所示的请求:

POST
Content-Type: application/reports+json

这些请求的载荷是一个报告列表。

报告列表示例

[
  {
    "age": 420,
    "body": {
      "columnNumber": 12,
      "disposition": "enforce",
      "lineNumber": 11,
      "message": "Document policy violation: document-write is not allowed in this document.",
      "policyId": "document-write",
      "sourceFile": "https://site.example/script.js"
    },
    "type": "document-policy-violation",
    "url": "https://site.example/",
    "user_agent": "Mozilla/5.0... Chrome/92.0.4504.0"
  },
  {
    "age": 510,
    "body": {
      "blockedURL": "https://site.example/img.jpg",
      "destination": "image",
      "disposition": "enforce",
      "type": "corp"
    },
    "type": "coep",
    "url": "https://dummy.example/",
    "user_agent": "Mozilla/5.0... Chrome/92.0.4504.0"
  }
]

您可以在这些报告中找到以下数据:

字段 说明
age 报告的时间戳与当前时间之间的毫秒数。
body 实际报告数据,已序列化为 JSON 字符串。报告的 body 中包含的字段由报告的 type 决定。⚠️ 不同类型的报告具有不同的正文。
type 报告类型,例如 csp-violation 或 coep。
url 生成报告的文档或 worker 的地址。敏感数据(例如用户名、密码和片段)会从此网址中剥离。
user_agent 生成报告的请求的 User-Agent 标头。

凭据报告

与生成报告的网页具有相同来源的报告端点会在包含报告的请求中接收凭据(Cookie)。

凭证可能会提供有关报告的有用额外背景信息;例如,给定用户的账号是否持续触发错误,或者在其他页面上执行的特定操作序列是否会触发此页面上的报告。

浏览器何时以及如何发送报告?

报告通过带外方式从您的网站传送:浏览器控制报告何时发送到配置的端点。此外,您也无法控制浏览器何时发送报告;浏览器会在合适的时间自动捕获、排队并发送报告。

这意味着,使用 Reporting API 时几乎无需担心性能问题。

报告会延迟(最多 1 分钟)发送,以增加批量发送报告的机会。这样可以节省带宽,以免占用过多的用户网络连接,这在移动设备上尤为重要。如果浏览器正忙于处理优先级较高的工作,或者用户当时处于网速较慢或拥塞的网络中,浏览器也可能会延迟传送。

第三方和第一方问题

因网页上发生违规或弃用情况而生成的报告将发送到您配置的端点。这包括在您的网页上运行的第三方脚本所造成的违规行为。

如果违规或弃用发生在嵌入在网页中的跨域 iframe 中,系统将不会向您的端点报告这些违规或弃用(至少默认情况下不会)。iframe 可以设置自己的报告,甚至可以向您网站(即第一方)的报告服务报告;但这取决于框架网站。另请注意,大多数报告仅在违反网页政策时生成,并且网页政策和 iframe 政策不同。

包含废弃内容的示例

如果您在网页上设置了 Reporting-Endpoints 标头,系统会将网页上运行的第三方脚本调用的已弃用 API 报告给您的端点。由嵌入在您网页中的 iframe 调用的已弃用 API 不会报告给您的端点。只有在 iframe 服务器设置了 Reporting-Endpoints 的情况下,系统才会生成弃用报告,并且此报告会发送到 iframe 服务器设置的任何端点。
如果您的网页上设置了 Reporting-Endpoints 标头:在您的网页上运行的第三方脚本调用的已弃用 API 将报告给您的端点。如果您的网页中嵌入的 iframe 调用了已弃用的 API,系统不会向您的端点报告。只有当 iframe 服务器设置了 Reporting-Endpoints 时,系统才会生成弃用报告,并且此报告会发送到 iframe 服务器设置的任何端点。

浏览器支持

下表总结了浏览器对 Reporting API v1(即带有 Reporting-Endpoints 标头)的支持情况。浏览器对 Reporting API v0(Report-To 标头)的支持情况相同,但有一种报告类型除外:新的 Reporting API 不支持网络连接错误日志记录。如需了解详情,请参阅迁移指南。

报告类型 Chrome Chrome iOS Safari Firefox Edge
CSP 违规(仅限 3 级)* ✔ 是 ✔ 是 ✔ 是 ✘ 否 ✔ 是
网络连接错误日志记录 ✘ 否 ✘ 否 ✘ 否 ✘ 否 ✘ 否
COOP/COEP 违规 ✔ 是 ✘ 否 ✔ 是 ✘ 否 ✔ 是
所有其他类型:违反文档政策、弃用、干预、崩溃 ✔ 是 ✘ 否 ✘ 否 ✘ 否 ✔ 是

此表仅总结了对具有新 Reporting-Endpoints 标头的 report-to 的支持。如果您想迁移到 Reporting-Endpoints,请参阅 CSP 报告迁移提示。

使用 Reporting API

确定报告的发送对象

您有两种选择:

  • 将报告发送到现有的报告收集器服务。
  • 将报告发送到您自行构建和运营的报告收集器。

方法 1:使用现有报告收集器服务

报告收集器服务的一些示例包括:

如果您知道其他解决方案,请提交问题告知我们,我们会更新此帖子!

除了价格之外,在选择报告收集器时,请考虑以下几点:🧐

  • 此收集器是否支持所有报告类型?例如,并非所有报告端点解决方案都支持 COOP/COEP 报告。
  • 您是否愿意与第三方报告收集器分享您应用的任何网址?即使浏览器会从这些网址中剥离敏感信息,敏感信息仍可能会以这种方式泄露。如果这对您的应用来说风险太大,请运行您自己的报告端点。

方法 2:自行构建和运营报告收集器

构建自己的服务器来接收报告并非易事。首先,您可以复刻我们的轻量级样板。它使用 Express 构建,可以接收和显示报告。

构建自己的报告收集器时:

  • 检查 POST 请求的 Content-Type 是否为 application/reports+json,以识别浏览器发送到您端点的报告请求。
  • 如果您的端点与网站位于不同的源,请确保它支持 CORS 预检请求。

方法 3:结合使用方法 1 和方法 2

您可能希望让特定提供商处理某些类型的报告,但自行处理其他类型的报告。

在这种情况下,请按如下方式设置多个端点:

Reporting-Endpoints: endpoint-1="https://reports-collector.example", endpoint-2="https://my-custom-endpoint.example"

配置 Reporting-Endpoints 标头

设置 Reporting-Endpoints 响应标头。其值必须是一个或一系列以逗号分隔的键值对:

Reporting-Endpoints: main-endpoint="https://reports.example/main", default="https://reports.example/default"

如果您要从旧版 Reporting API 迁移到新版 Reporting API,则可能需要同时设置 Reporting-Endpoints 和 Report-To。如需了解详情,请参阅迁移指南。 特别是,如果您仅使用 report-uri 指令来报告 Content-Security-Policy 违规行为,请查看 CSP 报告的迁移步骤。

Reporting-Endpoints: main-endpoint="https://reports.example/main", default="https://reports.example/default"
Report-To: ...

键(端点名称)

每个键都可以是您选择的名称,例如 main-endpoint 或 endpoint-1。您可以决定为不同的报告类型设置不同的命名端点,例如 my-coop-endpoint、my-csp-endpoint。这样,您就可以根据报告类型将其路由到不同的端点。

如果您想接收干预、弃用、崩溃报告或这些报告的组合,请设置名为 default 的端点。

如果 Reporting-Endpoints 标头未定义 default 端点,则系统不会发送此类型的报告(尽管会生成这些报告)。

值(网址)

每个值都是您选择的网址,报告将发送到该网址。此处要设置的网址取决于您在第 1 步中做出的决定。

端点网址:

示例

Reporting-Endpoints: my-coop-endpoint="https://reports.example/coop", my-csp-endpoint="https://reports.example/csp", default="https://reports.example/default"

然后,您可以在适当的政策中使用每个命名的端点,也可以在所有政策中使用一个端点。

在哪里设置标头?

在新的 Reporting API(本文介绍的 API)中,报告的作用域为文档。这意味着,对于一个给定的源,不同的文档(例如 site.example/page1 和 site.example/page2)可以将报告发送到不同的端点。

如需接收网站任何网页上发生的违规或弃用情况的报告,请在所有响应中将该标头设置为中间件。

以下是 Express 中的一个示例:

const REPORTING_ENDPOINT_BASE = 'https://report.example';
const REPORTING_ENDPOINT_MAIN = `${REPORTING_ENDPOINT_BASE}/main`;
const REPORTING_ENDPOINT_DEFAULT = `${REPORTING_ENDPOINT_BASE}/default`;

app.use(function (request, response, next) {
  // Set up the Reporting API
  response.set(
    'Reporting-Endpoints',
    `main-endpoint="${REPORTING_ENDPOINT_MAIN}", default="${REPORTING_ENDPOINT_DEFAULT}"`,
  );
  next();
});

修改政策

现在,Reporting-Endpoints 标头已配置完毕,请为要接收违规报告的每个政策标头添加 report-to 指令。report-to 的值应为您配置的某个命名端点。

您可以对多个政策使用多个端点,也可以在政策中使用不同的端点。

对于每项政策,report-to 的值应为您配置的某个已命名端点。

report-to 对于弃用、干预和崩溃报告不是必需的。这些报告未绑定到任何政策。只要设置了 default 端点,就会生成这些事件,并将其发送到此 default 端点。

示例

# Content-Security-Policy violations and Document-Policy violations
# will be sent to main-endpoint
Content-Security-Policy: script-src 'self'; object-src 'none'; report-to main-endpoint;
Document-Policy: document-write=?0;report-to=main-endpoint;
# Deprecation reports don't need an explicit endpoint because
# these reports are always sent to the default endpoint

调试报告设置

有意生成报告

设置 Reporting API 时,您可能需要故意违反政策,以检查报告是否按预期生成和发送。

节省时间

报告可能会延迟发送(大约一分钟),这在调试时是很长的时间。😴 幸运的是,在 Chrome 中进行调试时,您可以使用标志 --short-reporting-delay 在报告生成后立即接收报告。

在终端中运行以下命令,以开启此标志:

YOUR_PATH/TO/EXECUTABLE/Chrome --short-reporting-delay

使用开发者工具

在 Chrome 中,您可以使用开发者工具查看已发送或将要发送的报告。

截至 2021 年 10 月,此功能处于实验阶段。如要使用该功能,请按以下步骤操作:

  1. 使用 Chrome 96 及更高版本(在浏览器中输入 chrome://version 即可查看版本)
  2. 在 Chrome 的地址栏中输入或粘贴 chrome://flags/#enable-experimental-web-platform-features。
  3. 点击已启用。
  4. 重新启动浏览器。
  5. 打开 Chrome 开发者工具。
  6. 在 Chrome 开发者工具中,打开“设置”。在“Experiments”下,点击 Enable Reporting API panel in the Application panel。
  7. 重新加载开发者工具。
  8. 重新加载页面。在打开的网页中生成的报告将列在 Chrome 开发者工具的应用面板中的 Reporting API 下。
Chrome 开发者工具列出了报告。
Chrome 开发者工具会显示在您的网页上生成的报告及其状态。

报告状态

状态列会显示报告是否已成功发送。

状态 说明
Success 浏览器已发送报告,端点已回复成功代码(200 或其他成功响应代码 2xx)。
Pending 浏览器正在尝试发送报告。
Queued 报告已生成,但浏览器未尝试发送。在以下两种情况下,报告会显示为 Queued:
  • 报告是新生成的,浏览器正在等待,看看是否有更多报告到达,然后再尝试发送。
  • 报告不是新的;浏览器已尝试发送此报告,但失败了,目前正在等待,然后再重试。
MarkedForRemoval 在重试一段时间 (Queued) 后,浏览器已停止尝试发送报告,并将很快从待发送报告列表中移除该报告。

无论报告是否已成功发送,都会在一段时间后被移除。

问题排查

报告未按预期生成或发送到您的端点?以下是排查此问题的一些提示。

未生成报告

在开发者工具中显示的报告已正确生成。如果您预期的报告未显示在此列表中:

  • 检查政策中的 report-to。如果此项配置有误,系统将不会生成报告。请前往修改政策进行修正。另一种问题排查方法是检查 Chrome 中的开发者工具控制台:如果控制台中弹出您预期的违规错误,则表示您的政策可能已正确配置。
  • 请注意,此列表中只会显示为当前在开发者工具中打开的文档生成的报告。举例来说,如果您的网站 site1.example 嵌入了违反政策的 iframe site2.example,并因此生成了报告,那么只有当您在自己的窗口中打开该 iframe 并为该窗口打开开发者工具时,该报告才会显示在开发者工具中。

报告已生成,但未发送或未收到

如果您可以在 DevTools 中看到报告,但您的端点未收到该报告,该怎么办?

  • 请务必使用短延迟。您可能无法查看报告,是因为报告尚未发送!
  • 检查 Reporting-Endpoints 标头配置。如果该电子邮件地址存在问题,则系统不会发送已正确生成的报告。在这种情况下,在开发者工具中,报告的状态将保持为 Queued(在尝试交付时,状态可能会跳到 Pending,然后快速返回到 Queued)。以下是一些可能导致此问题的常见错误:

  • 端点已被使用,但未配置。示例:

包含错误的代码
 Document-Policy: document-write=?0;report-to=endpoint-1;
 Reporting-Endpoints: default="https://reports.example/default"

Document-Policy 违规报告应发送到 endpoint-1,但此端点名称未在 Reporting-Endpoints 中配置。

  • 缺少 default 端点。某些报告类型(例如弃用和干预报告)只会发送到名为 default 的端点。如需了解详情,请参阅配置 Reporting-Endpoints 标头。

  • 检查政策标题语法中是否存在问题,例如缺少英文引号。查看详情。

  • 检查您的端点是否可以处理传入的请求。

    • 确保您的端点支持 CORS 预检请求。如果未启用,则无法接收报告。

    • 测试端点的行为。为此,您可以模拟浏览器,向端点发送类似于浏览器会发送的请求,而不是手动生成报告。运行以下命令:

    curl --header "Content-Type: application/reports+json" \
      --request POST \
      --data '[{"age":420,"body":{"columnNumber":12,"disposition":"enforce","lineNumber":11,"message":"Document policy violation: document-write is not allowed in this document.","policyId":"document-write","sourceFile":"https://dummy.example/script.js"},"type":"document-policy-violation","url":"https://dummy.example/","user_agent":"xxx"},{"age":510,"body":{"blockedURL":"https://dummy.example/img.jpg","destination":"image","disposition":"enforce","type":"corp"},"type":"coep","url":"https://dummy.example/","user_agent":"xxx"}]' \
      YOUR_ENDPOINT
    

    您的端点应返回成功代码(200 或其他成功响应代码 2xx)。如果未返回,则表示其配置存在问题。

仅报告

-Report-Only 政策标头和 Reporting-Endpoints 协同工作。

在 Reporting-Endpoints 中配置并在 Content-Security-Policy、Cross-Origin-Embedder-Policy 和 Cross-Origin-Opener-Policy 的 report-to 字段中指定的端点,会在这些政策遭到违反时收到报告。

在 Reporting-Endpoints 中配置的端点也可以在 Content-Security-Policy-Report-Only、Cross-Origin-Embedder-Policy-Report-Only 和 Cross-Origin-Opener-Policy-Report-Only 的 report-to 字段中指定。 当这些政策被违反时,他们也会收到报告。

虽然这两种情况都会发送报告,但 -Report-Only 标头不会强制执行政策:任何内容都不会中断或实际被屏蔽,但您会收到有关会中断或被屏蔽的内容的报告。

ReportingObserver

ReportingObserver JavaScript API 可帮助您观察客户端警告。

ReportingObserver 和 Reporting-Endpoints 标头会生成看起来相同的报告,但它们支持的使用场景略有不同。

在以下情况下使用 ReportingObserver:

  • 您只想监控弃用或浏览器干预。 ReportingObserver 会显示客户端警告(例如弃用和浏览器干预),但与 Reporting-Endpoints 不同,它不会捕获任何其他类型的报告,例如 CSP 或 COOP/COEP 违规。
  • 您需要实时应对这些违规行为。ReportingObserver 允许将回调附加到违规事件。
  • 您希望使用自定义回调将其他信息附加到报告中,以帮助进行调试。

另一个区别是,ReportingObserver 仅在客户端配置:即使您无法控制服务器端标头且无法设置 Reporting-Endpoints,也可以使用它。

深入阅读

非常感谢 Ian Clelland、Eiji Kitamura 和 Milica Mihajlija 对本文档的审核和建议。