在 Next.js 边缘中间件中集成 DataDome 机器人防护:从一键部署到源码级原理
2026/9/18 17:41:01 网站建设 项目流程

在 Next.js 边缘中间件中集成 DataDome 机器人防护:从一键部署到源码级原理

【免费下载链接】examplesEnjoy our curated collection of examples and solutions. Use these patterns to build your own robust and scalable applications.项目地址: https://gitcode.com/GitHub_Trending/examples1/examples

DataDome 是一款实时机器人防护(Bot Protection)服务,能为任意网站提供 bot 识别、验证码挑战与其他安全防护能力。本仓库中的edge-middleware/bot-protection-datadome示例演示了如何通过 Next.js Edge Middleware,在请求到达应用之前就交给 DataDome 判定,从而把防护逻辑下沉到边缘网络。阅读本文后,你将掌握该示例的完整运行方式(一键部署与本地克隆)、三个演示路由的差异,以及lib/datadome.ts中请求校验、超时兜底、响应头回写等核心实现的底层原理。

示例概览:在边缘完成机器人判定

根据该示例 README 的说明,DataDome 提供实时机器人防护以及其它安全防护能力,而本模板的关键思路是在边缘(Edge Middleware)使用它:中间件把每个受保护请求的上下文信息转发给 DataDome 的校验接口,由 DataDome 返回"放行"或"拦截"的结论,拦截时可直接改写(rewrite)到 DataDome 的验证码页面。

