本节摘要:所有现代浏览器均将 HTTPS 作为启用 PWA 核心能力的强制先决条件——这不是可选项,而是存在性公理。本节讲三重安全保障的机理、证书类型与自动化生命周期管理、浏览器兼容性矩阵的正确读法,以及安全上下文之上的纵深防御。(素材映射:原 2.3 节全部内容。)
阅读完本节,你应当能够:
先看一个事实:主流浏览器早已把 Service Worker 接口的可用性与页面是否通过加密传输深度绑定。若应用运行在明文协议下,即使你写出了完美的缓存逻辑,注册调用也只会返回一个被拒绝的异步结果——不是报错,而是被静默拦截。
这种设计基于一个朴素的安全洞见:一条未加密的通道,本质是一根全透明的玻璃管道。攻击者不仅能窥探用户提交的密码与卡号,更能实时篡改页面、脚本、样式乃至应用清单本身。设想一下:若缓存脚本能被中间人注入恶意逻辑,它就能劫持整个应用的所有流量。在这种地基上部署任何"增强型"功能,无异于在流沙上盖楼。
因此 HTTPS 对 PWA 的意义,早已超越"传输层加密",升格为运行时环境的可信声明——向浏览器宣告:此上下文具备执行高权限操作的资格。

三重保障各司其职。机密性靠混合加密体系:用非对称算法安全交换会话密钥,再用对称算法高速加密应用数据,数学根基是大数分解与离散对数的计算不可行性——被缓存的用户资料不会在途中被截获解密。完整性靠内嵌的消息认证码:加密与认证原子化绑定,接收方验证每条记录的哈希签名,攻击者改动清单文件哪怕一个字节,连接立即中断。身份认证最常被忽视却对权限模型至关重要:数字证书将域名与公钥权威绑定,浏览器验证证书链并严格校验主题备用名称字段与请求域名的精确匹配——你确实在和目标域名的合法持有者对话。
三重保障共同构筑"安全上下文"。Web 标准把加密协议、本地回环地址列为安全上下文,明文协议则被划入降级上下文——只有前者允许调用 Service Worker、通知、凭据管理、加密子系统等高敏接口。
理解原理只是第一步,真正考验工程成熟度的是可持续地维护信任链。证书类型要按部署拓扑选:
尤其关键的一点:清单里的启动地址与作用域必须落在证书覆盖的域名范围内。证书只覆盖旧域名而应用部署在新域名上,浏览器将直接拒绝解析清单。
证书天生有时效。主流免费证书机构把有效期压缩到九十天,行业也在推动更短周期——这不是制造运维负担,而是最小化私钥泄露后的危害窗口:一张五年期证书的私钥泄露,攻击者可以冒充你五年;九十天加自动续期,把潜在危害期压到极致。自动化要求三件事:私钥在安全环境生成、绝不硬编码进配置或代码仓库;用自动化证书管理协议完成域名验证与签发,主流工具都能做到无人值守;更新后网关能不中断现有连接地加载新证书。
前沿架构进一步把证书管理下沉到边缘:内容分发网络在边缘节点终止加密,再以内部加密通道连接源站——源站不暴露公网、更新零感知,还能对清单与缓存脚本等关键资源配置精细化的访问控制规则。
拒绝罗列"某浏览器第几版支持"的静态表格,真正的兼容性是动态的能力映射:
| 能力 | 主流 Chromium 系 | Firefox | Safari | 关键依赖 |
|---|---|---|---|---|
| Service Worker | 广泛支持 | 支持 | 11.1 起支持 | 安全上下文 |
| 安装提示事件 | 支持 | 支持 | 不支持 | 用户交互历史 |
| 已安装应用查询 | 支持 | 不支持 | 不支持 | 清单声明关联应用 |
| 后台同步 | 支持 | 支持 | 不支持 | 注册加用户授权 |
Safari 对 PWA 采取"保守渐进"策略:早早启用了 Service Worker,却长期拒绝实现安装提示事件,理由是避免诱导安装。这种差异迫使开发者把"添加到主屏幕"设计为可选增强——核心功能在标签页中完整可用,安装引导只作锦上添花。
**localhost 的蜜糖与毒药。**本地回环地址在所有现代浏览器中都被豁免加密要求——它天然隔离于网络,不存在中间人攻击面。但这枚开发蜜糖暗藏生产毒药:豁免权不延伸到任何其他开发域名。本地测试完美、部署到测试环境全线崩溃的案例,十有八九栽在这里。解法两条:为开发域名签发本地可信证书实现零差异;在部署流水线加前置检查,发现明文协议直接阻断发布并提示。
**用能力检测,别用浏览器嗅探。**嗅探用户代理字符串既易伪造又追不上版本迭代。正确姿势是直接询问浏览器"你能做这个吗":
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').catch(console.warn); } else { // 回退到 HTTP 缓存头策略 } if ('onbeforeinstallprompt' in window) { // 捕获安装提示事件 定制引导按钮 } else if (matchMedia('(display-mode: standalone)').matches) { // 已在主屏独立窗口中运行 } else { // 提供通用安装指引文案 }
这种模式把兼容性决策权交还给浏览器本身,让每个浏览器在能力范围内给出最佳体验——恰好是"渐进式"哲学的代码化。
传输层信任解决后,PWA 的复杂性催生了新的攻击面,需要叠加专属防护。
**清单防篡改。**清单是纯文本资源,若内容分发网络被劫持、启动地址被改成钓鱼页,用户点图标就会中招。防御手段包括子资源完整性校验——为清单链接附加强哈希,内容不匹配即拒绝加载;以及用内容安全策略的清单来源指令限定加载源。
**缓存脚本最小权限。**Service Worker 拥有强大的网络拦截能力,也因此成为高价值目标:注册时指定最小必要作用域,避免根作用域导致可拦截全站请求;脚本中不处理密码卡号等敏感数据,鉴权交给后端;定期枚举缓存内容,确保没有意外缓存管理路径等敏感页面。
**更进一步的头配置。**严格传输安全头强制浏览器在未来一段时间内只走加密协议,加入预加载列表后即使用户手输明文地址也会自动跳转——这是安装体验的终极保障;证书透明度机制则要求证书被记录在公开日志中,防止证书机构被滥发欺诈证书。
⚠️ 常见坑:把 HTTPS 当"上线前最后一步配置"。证书链不完整、混合内容阻断、预加载缺失,任何一项都会让 PWA 能力静默降级。安全不是配置项,而是贯穿开发全周期的思维范式。
💡 关键直觉:信任无法被下载,只能被构建。它不存储在缓存里,而沉淀于每一次握手的严谨、每一张证书的及时更新、每一个接口的审慎调用——这才是"渐进式"最深刻的隐喻:不是功能的逐步叠加,而是信任的层层夯实。
**问:本地一切正常,部署到测试环境后注册接口不存在了?**答:九成是测试环境用了明文协议或自签证书。本地回环地址被豁免,但任何真实域名都不在豁免范围内。先确认协议,再确认证书链完整(缺中间证书是高频翻车点,部分客户端会静默失败而非警告)。
**问:页面有绿锁,为什么混合内容还会阻断能力?**答:绿锁只说明主文档走了加密,页面里引用的明文脚本、图片、接口仍构成混合内容。浏览器对"被动混合内容"(图片)宽容,对"主动混合内容"(脚本)严格——而后者一旦存在,安全上下文的完整性就打了折扣。排查方法:控制台过滤混合内容警告,逐个升级资源地址。
**问:证书自动续期了,为什么部分用户还是报证书错误?**答:续期后要确认两件事:网关真的加载了新证书(热重载没生效是常见原因);新证书的链与域名覆盖没有变化(换了签发机构可能缺中间证书)。再用第三方检测工具从外部验证一遍,别只信本机 curl。
**问:安全头配置后页面功能崩了一片?**答:内容安全策略是最容易"一配就崩"的头——先从报告模式开始(只上报不拦截),收集一周违规报告,逐步收紧指令后再切换到强制模式。这条渐进路线能避免上线事故。
调试推送和部分接口必须 HTTPS,本地开发第一段命令五分钟解决:
mkcert localhost 127.0.0.1 # 生成 localhost.pem / localhost-key.pem,vite.config 设 server.https 指向二者 # 局域网设备调试可临时用 chrome://flags 放行 http 源(仅限开发机)
第二段验证安全响应头是否生效,上线后例行跑一次:
async function auditHeaders(origin) { const res = await fetch(origin, { method: 'HEAD' }); const want = { 'strict-transport-security': s => /max-age=\d{6,}/.test(s), 'content-security-policy': s => s.includes('default-src'), 'x-content-type-options': s => s === 'nosniff' }; for (const name of Object.keys(want)) { const v = res.headers.get(name); console.log(v && want[name](v) ? 'PASS' : 'FAIL', name, v || '(缺失)'); } } auditHeaders(location.origin);
三件套凑齐了。下一章进入架构层:外壳、内核、数据三层如何拆分,组件之间如何协同。