返回博客

Service Worker 撞上 ForwardAuth:容器日志面板登录死循环排障

Lazybones Follow 0 2026年09月05日 排障实录 62 次阅读

Service Worker 撞上 ForwardAuth:容器日志面板登录死循环排障

系列第三篇,一个典型的「curl 全绿但浏览器依然是坏的」排障案例。问题的迷惑性在于:每次命令行测试都通过,真实浏览器一打开却掉进重定向循环,容易被连续误判成「已经修好了」。核心教训只有一句:curl 不执行 JavaScript,更不执行 Service Worker。 文中域名、版本号均为示例。

现象:卡在「登录校验」的死循环

Dozzle 是个容器日志查看器,部署在 logs.example.com,前面挂了 Traefik + Authelia ForwardAuth(见系列第二篇)。问题表现是:

  • 浏览器访问 logs.example.com,未登录或会话过期后,页面无限卡在「登录校验」——导航挂起、转圈、网络面板里重定向请求循环刷屏,就是进不去。
  • 诡异的是,同样认证配置的 Traefik Dashboard(dashboard.example.com)却完全正常。
  • 更诡异的是,用 curl 端到端测整个 ForwardAuth 流程——未登录 302、登录拿 Cookie、回跳 200——全部通过。

curl 全绿 + 同配置的另一个服务正常,这两个「证据」很容易让人连续多次误判成「已经修好了」,然后被浏览器现实打脸。

为什么 curl 会骗你

先说清楚这个最关键的认知:curl / WebFetch 这类工具只走 HTTP 请求-响应链,它不执行页面里的 JavaScript,更不会注册和运行 Service Worker。

Dozzle 的前端是一个 PWA。它加载后会注册一个 Service Worker(/sw.js),这个 SW 一旦注册就持久驻留在浏览器里,跨会话存活。curl 拿到的只是第一层 HTML 外壳,链条走到 Authelia 返回的登录页 HTML 就停了——从 curl 的视角看「拿到了 200/302,正常」。但真实浏览器里,那个驻留的 SW 会在背后发起一堆 curl 根本模拟不出来的请求。

这就是为什么这类问题容易被反复误判:验证手段(curl)和故障发生的层面(浏览器 + SW)根本不在一个维度上。

用真实浏览器复现,定位根因

