Cloudflare Worker缓存命中时被跳过,如何强制处理HTML认证请求?
Cloudflare Worker 强制登录保护文档服务的缓存绕过问题
问题概述
配置与预期
- 路由:
docs.example.com/*→ 绑定至Worker - 预期效果:未认证的HTML导航请求(
/、*.html)被重定向至/login(返回302状态码)
实际问题
访问/或/index.html时,返回200状态码的HTML内容,响应头显示cf-cache-status: HIT,Worker自定义响应头x-docs-worker从未出现。边缘节点在Worker执行前直接返回了缓存的index.html。
已尝试措施
- 执行
purge_everything及特定URL缓存清除 - 为
/和/index.html设置页面规则cache_level=bypass - 开启开发模式、更换新子域名、验证路由与Worker配置
但所有操作后,HTML请求仍显示cf-cache-status: HIT,Worker始终被跳过。
核心诉求
如何确保Cloudflare始终将HTML请求(/、*.html)转发至Worker执行登录认证,而非直接从Assets绑定或边缘缓存返回内容?
解决方案
1. 改用Cloudflare规则集(Ruleset)替代页面规则
旧页面规则优先级可能低于缓存机制,需在规则 → 规则集中创建以下规则:
- 匹配条件:
Host equals docs.example.com且 (URI equals /或URI ends with .html) - 执行操作:
- 缓存 → 缓存级别:绕过缓存
- 缓存 → 浏览器缓存TTL:0秒
- 缓存 → 边缘缓存TTL:0秒
2. 调整Worker路由的匹配模式与优先级
确保Worker路由docs.example.com/*的优先级高于所有缓存相关规则。在Cloudflare控制台的Workers → 路由中,将该路由的优先级设为最高(数值最小,比如1)。
3. 修改Worker代码,强制跳过Assets绑定的缓存
从Assets绑定获取资源时,添加Cache-Control: no-cache请求头,避免Assets返回缓存内容:
// 替换原Serve static assets via binding部分 const assetReq = new Request(req, { headers: new Headers(req.headers) }); // 强制Assets绑定跳过缓存 assetReq.headers.set('Cache-Control', 'no-cache'); const res = await env.ASSETS.fetch(assetReq);
4. 确保登录页面/login的缓存被完全禁用
在Worker中为/login请求添加缓存禁用逻辑,避免登录页面被缓存导致认证失效:
// 在Early HTML guard之后添加 if (url.pathname === "/login") { const loginRes = await env.ASSETS.fetch(req); const h = new Headers(loginRes.headers); h.set("cache-control", "private, no-store"); h.set("x-docs-worker", envLabel); return new Response(loginRes.body, { status: loginRes.status, headers: h }); }
完整修改后的Worker代码
type Env = { ASSETS: Fetcher; // 静态资源绑定(mkdocs输出) AUTH_COOKIE_NAME?: string; // 示例:"docs-access-token" WORKER_ENV?: string; // "prd" | "stg" }; const DEFAULT_COOKIE_NAME = "docs-access-token"; // 简单的Cookie获取函数 function cookieGet(req: Request, name: string): string | undefined { const cookie = req.headers.get("cookie") || ""; for (const part of cookie.split(/;\s*/)) { const [k, ...rest] = part.split("="); if (k?.trim() === name) return rest.join("="); } return undefined; } // HTML导航请求检测 function isHtmlNavigation(req: Request, url: URL): boolean { const accept = req.headers.get("accept") || ""; if (!accept.includes("text/html")) return false; return url.pathname === "/" || url.pathname.endsWith(".html"); } // 302重定向工具函数 function redirect(url: string, envLabel: string): Response { const h = new Headers({ location: url }); h.set("cache-control", "no-store"); h.set("x-docs-worker", envLabel); return new Response(null, { status: 302, headers: h }); } export default { async fetch(req: Request, env: Env): Promise<Response> { const url = new URL(req.url); const envLabel = env.WORKER_ENV?.trim() || "prd"; const cookieName = env.AUTH_COOKIE_NAME || DEFAULT_COOKIE_NAME; // HTML请求认证拦截 if (isHtmlNavigation(req, url) && !cookieGet(req, cookieName)) { return redirect(`/login?redirect=${encodeURIComponent(url.pathname + url.search)}`, envLabel); } // 登录页面缓存禁用 if (url.pathname === "/login") { const loginRes = await env.ASSETS.fetch(req); if (!loginRes) return new Response("Not found", { status: 404, headers: { "x-docs-worker": envLabel } }); const h = new Headers(loginRes.headers); h.set("cache-control", "private, no-store"); h.set("x-docs-worker", envLabel); return new Response(loginRes.body, { status: loginRes.status, headers: h }); } // Worker存活验证 if (url.pathname === "/health") { return new Response("ok", { status: 200, headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store", "x-docs-worker": envLabel, }, }); } // 强制跳过Assets绑定缓存,获取最新资源 const assetReq = new Request(req, { headers: new Headers(req.headers) }); assetReq.headers.set('Cache-Control', 'no-cache'); const res = await env.ASSETS.fetch(assetReq); if (!res) return new Response("Not found", { status: 404, headers: { "x-docs-worker": envLabel } }); // HTML资源添加缓存控制头 const ct = res.headers.get("content-type") || ""; if (ct.includes("text/html")) { const h = new Headers(res.headers); h.set("cache-control", "private, no-store"); h.set("vary", "Cookie"); h.set("x-docs-worker", envLabel); return new Response(res.body, { status: res.status, headers: h }); } // 非HTML资源透传,添加Worker标识头 const h = new Headers(res.headers); h.set("x-docs-worker", envLabel); return new Response(res.body, { status: res.status, headers: h }); }, };
验证命令
# 验证Worker是否正常运行 curl -i https://docs.example.com/health | egrep -i 'http/|x-docs-worker' # 未认证根路径请求(应返回302,无缓存命中) curl -i https://docs.example.com/ -H 'Accept: text/html' --cookie "" \ | egrep -i 'http/|location|cf-cache-status|x-docs-worker' # 显式index.html请求(应返回302,无缓存命中) curl -i https://docs.example.com/index.html -H 'Accept: text/html' --cookie "" \ | egrep -i 'http/|location|cf-cache-status|x-docs-worker'
内容的提问来源于stack exchange,提问作者Taco
相关产品推荐
相关产品推荐

