滑块验证完整实现教程(前端 + 后端 + Nginx 集成)

滑块验证完整实现教程(前端+后端+Nginx集成)

滑块验证的核心逻辑是:前端渲染滑块+缺口背景图,采集用户滑动轨迹;后端校验轨迹是否为真人行为(非机器匀速滑动),验证通过后生成时效token;Nginx拦截业务请求,校验token有效性后放行。以下是可直接落地的完整方案,包含前端、后端、部署全流程。

一、核心原理

  1. 前端:生成随机的背景图+缺口,监听鼠标/触摸滑动事件,采集滑动轨迹(时间戳、X/Y坐标、速度、加速度),滑动完成后将轨迹和缺口偏移量传给后端。
  2. 后端:校验轨迹特征(如滑动时长、速度波动、是否匀速、缺口偏移匹配度),真人轨迹会有“先快后慢/轻微抖动”,机器轨迹多为“匀速直线”;验证通过则生成短期有效token。
  3. Nginx:拦截业务请求,校验请求头/Cookie中的验证token,有效则放行,无效则重定向到滑块验证页面。

二、完整实现步骤

步骤1:环境准备

  • 前端:无需框架,原生HTML+JS即可(也可适配Vue/React);
  • 后端:Python 3.8+ + Flask + Pillow(生成验证图) + Redis(存储token/验证参数);
  • Nginx:确保包含ngx_http_auth_request_module模块(默认编译,nginx -V验证);

依赖安装:

# 后端依赖 pip install flask pillow redis requests # Redis(本地/云服务器,用于存储验证参数和token)# 参考安装:https://redis.io/docs/getting-started/installation/

步骤2:前端实现(滑块渲染+轨迹采集)

