# 保护 PWA

> 如何用安全上下文承载 PWA、用内容安全策略限制可执行资源，并有意识地约束 Service Worker 作用域。

PWA 用安全上下文承载强大功能，用内容安全策略（CSP）约束资源加载，并用明确的 Service Worker 作用域控制请求范围。部署时分别配置并检查这些边界。用 `window.isSecureContext` 检查活动上下文，注册前再检测 `navigator` 中是否有 `serviceWorker`。页面可以用 `ReportingObserver` 观察 CSP 违规，也可以借助报告响应头和指令把报告发送到端点（[Secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts)，MDN；[Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy)，MDN）。

## 支持范围

MDN 将 `ServiceWorkerContainer.register()` 描述为自 2018 年 4 月起已在各浏览器中广泛可用（[ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register)，MDN）。MDN 把 CSP 响应头标为广泛可用，但具体指令的支持情况仍有差异（[Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy)，MDN）。

## 在安全上下文中提供应用

MDN 将 HTTPS 与 `http://localhost` 等本地开发来源列为可能可信的来源。当前文档不可信时，仅限安全上下文的功能不可用。生产环境把 HTTP 流量重定向至 HTTPS，并在启动需要安全上下文的功能前检查运行时属性。

## 检测部署边界

回退路径保留导航和在线内容，同时跳过 Service Worker 注册。

```js
const status = document.createElement('output');
status.id = 'offline-status';
status.setAttribute('aria-live', 'polite');
document.body.append(status);

if (!window.isSecureContext) {
  status.textContent = '此连接无法使用离线安装。';
} else if (!('serviceWorker' in navigator)) {
  status.textContent = '此浏览器使用在线体验。';
} else {
  navigator.serviceWorker.register('/sw.js', { scope: '/' })
    .then(() => { status.textContent = 'Service Worker 注册已创建或更新。'; })
    .catch(() => { status.textContent = '在线体验仍可使用。'; });
}
```

明确的分支让安全性或能力检测失败可见，同时不阻塞页面。

:::caution
即使 iframe 自身使用 HTTPS，只要顶层文档仍是 HTTP，它就不属于安全上下文。请在实际调用受限 API 的 frame 内检查 `window.isSecureContext`。Service Worker 脚本还必须使用有效的 JavaScript 媒体类型；作用域超出脚本所在目录时，响应中必须有匹配的 `Service-Worker-Allowed` 头（[Secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts)，MDN；[ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register)，MDN）。
:::

## 通过响应头发送 CSP

`Content-Security-Policy` 响应头让网站限制脚本、样式、图片、frame 和连接等资源。MDN 的仅报告示例在测试期间使用 `Content-Security-Policy-Report-Only`，它会报告违规，但不会阻止代码执行。

```http
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
```

此示例把默认加载与脚本限制为同源，阻止插件，限制 `<base>` URL，并阻止页面被嵌入。请按应用实际加载的资源调整策略。

:::security
由于 `script-src` 既没有 `'unsafe-inline'`，也没有匹配的 nonce 或哈希，上述示例会拦截内联脚本。按照 MDN 的 CSP 参考，把启动代码移至同源文件，或在响应中生成新的 nonce 并应用于获准的 `<script>` 元素。
:::

## 限制 Service Worker 作用域

注册的默认作用域是 Service Worker 脚本所在目录。若要请求超出该目录的作用域，Service Worker 脚本响应必须提供允许该范围的 `Service-Worker-Allowed` 头（[ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register)，MDN；[Service worker script response](https://w3c.github.io/ServiceWorker/#service-worker-script-response)，W3C）。

如果 worker 只应控制 `/app/`，把脚本放在 `/app/sw.js`，并以 `{ scope: '/app/' }` 注册。避免作用域重叠：MDN 提醒通常应避免重叠，因为只有一个注册会控制文档。

## 验证部署后的边界

检查最终响应，而不只检查源文件。确认页面处于安全上下文、文档响应带有 CSP 头，并且 `registration.scope` 与预期 URL 边界一致。用 `ReportingObserver` 观察 CSP 违规，或按策略中的报告指令将报告发送到端点（[Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy)，MDN）。

## 另请参阅

- [Service Worker 生命周期](/zh/reference/service-worker/lifecycle/)
- [Manifest scope](/zh/reference/manifest/scope/)
- [离线指南](/zh/guides/offline/)
- [Secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts)（MDN）
- [Service Workers specification](https://w3c.github.io/ServiceWorker/)（w3.org）