You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 10:25:55