如何在Supabase Edge Function中获取Cloudflare Bot分数?
Supabase Edge Function 中获取Cloudflare机器人检测分数的实现方案
需求背景
我正在构建一个Supabase Edge Function,用于基于Cloudflare机器人检测自动封禁可疑用户。希望获取传入请求的机器人分数(与Supabase日志中的metadata.request.cf.botManagement.score类似),并在允许用户继续操作前做出决策。
目标实现示例
import { serve } from "https://deno.land/std@0.203.0/http/server.ts"; serve(async (req: Request) => { // 如何在此处获取机器人分数? const botScore = ???; if (botScore > 90) { return new Response("User banned", { status: 403 }); } return new Response("Allowed", { status: 200 }); });
已知信息
- Supabase控制台会自动记录请求的
metadata.request.cf.botManagement.score - Edge Function运行在Cloudflare边缘节点,CF元数据应该可用
- 本地终端执行
console.log(req.cf)没有输出内容
问题
- 如何在Supabase Edge Function中访问
botManagement.score? - 是否有类型安全的方式访问其他
req.cf元数据? - 是否需要从前端(React.js)发送任何内容才能使该元数据可用?
边缘日志示例
{"event_message": "POST | 204 | 182.4.101.81 | 97253acd29da5682 | https://vhslmvuozwidvzwpffcp.supabase.co/rest/v1/rpc/update_order_status_provider | Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36","id": "4bd856dd-705a-4d29-a557-3c80ee598174","metadata": [{"load_balancer_experimental_routing": null,"load_balancer_geo_aware_info": [],"load_balancer_redirect_identifier": null,"logflare_worker": [{"worker_id": "HPEERT"}],"request": [{"cf": [{"asOrganization": "PT. Telekomunikasi Selular (Telkomsel) Indonesia","asn": 23693,"botManagement": [{"corporateProxy": false,"detectionIds": [],"ja3Hash": "7a381f01c8988c2acc5cb971d3f964e8","ja4": "t13d1517h2_8daaf6152771_b6f405a00624","ja4Signals": [{"browser_ratio_1h": 0.7990557551384,"cache_ratio_1h": 0.26587697863579,"h2h3_ratio_1h": 0.98491811752319,"heuristic_ratio_1h": 0.0009424882591702,"ips_quantile_1h": 0.99997824430466,"ips_rank_1h": 5,"paths_rank_1h": 5,"reqs_quantile_1h": 0.99998259544373,"reqs_rank_1h": 4,"uas_rank_1h": 13}],"jsDetection": [{"passed": false}],"score": 99,"staticResource": false,"verifiedBot": false}],"city": "Yogyakarta","clientAcceptEncoding": "gzip, deflate, br","clientTcpRtt": 10,"clientTrustScore": 99,"colo": "CGK","continent": "AS","country": "ID","edgeRequestKeepAliveStatus": 1,"httpProtocol": "HTTP/2","isEUCountry": null,"latitude": "-7.80139","longitude": "110.36472","metroCode": null,"postalCode": "55122","region": "Yogyakarta","regionCode": "YO","requestPriority": "weight=220;exclusive=1","timezone": "Asia/Jakarta","tlsCipher": "AEAD-AES128-GCM-SHA256","tlsClientAuth": [{"certPresented": "0","certRevoked": "0","certVerified": "NONE"}],"tlsClientCiphersSha1": "nWmr2CuvhM3+1BAitb/WHA2q9wk=","tlsClientExtensionsSha1": "YBFOBuA5wMNs81uFFeCOyjk/B1k=","tlsClientExtensionsSha1Le": "ptC3B5vZg9mIOP8d5/vMURjex/s=","tlsClientHelloLength": "2068","tlsClientRandom": "0tpu2TaGmr0nnDLIuJhLSr2LkuR1nTxntepEkiORjRA=","tlsExportedAuthenticator": [{"clientFinished": "5764b39727d7eeb93a5b908cbd1bc87749de451a0245e8171e1392b4c881a579","clientHandshake": "b43176923aaf5bc5268c9939a43bb99492a03bd8569e26f7acf2c0a5e64010c1","serverFinished": "f8f3b78899d37a9a0d268e97fee75b9002e4cd61bf08ad5764d9fd07253f9100","serverHandshake": "c1a3299835ac373496942cf5c8371eb3659e84404dfc408dc9c3cd85a48eca4f"}],"tlsVersion": "TLSv1.3","verifiedBotCategory": null}],"headers": [{"accept": "*/*","cf_cache_status": null,"cf_connecting_ip": "182.4.101.81","cf_ipcountry": "ID","cf_ray": "97253acd29da5682","content_length": "76","content_location": null,"content_range": null,"content_type": "application/json","date": null,"host": "vhslmvuozwidvzwpffcp.supabase.co","prefer": null,"range": null,"referer": "https://terapis.pijit.id/","sb_gateway_mode": null,"sb_gateway_version": null,"user_agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36","x_client_info": "supabase-js-web/2.45.4","x_forwarded_host": null,"x_forwarded_proto": "https","x_forwarded_user_agent": null,"x_kong_proxy_latency": null,"x_kong_upstream_latency": null,"x_real_ip": "182.4.101.81"}],"host": "vhslmvuozwidvzwpffcp.supabase.co","method": "POST","path": "/rest/v1/rpc/update_order_status_provider","port": null,"protocol": "https:","sb": [{"apikey": [],"auth_user": "f25218ab-8ff5-480e-94b5-258c00a9d461","jwt": [{"apikey": [{"invalid": null,"payload": [{"algorithm": "HS256","expires_at": 2029332966,"issuer": "supabase","role": "anon","signature_prefix": "HE64j_","subject": null}]}],"authorization": [{"invalid": null,"payload": [{"algorithm": "HS256","expires_at": 1755731336,"issuer": "https://vhslmvuozwidvzwpffcp.supabase.co/auth/v1","key_id": "AwqaCaSbuwU/7BqS","role": "authenticated","session_id": "f88acab1-3dbc-473f-b8d0-d834251b9e4d","signature_prefix": "J8HzGw","subject": "f25218ab-8ff5-480e-94b5-258c00a9d461"}]}]}]}],"search": null,"url": "https://vhslmvuozwidvzwpffcp.supabase.co/rest/v1/rpc/update_order_status_provider"}],"response": [{"headers": [{"cf_cache_status": "DYNAMIC","cf_ray": "97253acd36a85682-CGK","content_length": null,"content_location": null,"content_range": "0-0/*","content_type": null,"date": "Wed, 20 Aug 2025 22:12:00 GMT","sb_gateway_mode": null,"sb_gateway_version": "1","transfer_encoding": null,"x_kong_proxy_latency": null,"x_kong_upstream_latency": null,"x_sb_error_code": null}],"origin_time": 142,"status_code": 204}]}],"timestamp": 1755727920189000}
解决方案
1. 访问botManagement.score的方法
Supabase Edge Function基于Cloudflare Workers运行,Cloudflare会自动将请求的CF元数据注入到Request对象的cf属性中,但本地开发环境无法获取该属性,必须部署到Supabase云端才能正常读取。
修改后的可运行代码:
import { serve } from "https://deno.land/std@0.203.0/http/server.ts"; serve(async (req: Request) => { // 类型断言获取cf属性(默认Request类型未定义该属性) const cf = (req as any).cf; // 处理可能的undefined情况,默认分数设为0 const botScore = cf?.botManagement?.score ?? 0; if (botScore > 90) { return new Response("用户已被封禁", { status: 403 }); } return new Response("允许访问", { status: 200 }); });
注意事项:
- 本地测试时
req.cf为undefined,可手动模拟数据进行测试 - 部署到云端后,Cloudflare会自动填充完整的CF元数据
2. 类型安全的访问方式
通过自定义TypeScript接口实现类型检查与提示:
import { serve } from "https://deno.land/std@0.203.0/http/server.ts"; // 定义Cloudflare机器人检测模块的类型 interface BotManagement { score: number; verifiedBot: boolean; corporateProxy: boolean; jsDetection: { passed: boolean }[]; // 可根据日志补充其他需要的字段 } // 定义完整的CF元数据类型 interface CloudflareProperties { botManagement: BotManagement; country: string; city: string; asn: number; // 可根据日志补充其他需要的字段 } // 扩展Request类型,添加cf属性 interface RequestWithCf extends Request { cf?: CloudflareProperties; } serve(async (req: RequestWithCf) => { const botScore = req.cf?.botManagement?.score ?? 0; if (botScore > 90) { return new Response("用户已被封禁", { status: 403 }); } // 示例:类型安全地访问其他CF元数据 console.log(`请求来源国家:${req.cf?.country}`); return new Response("允许访问", { status: 200 }); });
3. 前端无需额外操作
不需要从React前端发送任何额外数据,Cloudflare会在请求到达边缘节点时自动处理并注入CF元数据。只要请求直接发送到Supabase分配的Edge Function域名,就能获取完整的req.cf信息。
若前端通过代理转发请求,需确保代理不会丢失Cloudflare的请求头或元数据,但直接使用Supabase提供的域名时无需担心此问题。
内容的提问来源于stack exchange,提问作者abiieez
相关产品推荐
相关产品推荐