换成真实浏览器 + DevTools 的 Network / Application 面板复现,真相才浮出水面:

  1. Dozzle 的 SW(/sw.js,CACHE_NAME=dozzle-v10.9.0)注册后持久驻留。
  2. 会话缺失/过期时,SW 会做更新检查,并请求 /assets/* 等静态资源。
  3. 这些请求同样经过 Traefik,被 ForwardAuth 判定未认证,跨域 302 重定向到 auth.example.com。
  4. 浏览器跟随重定向,最终拿到的是 Authelia 的登录页——Content-Type: text/html 的 HTML 外壳,而不是它期待的 JS/CSS。
  5. Chrome 拒绝把 text/html 当 Service Worker 脚本解析(MIME 类型错误),叠加跨域重定向限制和 SW 生命周期钩子,导航被卡死/陷入循环。

一句话根因:SW 的更新检查与静态资源请求被认证中间件劫持,拿回 HTML 而非 JS,触发 MIME 错误 + 重定向循环。

为什么 Traefik Dashboard 同配置却正常

对比之下答案很清楚:Traefik Dashboard 没有注册 Service Worker。它就是个普通的受保护页面,未登录时干净地 302 到门户、登录后正常加载,不存在「后台有个 SW 在偷偷发被劫持的请求」这回事。同样的 ForwardAuth 配置,有没有 SW 是天壤之别。 这也反过来印证了根因就在 SW。

解法:用路由优先级放行静态资源

思路是让 SW 脚本和静态资源绕过认证(它们本来就不含敏感数据),而真正敏感的容器日志接口继续受保护。Traefik 的 router 支持 priority,正好用来做这件事。

在 File Provider(services.yml)里,为这个 Host 新增一个更高优先级、不挂认证中间件的 router,专门放行静态资源;原来的主 router 显式降低优先级并保留 authelia-forward,继续保护 / 和 /api/*:

http:
  routers:
    # 高优先级静态资源路由:不挂认证中间件,未登录也返回真实 JS/CSS
    dozzle-static:
      rule: "Host(`logs.example.com`) && (Path(`/sw.js`) || PathPrefix(`/assets`) || Path(`/favicon.ico`) || Path(`/manifest.json`) || PathPrefix(`/logo`))"
      entryPoints:
        - websecure
      priority: 100
      service: dozzle
      tls:
        certResolver: letsencrypt

    # 主路由:显式降优先级,保留 ForwardAuth,保护 / 和 /api/*(敏感容器日志)
    dozzle:
      rule: "Host(`logs.example.com`)"
      entryPoints:
        - websecure
      priority: 50
      middlewares:
        - authelia-forward
      service: dozzle
      tls:
        certResolver: letsencrypt

要点:

  • dozzle-static 的 priority: 100 高于主路由的 priority: 50,Traefik 会优先匹配它,让 /sw.js、/assets/* 等未登录也能拿到 200 + 正确的 JS/CSS MIME 类型。
  • 主路由 priority: 50 保留 authelia-forward,敏感的容器日志 / 和 /api/* 依然受认证保护——放行静态资源不等于敞开日志。
  • 精确列出需要 bypass 的静态路径(/sw.js、/assets、/favicon.ico、/manifest.json、/logo),不要用宽泛的通配把整个域都放行。

已注册的 SW 会自愈吗

会。容易担心需要手动去浏览器 unregister SW、清 Site Data 才能让修复生效,实测发现不用:服务端的静态路由 fix 生效后,下次访问时 SW 的更新检查能正常拿到真正的 /sw.js,MIME 正确、不再被劫持,链条自愈,页面干净地跳转到登录页。这一点必须以真实浏览器实测确认。

真实浏览器多轮验证

修复后的验证也必须在真实浏览器里做,覆盖关键场景:

  1. 干净环境首次访问:未登录 → 干净 302 到 Authelia 门户 → 登录 → 回跳 Dozzle 正常加载。
  2. SW 已注册 + 会话过期:这是原来必崩的场景。现在 SW 更新检查能拿到真 JS,会话过期时干净跳转到登录页,不再死循环。
  3. 自愈验证:不做任何手动清理,直接访问,确认从故障态自动恢复。

DevTools 的 Network 面板里,/sw.js 和 /assets/* 现在返回 200 + application/javascript / text/css,不再有跨域 302 到 auth 的记录——这是修复到位的直接证据。

普适经验

  1. PWA / Service Worker 相关的问题,必须用真实浏览器复现和验证。 curl 通过 ≠ 浏览器通过。凡是「curl 全绿但用户说坏了」,第一反应就该怀疑客户端脚本/SW 层面。
  2. 认证反代 + 带 SW 的前端(PWA) 是一个高风险组合。SW 的后台请求会被认证中间件无差别劫持,拿回 HTML 而非 JS/CSS,引发 MIME 错误和重定向循环。任何这类组合都值得预先排查。
  3. 用路由优先级排除静态路径绕过认证,是解决这类问题的通用手法:给静态资源开一条高优先级、无认证的通道,敏感接口留在受保护的主路由上。既修好了 SW,又不牺牲安全性。
  4. 对比法排障:找一个「配置相同但表现正常」的参照物(这里是无 SW 的 Traefik Dashboard),差异点往往就是根因所在。

延伸话题:如何给 Django 站点零回归地加上明暗模式——同样是「必须真实浏览器验证」,但坑在 CSS 特异性和对比度上。

评论 (0)

暂无评论,来说点什么吧。

登录 后可以发表评论