5月31日 17:42

PWA 安全性如何保障才算到位?

PWA 的安全底线比普通网页更高,因为它能被安装、能离线运行,还可能处理推送、缓存和本地数据。安全不是给页面加一个 HTTPS 就结束,而是要同时管住传输、脚本来源、Service Worker 作用域、缓存内容、身份凭证和监控告警。最重要的原则是:Service Worker 能拦截请求,所以它本身必须像后端入口一样谨慎对待;本地缓存能提升体验,也可能把过期页面、敏感接口响应和错误权限一起保存下来。

必做的安全配置

首先,生产环境必须全站 HTTPS,localhost 只适合开发调试。建议开启 HSTS,但第一次上线不要立刻加 preload,确认所有子域都支持 HTTPS 后再提交,否则某个历史子域会被浏览器强制 HTTPS 访问失败。其次,CSP 要尽量收紧脚本来源,少用 unsafe-inline,第三方统计、客服、广告脚本要单独评估。安全响应头还应补上 nosniff、合理的 Referrer Policy 和必要的 frame 限制,避免浏览器在边界场景里做出宽松解释。

nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; connect-src 'self' https://api.example.com; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Service Worker 作用域也要控制。放在根目录的 sw.js 可以控制整站,适合单应用站点;如果同域下有管理后台、支付页或多个业务线,最好用 /app/sw.jsscope: '/app/' 限定范围。缓存时只缓存同源、GET、非敏感响应,接口响应尤其要区分公开数据和用户私有数据。

js
self.addEventListener('fetch', event => { const req = event.request; const url = new URL(req.url); if (req.method !== 'GET' || url.origin !== location.origin) return; if (url.pathname.startsWith('/api/private')) return; event.respondWith(caches.match(req).then(r => r || fetch(req))); });

本地数据要按风险分层

PWA 经常用 Cache Storage、IndexedDB 和 Cookie 保存状态,但它们解决的是可用性,不自动解决安全性。公开静态资源可以放心缓存,公开列表数据可以设置短过期,用户私有数据则要绑定身份、过期时间和登出清理。真正敏感的数据,例如长期令牌、身份证明、支付信息,不应该长期留在前端;如果业务必须离线保存,也要用 Web Crypto 加密,并把密钥生命周期设计清楚。这里的边界是:前端设备一旦被用户或恶意脚本控制,本地加密只能降低泄露概率,不能替代服务端权限校验。

追问

Token 应该放 localStorage、Cookie 还是 IndexedDB?

没有一个选择适合所有场景。localStorage 读取方便,但一旦 XSS 成功,Token 很容易被直接拿走;HttpOnly Cookie 防读取更强,但要认真处理 CSRF、SameSite 和跨域配置。IndexedDB 适合存离线数据,不代表天然安全,敏感数据仍应加密并控制生命周期。取舍是:高安全业务优先短期访问令牌 + HttpOnly Refresh Cookie,普通工具也至少不要把长期 Token 明文塞进 localStorage。

PWA 离线缓存会带来哪些安全坑?

最大的坑是把用户私有接口、过期权限页面或错误版本的配置缓存下来。用户退出登录后,如果离线页还能展示旧订单、旧资料,就不是体验问题,而是数据泄露问题。边界做法是缓存静态资源和公开内容,私有数据缓存必须绑定用户、过期时间和登出清理。版本升级时还要删除旧缓存,否则修复过的 XSS 页面可能继续从缓存里被打开。

CSP 配得越严越好吗?

方向上越严越安全,但不能不测试就上线。很多 PWA 依赖构建工具注入运行时代码、第三方 CDN、Web Worker 或字体资源,CSP 一刀切会导致白屏、推送脚本失败或埋点丢失。建议先用 Content-Security-Policy-Report-Only 收集违规报告,再逐步切到强制模式。踩坑点是为了省事加回 unsafe-inline*,这会让 CSP 的防护价值大幅下降。

推送通知安全要注意什么?

VAPID 私钥只能在服务端保存,前端只暴露公钥。服务端保存订阅对象时要校验 endpoint、p256dh、auth 字段,并把订阅和用户身份、设备、撤销状态关联起来。不要把营销推送和安全推送混在一起,否则用户关闭通知后,真正重要的登录风险提醒也送不到。边界是:推送内容不要携带敏感明文,通知点击后再回到 HTTPS 页面拉取详情。

如何发现 PWA 安全问题已经在线上发生?

只靠 Lighthouse 不够,它更像上线前体检。线上要记录 Service Worker 安装失败、controllerchange 异常、CSP violation、异常 401/403、缓存命中旧版本等事件。日志里不要上传 Token、Cookie 或完整个人数据,只保留排查所需的匿名标识和版本号。安全监控的取舍是信息越多越好排查,但采集过度本身也会变成隐私风险。

收束

PWA 安全的重点,是承认它已经不只是一个普通网页。HTTPS、CSP、受控的 Service Worker、谨慎的缓存策略、合理的 Token 存储和线上监控,缺一块都可能把“离线可用”和“快速更新”变成风险。真正可靠的方案通常不会追求前端万能,而是让前端只保存必要状态,把最终权限、敏感数据和审计证据留在服务端。

标签:PWA