新建slider.html,实现滑块渲染、滑动监听、轨迹采集和验证请求:

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>滑块验证</title><style>/* 滑块容器样式 */.slider-container{width: 320px;height: 160px;margin: 50px auto;border: 1px solid #e5e5e5;border-radius: 8px;padding: 10px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);}.slider-bg{width: 100%;height: 120px;position: relative;border-radius: 4px;overflow: hidden;background: #f5f5f5;}.slider-gap{position: absolute;width: 40px;height: 40px;background: #fff;border: 1px solid #e5e5e5;box-shadow: 0 0 5px rgba(0,0,0,0.2);cursor: move;/* 缺口位置由后端返回,初始隐藏 */display: none;}.slider-bar{width: 100%;height: 30px;background: #f8f8f8;margin-top: 10px;border-radius: 15px;position: relative;cursor: pointer;}.slider-btn{width: 40px;height: 30px;background: #409eff;border-radius: 15px;position: absolute;top: 0;left: 0;box-shadow: 0 0 5px rgba(64,158,255,0.5);cursor: move;text-align: center;line-height: 30px;color: #fff;font-size: 12px;}.tips{text-align: center;margin-top: 10px;color: #666;font-size: 14px;}</style></head><body><divclass="slider-container"><divclass="slider-bg"id="sliderBg"><divclass="slider-gap"id="sliderGap"></div></div><divclass="slider-bar"id="sliderBar"><divclass="slider-btn"id="sliderBtn">→</div></div><divclass="tips"id="tips">请拖动滑块完成验证</div></div><script>// 核心变量const sliderBtn = document.getElementById('sliderBtn');const sliderBar = document.getElementById('sliderBar');const sliderGap = document.getElementById('sliderGap');const sliderBg = document.getElementById('sliderBg');const tips = document.getElementById('tips');let startX =0;// 滑动起始X坐标let isDragging =false;// 是否正在滑动let track =[];// 滑动轨迹:[{time: 时间戳, x: 坐标, y: 坐标}]let verifyId ='';// 本次验证的唯一ID(后端生成)let targetOffset =0;// 目标缺口偏移量(后端返回)// 1. 初始化:从后端获取验证图和缺口参数asyncfunctioninitVerify(){try{const res =awaitfetch('/api/slider/init');const data =await res.json();if(data.code ===200){ verifyId = data.data.verifyId; targetOffset = data.data.offset;// 目标偏移量(像素)// 设置背景图 sliderBg.style.background =`url(${data.data.bgImg}) no-repeat center/contain`;// 设置缺口位置 sliderGap.style.left =`${targetOffset}px`; sliderGap.style.top =`${data.data.top}px`; sliderGap.style.display ='block'; tips.textContent ='请拖动滑块完成验证'; tips.style.color ='#666';}}catch(e){ tips.textContent ='初始化失败,请刷新重试'; tips.style.color ='#f56c6c'; console.error('初始化失败:', e);}}// 2. 采集滑动轨迹(每10ms记录一次坐标和时间)functionrecordTrack(x, y){ track.push({ time: Date.now(), x: x, y: y });}// 3. 滑动结束:提交轨迹到后端验证asyncfunctionsubmitVerify(){if(track.length <5){// 轨迹过短,判定为机器 tips.textContent ='验证失败:滑动轨迹异常'; tips.style.color ='#f56c6c';resetSlider();return;}try{const res =awaitfetch('/api/slider/verify',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ verifyId: verifyId, track: track,// 滑动轨迹 finalOffset: sliderBtn.offsetLeft // 最终滑块偏移量})});const data =await res.json();if(data.code ===200){// 验证通过:设置token到Cookie(供Nginx校验) document.cookie =`slider_token=${data.data.token}; max-age=300; path=/`; tips.textContent ='验证通过!即将跳转...'; tips.style.color ='#67c23a';// 跳转到业务页面(或通知父页面)setTimeout(()=>{ window.location.href ='/';// 业务首页},1000);}else{ tips.textContent =`验证失败:${data.msg}`; tips.style.color ='#f56c6c';resetSlider();// 重新初始化验证setTimeout(initVerify,1000);}}catch(e){ tips.textContent ='验证请求失败,请重试'; tips.style.color ='#f56c6c';resetSlider(); console.error('验证提交失败:', e);}}// 4. 重置滑块和轨迹functionresetSlider(){ sliderBtn.style.left ='0px'; isDragging =false; track =[];}// 5. 绑定滑动事件// 鼠标按下/触摸开始 sliderBtn.addEventListener('mousedown',(e)=>{ isDragging =true; startX = e.clientX - sliderBtn.offsetLeft;// 开始记录轨迹recordTrack(sliderBtn.offsetLeft, e.clientY);// 每10ms持续记录轨迹 trackTimer =setInterval(()=>{if(isDragging){recordTrack(sliderBtn.offsetLeft, e.clientY);}},10);});// 触摸适配(移动端) sliderBtn.addEventListener('touchstart',(e)=>{ isDragging =true; startX = e.touches[0].clientX - sliderBtn.offsetLeft;recordTrack(sliderBtn.offsetLeft, e.touches[0].clientY); trackTimer =setInterval(()=>{if(isDragging){recordTrack(sliderBtn.offsetLeft, e.touches[0].clientY);}},10);});// 鼠标移动/触摸移动 document.addEventListener('mousemove',(e)=>{if(!isDragging)return;const newX = e.clientX - startX;// 限制滑块范围:0 ~ 滑块条宽度 - 滑块宽度const maxX = sliderBar.offsetWidth - sliderBtn.offsetWidth;const finalX = Math.max(0, Math.min(newX, maxX)); sliderBtn.style.left =`${finalX}px`;}); document.addEventListener('touchmove',(e)=>{if(!isDragging)return;const newX = e.touches[0].clientX - startX;const maxX = sliderBar.offsetWidth - sliderBtn.offsetWidth;const finalX = Math.max(0, Math.min(newX, maxX)); sliderBtn.style.left =`${finalX}px`;});// 鼠标松开/触摸结束 document.addEventListener('mouseup',()=>{if(!isDragging)return; isDragging =false;clearInterval(trackTimer);submitVerify();// 提交验证}); document.addEventListener('touchend',()=>{if(!isDragging)return; isDragging =false;clearInterval(trackTimer);submitVerify();});// 页面加载时初始化验证 window.onload = initVerify;</script></body></html>

步骤3:后端实现(验证图生成+轨迹校验)

新建slider_server.py,实现3个核心接口:初始化验证(生成背景图/缺口)、校验轨迹、验证token(供Nginx调用):

import os import uuid import random import time import json from PIL import Image, ImageDraw from flask import Flask, jsonify, request, send_file, make_response import redis import hashlib app = Flask(__name__)# Redis配置(存储验证参数和token,过期时间5分钟) redis_client = redis.Redis( host='127.0.0.1', port=6379, db=0, decode_responses=True, password=''# 如有密码请填写)# 配置:验证图存储路径(临时) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) IMG_DIR = os.path.join(BASE_DIR,'slider_imgs')ifnot os.path.exists(IMG_DIR): os.makedirs(IMG_DIR)# -------------------------- 核心工具函数 --------------------------# 1. 生成随机验证图(带缺口)defgenerate_slider_img():# 生成背景图(随机颜色+随机线条,模拟真实图片) width, height =300,100# 背景图尺寸 bg_img = Image.new('RGB',(width, height),(random.randint(230,255), random.randint(230,255), random.randint(230,255))) draw = ImageDraw.Draw(bg_img)# 画随机线条(增加干扰)for _ inrange(5): x1 = random.randint(0, width) y1 = random.randint(0, height) x2 = random.randint(0, width) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=(random.randint(100,200), random.randint(100,200), random.randint(100,200)), width=2)# 生成缺口(随机位置:X轴 80~220,Y轴 30~70) gap_width, gap_height =40,40 gap_x = random.randint(80,220)# 缺口X偏移(目标偏移量) gap_y = random.randint(30,70)# 缺口Y偏移# 画缺口(白色矩形,模拟缺失) draw.rectangle((gap_x, gap_y, gap_x+gap_width, gap_y+gap_height), fill=(255,255,255))# 保存背景图 img_name =f'{uuid.uuid4()}.png' img_path = os.path.join(IMG_DIR, img_name) bg_img.save(img_path)return{'img_path': img_path,'img_name': img_name,'offset': gap_x,# 缺口X偏移量(目标值)'top': gap_y # 缺口Y偏移量}# 2. 校验滑动轨迹(核心:区分真人/机器)defcheck_track(track, final_offset, target_offset):""" :param track: 滑动轨迹列表 [{time, x, y}] :param final_offset: 用户最终滑动的偏移量 :param target_offset: 目标缺口偏移量 :return: (是否通过, 失败原因) """# 1. 偏移量校验:误差±5像素内 offset_error =abs(final_offset - target_offset)if offset_error >5:returnFalse,f'偏移量错误(目标{target_offset},实际{final_offset})'# 2. 轨迹长度校验:至少5个点(避免瞬间滑动)iflen(track)<5:returnFalse,'轨迹过短'# 3. 滑动时长校验:0.5~3秒(真人滑动不会太快/太慢) total_time = track[-1]['time']- track[0]['time'] total_time_s = total_time /1000if total_time_s <0.5or total_time_s >3:returnFalse,f'滑动时长异常({total_time_s:.2f}秒)'# 4. 速度波动校验:真人速度有波动,机器多匀速 speeds =[]for i inrange(1,len(track)): time_diff = track[i]['time']- track[i-1]['time'] x_diff = track[i]['x']- track[i-1]['x']if time_diff ==0:continue speed = x_diff / time_diff # 像素/毫秒 speeds.append(speed)# 计算速度标准差(波动值):<0.01 判定为匀速(机器)iflen(speeds)<3:returnFalse,'轨迹点数不足' avg_speed =sum(speeds)/len(speeds) std_speed =(sum([(s - avg_speed)**2for s in speeds])/len(speeds))**0.5if std_speed <0.01:returnFalse,'滑动速度匀速(疑似机器)'# 5. 轨迹Y轴校验:真人滑动Y轴有轻微波动,机器Y轴固定 y_values =[p['y']for p in track] y_max =max(y_values) y_min =min(y_values)if y_max - y_min <2:returnFalse,'Y轴无波动(疑似机器)'returnTrue,'验证通过'# 3. 生成验证token(供Nginx校验)defgenerate_token(verify_id): token = hashlib.md5(f'{verify_id}_{int(time.time())}_slider_secret'.encode()).hexdigest()# 存储token到Redis,过期5分钟 redis_client.setex(f'slider_token:{token}',300,'valid')return token # -------------------------- 接口实现 --------------------------# 1. 初始化验证接口(生成背景图+缺口参数)@app.route('/api/slider/init', methods=['GET'])definit_slider():try:# 生成验证图 img_info = generate_slider_img()# 生成唯一验证ID verify_id =str(uuid.uuid4())# 存储验证参数到Redis(过期5分钟) redis_client.setex(f'slider_verify:{verify_id}',300, json.dumps({'offset': img_info['offset'],'top': img_info['top'],'img_name': img_info['img_name']}))# 返回结果(图片路径为访问路径)return jsonify({'code':200,'msg':'初始化成功','data':{'verifyId': verify_id,'offset': img_info['offset'],'top': img_info['top'],'bgImg':f'/api/slider/img/{img_info["img_name"]}'# 图片访问接口}})except Exception as e:return jsonify({'code':500,'msg':f'初始化失败:{str(e)}'}),500# 2. 验证图片访问接口@app.route('/api/slider/img/<img_name>', methods=['GET'])defget_slider_img(img_name): img_path = os.path.join(IMG_DIR, img_name)ifnot os.path.exists(img_path):return jsonify({'code':404,'msg':'图片不存在'}),404# 返回图片,并设置缓存(短期) response = make_response(send_file(img_path, mimetype='image/png')) response.headers['Cache-Control']='max-age=300'return response # 3. 滑块验证接口(校验轨迹)@app.route('/api/slider/verify', methods=['POST'])defverify_slider():try: data = request.get_json() verify_id = data.get('verifyId') track = data.get('track',[]) final_offset = data.get('finalOffset',0)# 1. 校验参数ifnot verify_id ornot track or final_offset isNone:return jsonify({'code':400,'msg':'参数缺失'}),400# 2. 获取Redis中的验证参数 verify_info_str = redis_client.get(f'slider_verify:{verify_id}')ifnot verify_info_str:return jsonify({'code':400,'msg':'验证已过期,请刷新'}),400 verify_info = json.loads(verify_info_str) target_offset = verify_info['offset']# 3. 校验轨迹 is_pass, msg = check_track(track, final_offset, target_offset)ifnot is_pass:return jsonify({'code':403,'msg': msg}),403# 4. 验证通过:生成token,删除验证参数(防止复用) token = generate_token(verify_id) redis_client.delete(f'slider_verify:{verify_id}')return jsonify({'code':200,'msg':'验证通过','data':{'token': token}})except Exception as e:return jsonify({'code':500,'msg':f'验证失败:{str(e)}'}),500# 4. Nginx校验token接口(内部调用)@app.route('/api/slider/check_token', methods=['GET'])defcheck_token():# 从Cookie获取token token = request.cookies.get('slider_token')ifnot token:return'',401# 无token,验证失败# 校验token是否有效 is_valid = redis_client.get(f'slider_token:{token}')if is_valid =='valid':# 验证通过,删除token(防止复用) redis_client.delete(f'slider_token:{token}')return'',200else:return'',401# token无效if __name__ =='__main__': app.run(host='0.0.0.0', port=5001, debug=False)

步骤4:Nginx配置(拦截请求+校验token)

修改nginx.conf,实现“拦截业务请求→校验滑块token→放行/重定向”:

http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # 后端滑块验证服务 upstream slider_server { server 127.0.0.1:5001; } server { listen 80; server_name localhost; # 业务根路径(需要验证的路径) location / { # 1. 校验滑块token(内部子请求) auth_request /api/slider/check_token; # 2. token无效/缺失 → 重定向到滑块验证页面 error_page 401 = @redirect_slider; # 3. token有效 → 放行到业务服务(替换为你的实际业务地址) proxy_pass http://127.0.0.1:8080; # 你的业务服务地址 } # 重定向到滑块验证页面 location @redirect_slider { rewrite ^/(.*)$ /slider.html permanent; } # 滑块验证相关接口 → 转发到后端服务 location /api/slider/ { proxy_pass http://slider_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 滑块验证页面(静态文件) location /slider.html { root /path/to/your/static/files; # 替换为slider.html所在目录 expires 0; # 禁止缓存 } # Nginx内部调用的token校验接口(禁止外部访问) location = /api/slider/check_token { internal; proxy_pass http://slider_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } } 

步骤5:启动与测试

  1. 启动Redisredis-server(确保端口6379);
  2. 启动后端服务python slider_server.py
  3. 放置前端文件:将slider.html放到Nginx配置的静态文件目录;
  4. 重启Nginxnginx -s reload
  5. 测试:访问http://localhost → 自动重定向到滑块验证页面 → 拖动滑块完成验证 → 验证通过后跳转到业务页面。

三、防破解优化(关键!避免被机器破解)

1. 前端优化

  • 轨迹加密:将轨迹数据用AES加密后传输(避免抓包篡改);
  • 混淆JS:对滑动事件的JS代码进行混淆(防止逆向分析);
  • 禁用模拟:检测是否为模拟器/自动化工具(如检测webdriver标识);
  • 动态样式:滑块样式随机变化(颜色、大小、形状),避免固定模板。

2. 后端优化

  • 动态阈值:根据IP/设备调整校验阈值(如高频验证的IP提高校验严格度);
  • 防重放:验证ID仅能使用一次,校验后立即删除;
  • 图片增强:验证图加入随机水印、扭曲、噪点(防止图像识别破解);
  • 风控结合:结合IP黑名单、设备指纹(如浏览器指纹)、访问频率限制。

3. 部署优化

  • 分布式部署:Redis使用集群,支持多实例后端服务;
  • 日志监控:记录验证失败日志,分析异常IP/设备,及时调整策略。

频率限制:Nginx配置limit_req_module限制验证接口请求频率(如每秒1次);

limit_req_zone $binary_remote_addr zone=slider:10m rate=1r/s; location /api/slider/ { limit_req zone=slider burst=2 nodelay; proxy_pass http://slider_server; } 

四、扩展适配

1. 移动端适配

  • 前端已兼容touch事件,只需调整样式适配移动端屏幕;
  • 优化滑块大小(如宽度280px,适配手机屏幕)。

2. 集成第三方滑块(简化开发)

如果不想自研,可直接集成成熟的第三方滑块:

  • 极验(GEETEST):https://www.geetest.com/ (文档完善,支持私有化部署);
  • 顶象:https://www.dingxiang-inc.com/ (风控能力强,适合高安全场景);
  • 集成方式:替换前端滑块代码为第三方SDK,后端调用第三方校验接口即可。

五、常见问题排查

  1. 验证图无法显示:检查IMG_DIR路径是否正确,Nginx是否有权限访问图片目录;
  2. 轨迹校验失败:调整check_track函数的阈值(如速度标准差、滑动时长);
  3. Redis连接失败:检查Redis地址、端口、密码是否正确,确保Redis服务运行;
  4. Nginx重定向循环:确保/api/slider/接口不被auth_request拦截(Nginx配置中排除)。

总结

滑块验证的核心是轨迹特征校验(区分真人/机器),而非单纯的偏移量匹配。自研方案适合中小场景,若追求更高安全性/更低开发成本,建议集成极验、顶象等第三方滑块服务。实际部署时,需结合风控策略(IP、设备、频率),构建多层防御体系,平衡安全与用户体验。

Read more

YOLO12目标检测WebUI效果展示:高清图片检测实例

YOLO12目标检测WebUI效果展示:高清图片检测实例 1. 引言:当AI视觉遇见极致清晰度 想象一下这样的场景:你有一张高清的城市街景照片,画面中行人匆匆、车辆穿梭、宠物嬉戏。你想知道这张照片里到底有多少个物体,它们分别是什么,位置在哪里——这就是目标检测技术的用武之地。 YOLO12作为YOLO系列的最新成员,在2025年初由纽约州立大学布法罗分校与中国科学院大学团队联合发布。这个以注意力机制为核心的实时目标检测模型,不仅延续了YOLO系列"你只看一次"的极速特性,更在检测精度上实现了显著提升。 今天,我们将通过实际的高清图片检测案例,全方位展示YOLO12 WebUI的惊艳效果。无论你是技术开发者还是AI应用爱好者,都能直观感受到现代目标检测技术的强大能力。 2. YOLO12核心能力概览 2.1 技术特点解析 YOLO12采用创新的注意力机制设计,让模型能够更智能地聚焦于图像中的关键区域。与前辈版本相比,YOLO12在保持毫秒级推理速度的同时,大幅提升了小目标检测和复杂场景下的识别准确率。 模型支持完整的80类COCO数据集物体检测,涵盖从日常物品到特殊场

告别代码,迎接代理:Claude Code、OpenCode、OpenClaw等六大AI工具全面解析

如果你最近关注科技圈,一定会被一个词刷屏:AI代理(AI Agent)。从2024年底到2026年初,AI的发展已经不再局限于聊天窗口里的文字游戏,而是真正开始操控电脑、编写代码、甚至替我们“干活”。 Anthropic、OpenAI以及开源社区接连丢出一系列重磅产品:Claude Code、Cowork、OpenCode、OpenWork、OpenClaw、Codex……这些名字听起来既有重复又相互关联,它们到底有什么区别?哪个才是普通人也用得上的工具? 今天,我们就来一次性梳理这七大项目,看看它们分别是什么,以及它们如何共同指向一个“AI执行一切”的未来。 一、六大“工具”逐个看 在深入对比之前,我们先分别认识一下这六位主角。它们虽然都顶着“AI工具”的头衔,但出身、能力和使命却大相径庭。 1. Claude Code:披着编程外衣的通用Agent 出身:Anthropic(2024年底推出) 核心定位:终端里的自主AI助手。 Claude

OpenClaw最强Agent Skills推荐:从“会聊“到“会干“,你的AI只差一个插件(附完整安装教程)

文章目录 * 📌 引言 * 第一部分:Skills到底是什么? * 🤔 概念解析 * 🧩 Skill的组成 * 📦 Skills的获取渠道 * 第二部分:为什么你的OpenClaw必须装Skills? * 理由1:原生能力极其有限 * 理由2:Skills让AI"学会用工具" * 理由3:生态是OpenClaw的最大优势 * 第三部分:2026年十大热门Skills推荐 * 🥇 第1名:agent-browser —— 浏览器自动化工具 * 🥈 第2名:tavily-search —— 实时联网搜索 * 🥉 第3名:self-improving-agent —— "会成长的AI" * 第4名:find-skills —— 技能发现与管理神器 * 第5名:email-management —— 邮件管理助手 * 第6名:messaging-integration —— 通讯工具集成 * 第7名:database-query —— 自然语言数据库查询 * 第8名:n8n-workfl

告别手动改配置!CC-Switch:你的AI编码助手“万能遥控器”

告别手动改配置!CC-Switch:你的AI编码助手“万能遥控器”

作为一名天天和代码打交道的开发者,你一定没少用 Claude Code、Codex 或 Gemini CLI 这些 AI 编码助手。它们确实能让你效率飞起,但有一个问题,简直让人抓狂——配置管理。 想象一下这个场景:你在 A 项目用 Anthropic 官方接口,B 项目用代理中转,C 项目想试试某家“神秘”供应商……于是你开始了“手艺人”日常:打开 settings.json,小心翼翼地改 BASE_URL,粘贴新的 API_KEY,生怕一个多余的空格让整个 CLI 崩掉。 烦不烦?太烦了! 今天,我就来给你安利一个能让你彻底告别手动配置的“神器”——CC-Switch。它就像 AI