ESM模式下鉴权场景注册Service Worker失败问题
问题根因
这是浏览器遵循W3C Service Worker规范做出的默认行为,不是实现bug,两种加载模式的核心差异在请求的默认凭证策略:
- 当使用
type: "classic"注册Service Worker时,脚本请求默认采用same-origin凭证模式,同域请求会自动携带当前站点的Cookie,和普通静态资源、脚本的请求逻辑完全一致,因此Cookie鉴权可以正常通过。 - 当使用
type: "module"注册Service Worker时,初始Service Worker脚本请求、以及后续所有静态导入的模块依赖请求,默认凭证模式为omit,即默认不携带任何Cookie、HTTP认证头等身份凭证,因此后端收不到Cookie,返回401。
两种模式存在差异的原因
规范为模块Service Worker设置更严格的默认凭证策略,核心是出于安全考量:
模块类型的Service Worker支持导入跨域脚本(只要跨域资源返回合法的CORS响应头即可),如果沿用经典模式默认携带同域Cookie的逻辑,开发者在导入第三方维护的跨域模块时,很容易在无感知的情况下把用户的身份凭证发送给第三方站点,引发身份泄露、CSRF等安全风险。因此规范从默认行为层面做了约束,把模块Service Worker相关请求的默认凭证设为不携带,从源头降低安全隐患。
修复方案
在调用register方法时手动指定credentials配置即可,配置规则和fetch API的凭证选项完全一致。如果Service Worker和站点同域部署,只需要设置为same-origin就能让请求正常携带Cookie:
const registration = await navigator.serviceWorker.register('test.js', { type: "module", credentials: "same-origin" // 同域请求携带Cookie,跨域请求不携带 })
如果确实存在跨域部署Service Worker、且跨域请求需要携带凭证的场景,可以将值设为include,注意此时需要跨域服务端返回正确的CORS响应头(Access-Control-Allow-Credentials: true、且Access-Control-Allow-Origin不能为通配符*)。
问题最小复现
后端实现(index.js)
// Service Worker 文件路由 app.get('/test.js', (req,res) => { if(req.cookies.token != null) { res.sendFile('./public/test.js') } else { res.sendStatus(401) } }) // 首页路由 app.get('/', (req, res) => { // 登录校验逻辑 res.cookie('token',"<user token>").sendFile('./public/index.html') })
前端实现(index.html)
<body> <h1>Test</h1> <!-- 模块模式默认不带Cookie,鉴权失败 --> <script type="module"> const registration = await navigator.serviceWorker.register('test.js', {type: "module"}) console.log(registration) </script> <!-- 普通脚本请求正常携带Cookie --> <script src="test2.js"></script> <!-- 经典模式Service Worker默认携带Cookie,鉴权正常 --> <script type="module"> const registration = await navigator.serviceWorker.register('test.js', {type: "classic"}) console.log(registration) </script> </body>
注:原示例中
{type: "clasic"}存在拼写错误,正确枚举值为classic。
内容的提问来源于stack exchange,提问作者Francesco Terrell
相关产品推荐
相关产品推荐

