Cloudflare Worker防止R2反薅
2026-01-31 19:30:00

前言

之前在文章:博客图片迁移至 Cloudflare R2 记录 中写过R2 防盗链的配置步骤,听说只配置防盗链,也不一定能防住被恶意刷次数。后面又参考了这两篇文章:防止 Cloudflare R2 被恶意刷次数的实用技巧CloudflareR2防刷请求方法分享,配置了速率限制+缓存限制。一通操作下来,感觉好麻烦。最近看到这篇文章:Cloudflare大善人 R2存储的正确用法?防反薅,通过Cloudflare Worker来转发图片请求,Cloudflare Worker 每日限制10 万个请求,被刷爆也不用担心扣费了。

开始

1、关闭 R2 的公开访问权限,就是删除所有自定义域以及关闭公共开发 URL

image.png

2、创建Worker

image.png

image.png

image.png

3、编辑 Worker 代码

image.png

4、替换掉原本的 Hello World 代码,然后部署。(PS:以下代码,一般只需要更改ALLOWED_DOMAINS的内容。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
export default {
async fetch(request, env) {
const ALLOWED_DOMAINS = [
"https://1997.run",
"https://www.1997.run"
];

const origin = request.headers.get("Origin") || "";
const referer = request.headers.get("Referer") || "";
const isAllowed = ALLOWED_DOMAINS.some(domain =>
origin.startsWith(domain) || referer.startsWith(domain)
);

// 处理 CORS 预检(很多 fetch 会需要)
if (request.method === "OPTIONS") {
if (!isAllowed) {
return new Response("Forbidden: Invalid domain.", {
status: 403,
headers: {
"Access-Control-Allow-Origin": origin || ALLOWED_DOMAINS[0],
"Access-Control-Allow-Methods": "GET,HEAD,OPTIONS",
"Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers") || "*",
"Access-Control-Max-Age": "86400",
},
});
}
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": origin || ALLOWED_DOMAINS[0],
"Access-Control-Allow-Methods": "GET,HEAD,OPTIONS",
"Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers") || "*",
"Access-Control-Max-Age": "86400",
},
});
}

if (!isAllowed) {
return new Response("Forbidden: Invalid domain.", {
status: 403,
headers: { "Access-Control-Allow-Origin": origin || ALLOWED_DOMAINS[0] }
});
}

if (!["GET", "HEAD"].includes(request.method)) {
return new Response("Method Not Allowed", { status: 405 });
}

const url = new URL(request.url);
const objectKey = decodeURIComponent(url.pathname.slice(1));

// 你也可以加:只允许某个前缀,比如 public/
// if (!objectKey.startsWith("public/")) return new Response("Forbidden", { status: 403 });

if (!objectKey || objectKey.length > 1024) {
return new Response("Bad Request", { status: 400 });
}

const object = await env.R2_BUCKET.get(objectKey);
if (!object) {
return new Response("Not Found", { status: 404 });
}

const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
headers.set("Cache-Control", "public, max-age=31536000, immutable");

// CORS:只允许你的站点跨域读
headers.set("Access-Control-Allow-Origin", origin || ALLOWED_DOMAINS[0]);
headers.set("Vary", "Origin"); // 重要:不同 Origin 缓存分开

const ifNoneMatch = request.headers.get("If-None-Match");
if (ifNoneMatch && ifNoneMatch === object.httpEtag) {
return new Response(null, { status: 304, headers });
}

return new Response(object.body, { headers });
}
};

image.png

5、绑定 R2 存储桶

image.png

image.png

image.png

6、给 Worker 添加自定义域,配置完后,通过该自定义域就能访问R2 中的图片。

image.png

后记

通过 Worker 转发请求访问图片,应该会比正常开放 R2 公开访问图片慢一点点。不过感觉没啥影响,另外可以在 Cloudflare 页面规则里加下Worker 转发请求那个链接,缓存一下。如图:

image.png

上一页
2026-01-31 19:30:00
下一页