模板自带一个在线演示地址(https://edge-functions-bot-protection-datadome.vercel.app),你可以通过它直观对比"受保护页面"与"未受保护页面"在响应头、延迟上的差异。

三个演示路由:受保护、被拦截与豁免

示例页面共三个路由,分别对应防护的三种状态,源码位于 pages 目录:

路由页面文件行为
/pages/index.tsx启用 DataDome 的首页,正常用户可直接访问,并展示响应中的x-datadome
/blockedpages/blocked.tsx强制触发拦截的演示页,会把你送入验证码挑战
/omitpages/omit.tsx完全不走 DataDome 的对照页,便于观察防护带来的头部与延迟差异

blocked页面中,README 页面文案提示:验证码通常只需要通过一次,之后刷新或再次访问不会再弹出,除非你在 DevTools 的 Application → Storage → Cookies 中手动删除datadomecookie。这也侧面说明 DataDome 依靠客户端 cookie 记忆"已通过验证"的身份。

快速开始

示例 README 提供了两种使用方式。

方式一:一键部署到 Vercel

点击 README 中的 "Deploy with Vercel" 按钮即可完成部署,部署时会要求配置两个环境变量:NEXT_PUBLIC_DATADOME_CLIENT_SIDE_KEYDATADOME_SERVER_SIDE_KEY。该需求同时被 vercel.json 与 pages/_app.tsx 中的部署按钮配置所印证。

方式二:克隆到本地运行

使用create-next-app配合 pnpm 拉取该示例:

pnpm create next-app --example https://github.com/vercel/examples/tree/main/edge-middleware/bot-protection-datadome bot-protection-datadome

运行前需要有一个 DataDome 账号,然后在示例目录中把环境变量示例文件复制为本地文件(该文件会被 Git 忽略):

cp .env.example .env.local

接着打开.env.local,将环境变量替换为 DataDome 控制台中显示的密钥。README 指出密钥可在 DataDome 控制台的https://app.datadome.co/dashboard/config/protection/keys找到。最后启动开发服务器:

pnpm dev

环境变量清单

结合 lib/datadome.ts 与 pages/_app.tsx 的源码,本示例实际读取的环境变量如下:

变量是否必填默认值用途
DATADOME_SERVER_SIDE_KEY服务端密钥,作为校验请求中的Key字段(见datadome.ts中的requestData.Key
NEXT_PUBLIC_DATADOME_CLIENT_SIDE_KEY客户端密钥,注入到tags.js脚本的window.ddjskey
DATADOME_TIMEOUT300边缘侧等待 DataDome 响应的超时毫秒数
DATADOME_ENDPOINThttps://api.datadome.coDataDome 校验 API 地址,validateEndpoint()会自动补全https://前缀

边缘中间件:只保护需要保护的路由

入口中间件位于 middleware.ts,其核心逻辑非常精简:

export const config = { // It's possible to run Datadome for all paths, but it's better to take // advantage of pattern matching and only protect from bots where required. matcher: ['/', '/blocked'], } export default async function middleware(req: NextRequest) { const { pathname } = req.nextUrl // Force the page to be blocked by DataDome if (pathname === '/blocked') { req.headers.set('user-agent', 'BLOCKUA') } return datadome(req) }

两点值得注意:

  1. matcher精确圈定保护范围:源码注释明确说明,虽然可以让 DataDome 覆盖所有路径,但更优的做法是利用matcher模式匹配,只在需要的地方启用防护。这也是为什么/omit路由不在matcher列表中——它天然绕过了中间件,成为对照组。
  2. /blocked的拦截是"伪造"的:该路由会把请求头中的user-agent强行改成BLOCKUA,让 DataDome 把本应正常的请求判定为机器人,从而演示拦截与验证码流程。

中间件对datadome(req)的返回值有三种理解:有响应且带 rewrite,说明被拦截(应返回该响应,通常已改写为 DataDome 验证码页);有响应但无 rewrite,说明你不是机器人,响应会携带 DataDome 的回写头;无响应则直接放行。

源码级拆解:lib/datadome.ts 如何与 DataDome 通信

中间件真正的重头戏在 lib/datadome.ts(通过 tsconfig 中的@lib/*路径别名引入)。整个校验流程可以概括为:静态资源放行 → 组装请求特征 → POST 校验 → 超时兜底 → 按状态码处理 → 回写响应头

1. 静态资源直接放行

const DATADOME_URI_REGEX_EXCLUSION = /\.(avi|flv|mka|mkv|mov|mp4|mpeg|mpg|mp3|flac|ogg|ogm|opus|wav|webm|webp|bmp|gif|ico|jpeg|jpg|png|svg|svgz|swf|eot|otf|ttf|woff|woff2|css|less|js|map)$/i

对图片、音视频、字体、样式表、脚本等静态资源直接return,不占用 DataDome 配额,也避免边缘侧为每个静态请求增加额外延迟。

2. 组装完整的请求特征上报

requestData把一次 HTTP 请求的几乎所有上下文(方法、路径、查询参数、Host、各协议头、Cookie 长度、sec-ch-ua*客户端提示、sec-fetch-*等)收集起来,POST 到DATADOME_ENDPOINT + '/validate-request/'。其中两个细节很有参考价值:

  • 客户端 IP 的取法:代码注释说明按规范应取x-real-ip,但它在 Edge Middleware 上不可用,因此退而使用x-forwarded-for的第一段,本地无此头时回退到127.0.0.1(注释同时提醒:本地调试时 DataDome 通常不会拦截,除非使用真实 IP)。
  • 字段长度裁剪truncateRequestData维护了一张字段长度上限表(如useragent: 768referer: 1024request: 2048xforwardedforip-512表示保留尾部),超长字段会被截断,避免上报体过大。

3. 超时兜底:Promise.race 竞速

const timeoutPromise = new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Datadome timeout')) }, DATADOME_TIMEOUT) }) dataDomeRes = (await Promise.race([ dataDomeReq, timeoutPromise, ])) as NextResponse

当 DataDome 服务不可用或响应过慢时,Promise.race会以DATADOME_TIMEOUT(默认 300ms)触发超时,代码捕获异常后console.errorreturn(即放行请求)。这是一个典型的fail-open设计:防护服务故障不应拖垮正常业务,宁可暂时放行也不阻塞用户。

4. 按状态码分流处理

switch (dataDomeRes.status) { case 400: // Something is wrong with our authentication return case 200: case 301: case 302: case 401: case 403: let res = NextResponse.next() if (dataDomeRes.status !== 200) { // blocked! res = new Response(dataDomeRes.body, {status: dataDomeRes.status}) as NextResponse ... } ... }
  • 400:说明服务端密钥等认证信息有问题,日志输出statusText与响应体后放行;
  • 200:未拦截,构造NextResponse.next()继续正常处理;
  • 301 / 302 / 401 / 403:被拦截,直接透传 DataDome 的响应体与状态码(通常是验证码页面)。命中 bot 时还会通过x-datadome-isbotx-datadome-botnamex-datadome-ruletype打印出机器人的名称与命中规则类型。

5. 回写 DataDome 响应头与 Cookie 域修复

DataDome 的响应通过x-datadome-headers头声明需要回写到浏览器的头列表,toHeaders会逐一取出并合并进最终响应。其中内置了一个知名 bug 的 workaround:当 DataDome 返回的set-cookie把域设置为整个公共后缀.vercel.app时,浏览器会拒绝写入该 cookie,因此代码将其改写为Domain=${req.headers.get('host')},保证datadomecookie 能正确种到当前域名下。

另外,源码中res.headers.set('x-datadome-latency', ...)这行带有注释:该延迟头仅为演示目的而加,生产环境并非必需。

客户端脚本注入:验证码与指纹的浏览器侧配合

DataDome 的防护并不只有服务端判定,浏览器侧脚本同样关键。pages/_app.tsx 使用next/scriptlazyOnload策略加载两个脚本,脚本地址定义在 lib/constants.ts:

export const DATADOME_TAGS = 'https://js.datadome.co/tags.js' export const DATADOME_JS = 'https://api-js.datadome.co/js/'

注入方式如下:

<Script strategy="lazyOnload" id="load-datadome">{` window.ddjskey = '${process.env.NEXT_PUBLIC_DATADOME_CLIENT_SIDE_KEY}' window.ddoptions = { endpoint: '${DATADOME_JS}' } `}</Script> <Script src={DATADOME_TAGS} strategy="lazyOnload" />
  • ddjskey使用客户端密钥NEXT_PUBLIC_DATADOME_CLIENT_SIDE_KEY(带NEXT_PUBLIC_前缀,才能被 Next.js 暴露到浏览器);
  • ddoptions.endpoint指向 DataDome 的 JS API 地址;
  • 两个脚本都采用lazyOnload策略,避免阻塞页面首屏渲染。

页面如何直观展示防护效果

components/headers.tsx 是页面上的调试组件:它对指定路径发起HEAD请求,读取响应的x-datadomex-datadome-latency两个头,连同实测延迟一并 JSON 展示。这样,访问/(受保护)与/omit(未受保护)时,你能直接看到:

  • 受保护页面多出x-datadome相关的响应头;
  • x-datadome-latency与页面整体延迟的差异,即边缘侧引入 DataDome 校验的额外开销(可结合浏览器 DevTools Network 面板进一步确认)。

部署与工程化配置

项目脚本在 package.json 中定义(dev/build/start/lint),依赖方面使用nextreactreact-dom以及@vercel/examples-ui示例 UI 库。vercel.json 中指定了pnpm turbo build作为构建命令,并用turbo-ignore实现"无相关变更不触发构建"的增量部署优化。

小结与生产实践建议

回顾本示例可以提炼出几条可直接复用的经验:

  1. 在边缘做防护:通过matcher精确控制保护范围,静态资源与无关路由直接放行,兼顾安全与性能;
  2. fail-open 超时策略Promise.race+ 默认 300ms 超时,防护服务异常时优雅降级,不阻塞正常流量;
  3. 密钥分级DATADOME_SERVER_SIDE_KEY只存在于服务端(中间件内使用),浏览器侧只暴露NEXT_PUBLIC_DATADOME_CLIENT_SIDE_KEY
  4. 响应头透传:DataDome 通过x-datadome-headers声明需要回写的头(含 cookie),并需注意公共后缀域名的 cookie 兼容问题;
  5. 演示辅助头按需取舍x-datadome-latency属于演示用途,生产环境应评估是否需要暴露。

如果你想在自己的 Next.js 应用中接入 DataDome,最直接的方式就是克隆本示例,配置好两个密钥环境变量,再按需调整middleware.ts中的matcher覆盖范围即可。

【免费下载链接】examplesEnjoy our curated collection of examples and solutions. Use these patterns to build your own robust and scalable applications.项目地址: https://gitcode.com/GitHub_Trending/examples1/examples

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询