Cloudflare验证码对接指南(Next.js)
在原来的项目中,我们在注册阶段使用的验证方式是六位图形验证码,但这种方式对于用户而言相对比较麻烦,且较为容易被攻破。因此,后来决定采用Cloudflare的验证码。在实际对接时发现网上在这部分的描述大同小异且都没有什么实际用途,cloudflare的官网也描述的非常模糊,造成了不小的麻烦,最后经过深挖文档和进行调研,终于获得了一个可行的对接方案。
首先,让我们来看一下我的实现效果:
这里不一定需要手动点击才算验证成功,我在Microsoft Edge浏览器下打开能够直接成功,而有的情况下需要进行一次手动点击才可以通过验证。
一、Cloudflare验证码的验证原理
Cloudflare 提供的轻量级验证码和无感知验证码的实现,主要依赖于其对用户和请求的智能分析。这些验证码的设计目的是在确保安全的前提下,尽量减少对用户的干扰和提升用户体验。以下是这些验证码的工作原理:
1. 行为分析与机器学习
Cloudflare 利用先进的行为分析和机器学习算法来评估用户请求的真实性和可信度。通过分析用户在页面上的行为模式,如鼠标移动、点击、滚动等,Cloudflare 能够判断请求是否来自人类用户。这些行为特征难以被自动化脚本准确模仿。
2. 设备指纹与浏览器验证
设备指纹技术通过收集并分析浏览器和设备信息(例如,用户代理、屏幕分辨率、时区设置、安装的插件等),生成一个独特的指纹。Cloudflare 可以通过对比设备指纹和已知的正常用户行为数据库,快速识别并区分合法用户与自动化脚本。
此外,Cloudflare 还使用浏览器验证技术,如执行一段 JavaScript 代码,验证请求是由真实的浏览器发起的。这些代码通常用于检查浏览器的特定行为和特性,自动化脚本很难模拟这些行为。
3.风险评分
Cloudflare 会为每个请求分配一个风险评分。这个评分基于多种因素,包括 IP 信誉、行为分析、设备指纹、地理位置等。对于低风险请求,Cloudflare 可以直接通过验证,用户无需额外操作。对于中高风险请求,则可能要求用户进行简单的点击验证或传统的 CAPTCHA 验证码。
4.无感知验证
无感知验证是 Cloudflare 提供的一种高级验证方式,用户无需进行任何额外的操作即可通过验证。其工作流程如下:
- 请求分析:当用户访问网站时,Cloudflare 会立即分析该请求,包括用户行为、设备指纹、网络流量模式等。
- 隐式验证:Cloudflare 在后台执行一段 JavaScript 代码,以确保请求来自真实的浏览器和正常的用户行为。
- 风险评估:基于分析结果和风险评分,Cloudflare 决定是否直接通过验证。如果通过,用户不会感知到任何验证过程。
5.人机交互式别
对于一些轻量级的点击验证码,Cloudflare 可能仅要求用户点击一次按钮来证明自己是人类。这种简单的交互结合了前面提到的行为分析和设备指纹,可以有效区分人类与自动化脚本。甚至在某些情况下,如果 Cloudflare 已经对用户的可信度进行了充分的验证,即使用户没有进行点击,系统也能自动判定其为合法请求。
二、Cloudflare实现流程
由于验证码是前端+后端联动实现的,我们将对此分别进行说明:
前端:
import React, { useEffect, useState } from 'react'; import nextConfig from "../../../next.config.mjs"; //该组件用于生成CF验证码 interface TurnstileProps { onVerify: (token: string) => void; } const Turnstile: React.FC<TurnstileProps> = ({ onVerify }) => { const [scriptLoaded, setScriptLoaded] = useState(false); useEffect(() => { const script = document.createElement('script'); script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'; script.async = true; script.defer = true; script.onload = () => { window.turnstileCallback = handleVerification; setScriptLoaded(true); // 标记脚本加载完成 }; document.body.appendChild(script); }, []); useEffect(() => { if (scriptLoaded) { // 当脚本加载完成时,初始化 Turnstile if (window.turnstile) { window.turnstile.render('#turnstile-container', { sitekey: '0x4AAAAAAAbYS5Shq7Off4zS', callback: handleVerification }); } } }, [scriptLoaded]); const handleVerification = (token: string) => { onVerify(token); }; if (!scriptLoaded) { return null; // 脚本加载未完成时不渲染组件 } return ( <div id="turnstile-container" className="cf-turnstile" data-sitekey={process.env.DATA_SITE_KEY} data-lang="en" data-callback="turnstileCallback" ></div> ); }; export default Turnstile;
这是一段next.js的代码,让我们来对其进行解释:
第一个 useEffect 在组件挂载时加载 Cloudflare Turnstile 脚本,并创建了一个 script 元素并设置其 src 属性指向 Turnstile API 的 URL。并将脚本设置为异步加载。在脚本加载完成后,将 handleVerification 函数设置为 window.turnstileCallback,并更新 scriptLoaded 状态为 true。
另一个useEffect
监听 scriptLoaded
状态,当脚本加载完成时执行。这么做的目的是防止在脚本未加载完成时就渲染组件,由于加载cloudflare的组件是需要一定时间的,如果不加入该useEffect,那么就会导致每次跳转到该页面时会无法显示该验证码组件,而只有刷新了之后才能够看到(对于十分熟悉next.js的同学而言这个一定是非常清楚的)。最后,检查 window.turnstile
是否存在,如果存在则调用其 render
方法初始化 Turnstile。render
方法接收两个参数:Turnstile 容器的 ID 和配置对象。配置对象包含 sitekey
和 callback
(验证成功后的回调函数)。
data-sitekey是在cloudflare中获得的密钥,填入即可。另外,这里的data-lang其实设置并没有用,根据我的测试,其无法显式指定语言,而会根据浏览器的默认语言来进行设置。换句话说,如果你设置的语言是英语,那么组件中的文字也会是英语,非常灵活。
后端:
相比于前端较为复杂的代码,后端就相对比较清晰:
private void registerVerify(UserDto userDto) { String turnstileToken = userDto.getTurnstileToken(); if (!StringUtils.hasText(turnstileToken)){ throw new CusobException(ResultCodeEnum.VERIFY_CODE_EMPTY); } String url = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; //验证CF是否通过了验证 Map<String, String> requestBody = new HashMap<>(); requestBody.put("secret", turnstileSecretKey); requestBody.put("response", turnstileToken); Map<String, Object> response = restTemplate.postForObject(url, requestBody, Map.class); if (!(response != null && Boolean.TRUE.equals(response.get("success")))) { throw new CusobException(ResultCodeEnum.VERIFY_CODE_WRONG); } String email = userDto.getEmail(); User userDb = this.getUserByEmail(email); if (userDb != null){ throw new CusobException(ResultCodeEnum.EMAIL_IS_REGISTERED); } }
这部分和CF验证码相关的内容已经加粗显示,其中turnstileSecretKey也是cloudflare提供的,属于静态常量,turnstileToken是前端返回的内容,用于进行后端校验。接着,使用restTemplate进行远程调用,访问CF的验证码验证接口,如果返回为true,则表明验证成功!