# 滑块验证码后端验证实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 为现有的纯前端滑块验证码组件添加后端数据验证,提高安全性,防止前端绕过攻击。 **Architecture:** 采用前后端分离架构。后端负责生成验证码数据(包括加密的缺口位置)和验证用户提交的滑动位置。前端负责UI交互和与后端API通信。使用token机制确保验证码的一次性使用。 **Tech Stack:** - 前端:React, TypeScript, Ant Design, Axios - 后端:Django REST Framework (假设), Python, 加密库(如hashlib, hmac) --- ## 文件结构 ### 前端文件 - Modify: `src/components/SliderCaptcha/SliderCaptcha.tsx` - 修改为从后端获取验证码数据并发送验证请求 - Modify: `src/components/SliderCaptcha/captchaUtils.ts` - 移除本地生成逻辑,保留工具函数 - Modify: `src/utils/request.ts` - 添加验证码API请求方法 - Modify: `src/pages/SliderCaptchaDemo/SliderCaptchaDemo.tsx` - 更新演示页面以使用新的后端验证 ### 后端文件(设计指导) - Create: `captcha/views.py` - 验证码API视图 - Create: `captcha/serializers.py` - 数据序列化器 - Create: `captcha/urls.py` - URL路由 - Create: `captcha/utils.py` - 加密和验证工具函数 --- ## Task 1: 后端API设计与实现 **Files:** - Create: `captcha/views.py` - Create: `captcha/serializers.py` - Create: `captcha/urls.py` - Create: `captcha/utils.py` - [ ] **Step 1: 设计验证码数据生成算法** 后端需要生成验证码数据,包括: 1. 随机背景图片URL(或使用预设图片库) 2. 缺口位置(x, y坐标) 3. 加密的token(包含缺口位置信息,带签名防篡改) 4. 滑块图片(可由前端根据缺口位置生成,或后端提供) 加密方案: - 使用HMAC-SHA256对缺口位置进行签名 - token格式:`base64(x,y):signature` - 密钥使用Django的SECRET_KEY ```python # captcha/utils.py import hmac import hashlib import base64 import json from django.conf import settings def generate_captcha_token(x: int, y: int) -> str: """生成验证码token,包含加密的缺口位置""" data = json.dumps({'x': x, 'y': y}).encode('utf-8') signature = hmac.new( settings.SECRET_KEY.encode('utf-8'), data, hashlib.sha256 ).hexdigest() token_data = base64.urlsafe_b64encode(data).decode('utf-8') return f"{token_data}:{signature}" def verify_captcha_token(token: str, x: int, y: int, tolerance: int = 5) -> bool: """验证token和用户提交的坐标""" try: token_data, signature = token.split(':') data = base64.urlsafe_b64decode(token_data) # 验证签名 expected_signature = hmac.new( settings.SECRET_KEY.encode('utf-8'), data, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(signature, expected_signature): return False # 解析原始坐标 original = json.loads(data) # 验证用户坐标是否在容差范围内 return (abs(original['x'] - x) <= tolerance and abs(original['y'] - y) <= tolerance) except Exception: return False ``` - [ ] **Step 2: 创建验证码API视图** ```python # captcha/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from django.core.cache import cache import random from .utils import generate_captcha_token, verify_captcha_token class GenerateCaptchaView(APIView): """生成验证码数据""" def get(self, request): # 生成随机缺口位置(实际应用中应根据图片尺寸调整) x = random.randint(50, 250) y = random.randint(20, 120) # 生成token token = generate_captcha_token(x, y) # 缓存token,有效期5分钟 cache_key = f"captcha:{token}" cache.set(cache_key, {'x': x, 'y': y}, 300) # 返回数据(实际应用中应返回真实图片URL) return Response({ 'token': token, 'bg_image': '/static/captcha/bg1.jpg', # 示例背景图 'gap_x': x, 'gap_y': y, 'width': 320, 'height': 160, 'gap_size': 40 }) class VerifyCaptchaView(APIView): """验证用户提交的滑块位置""" def post(self, request): token = request.data.get('token') user_x = request.data.get('x') user_y = request.data.get('y') if not all([token, user_x is not None, user_y is not None]): return Response( {'error': '缺少必要参数'}, status=status.HTTP_400_BAD_REQUEST ) # 从缓存获取原始数据(可选,用于额外验证) cache_key = f"captcha:{token}" cached_data = cache.get(cache_key) if not cached_data: return Response( {'error': '验证码已过期或无效'}, status=status.HTTP_400_BAD_REQUEST ) # 验证token和坐标 if verify_captcha_token(token, int(user_x), int(user_y)): # 验证成功,删除缓存(一次性使用) cache.delete(cache_key) return Response({'success': True, 'message': '验证成功'}) else: return Response( {'success': False, 'message': '验证失败'}, status=status.HTTP_400_BAD_REQUEST ) ``` - [ ] **Step 3: 创建URL路由** ```python # captcha/urls.py from django.urls import path from .views import GenerateCaptchaView, VerifyCaptchaView urlpatterns = [ path('generate/', GenerateCaptchaView.as_view(), name='generate-captcha'), path('verify/', VerifyCaptchaView.as_view(), name='verify-captcha'), ] ``` - [ ] **Step 4: 在Django项目中注册路由** 在项目的`urls.py`中添加: ```python from django.urls import path, include urlpatterns = [ # ... 其他路由 path('api/captcha/', include('captcha.urls')), ] ``` - [ ] **Step 5: 创建序列化器(可选,用于数据验证)** ```python # captcha/serializers.py from rest_framework import serializers class VerifyCaptchaSerializer(serializers.Serializer): token = serializers.CharField(required=True) x = serializers.IntegerField(required=True) y = serializers.IntegerField(required=True) ``` - [ ] **Step 6: 提供示例背景图片** 在`static/captcha/`目录下放置几张背景图片,用于验证码显示。 --- ## Task 2: 前端API集成 **Files:** - Modify: `src/utils/request.ts` - Modify: `src/components/SliderCaptcha/SliderCaptcha.tsx` - Modify: `src/components/SliderCaptcha/captchaUtils.ts` - [ ] **Step 1: 在request.ts中添加验证码API方法** 在`api_request`对象中添加captcha模块: ```typescript // 在request.ts的api_request对象中添加 const captcha = { generate: () => request.public.get("/api/captcha/generate/"), verify: (data: { token: string; x: number; y: number }) => request.public.post("/api/captcha/verify/", data), }; export const api_request = { // ... 其他模块 captcha, // 添加这一行 }; ``` - [ ] **Step 2: 修改SliderCaptcha组件,使用后端API** 修改`SliderCaptcha.tsx`,主要变更: 1. `loadCaptcha`函数改为从后端获取数据 2. `doVerify`函数改为发送验证请求到后端 3. 添加token状态管理 4. 处理API错误 ```typescript // SliderCaptcha.tsx 关键修改部分 const SliderCaptcha: React.FC = ({ width = 320, height = 160, gapSize = 40, tolerance = 5, onSuccess, onFail, onRefresh, className = "", style = {}, }) => { const [status, setStatus] = useState("loading"); const [captchaData, setCaptchaData] = useState(null); const [sliderX, setSliderX] = useState(0); const [message, setMessage] = useState("向右滑动完成验证"); const [token, setToken] = useState(""); // 新增token状态 // ... 其他状态和ref const loadCaptcha = useCallback(async () => { setStatus("loading"); setSliderX(0); setMessage("向右滑动完成验证"); try { // 从后端获取验证码数据 const response = await api_request.captcha.generate(); const data = response.data; // 使用后端返回的数据生成前端验证码 const captchaResult = await generateCaptcha( data.bg_image, data.width, data.height, data.gap_size ); setCaptchaData(captchaResult); setToken(data.token); // 保存token setStatus("ready"); } catch (error) { console.error("获取验证码失败:", error); setMessage("验证码加载失败,请刷新重试"); setStatus("fail"); } }, []); const doVerify = useCallback(async () => { if (!isDraggingRef.current || !captchaData || !token) return; isDraggingRef.current = false; setStatus("verifying"); try { // 发送到后端验证 const response = await api_request.captcha.verify({ token, x: sliderX, y: captchaData.gapY // 如果后端需要Y坐标 }); if (response.data.success) { setStatus("success"); setMessage("验证成功"); onSuccess?.(); } else { setStatus("fail"); setMessage("验证失败,请重试"); setSliderX(0); onFail?.(); setTimeout(() => { handleRefresh(); }, 1000); } } catch (error) { console.error("验证请求失败:", error); setStatus("fail"); setMessage("验证失败,请重试"); setSliderX(0); onFail?.(); setTimeout(() => { handleRefresh(); }, 1000); } }, [captchaData, sliderX, token, onSuccess, onFail]); // ... 其他代码保持不变 }; ``` - [ ] **Step 3: 更新captchaUtils.ts** 移除本地生成逻辑,但保留`generateCaptcha`函数用于前端渲染。实际上,我们可以保留现有函数,因为它只是用于生成Canvas图像,不涉及安全逻辑。 但需要修改`getRandomImage`函数,因为图片现在由后端提供: ```typescript // captchaUtils.ts 修改 /** * 获取验证码图片(现在由后端提供,此函数可保留用于兼容) */ export const getRandomImage = (width: number, height: number): string => { // 实际应用中,这个URL应该来自后端API // 这里保留作为fallback const seed = randomInt(1, 1000); return `https://picsum.photos/seed/${seed}/${width}/${height}`; }; ``` - [ ] **Step 4: 更新演示页面** 修改`SliderCaptchaDemo.tsx`,展示后端验证的使用方式,并更新说明文档。 --- ## Task 3: 安全增强与测试 **Files:** - Modify: `src/components/SliderCaptcha/SliderCaptcha.tsx` - Create: 测试用例 - [ ] **Step 1: 添加请求频率限制** 在前端添加请求间隔限制,防止暴力破解: ```typescript // SliderCaptcha.tsx 中添加 const lastVerifyTime = useRef(0); const VERIFY_COOLDOWN = 1000; // 1秒冷却时间 const doVerify = useCallback(async () => { const now = Date.now(); if (now - lastVerifyTime.current < VERIFY_COOLDOWN) { setMessage("操作过于频繁"); return; } lastVerifyTime.current = now; // ... 验证逻辑 }, []); ``` - [ ] **Step 2: 添加错误处理和重试机制** ```typescript // SliderCaptcha.tsx 中添加重试逻辑 const [retryCount, setRetryCount] = useState(0); const MAX_RETRIES = 3; const doVerify = useCallback(async () => { // ... 验证逻辑 if (!response.data.success) { if (retryCount < MAX_RETRIES) { setRetryCount(prev => prev + 1); setMessage(`验证失败,还有${MAX_RETRIES - retryCount}次机会`); } else { setMessage("验证次数已用完,请刷新页面"); setStatus("fail"); } } }, [retryCount]); ``` - [ ] **Step 3: 后端安全增强** 1. 添加请求频率限制(Django Ratelimit) 2. 记录验证尝试次数 3. 添加IP黑名单机制 4. 使用HTTPS传输 ```python # 后端安全增强示例 from django.core.cache import cache from django_ratelimit.decorators import ratelimit class VerifyCaptchaView(APIView): @ratelimit(key='ip', rate='5/m', method='POST') def post(self, request): # ... 验证逻辑 pass ``` - [ ] **Step 4: 编写单元测试** 为后端API编写测试用例,确保: 1. token生成和验证的正确性 2. 过期token的处理 3. 错误坐标的验证 4. 频繁请求的限制 --- ## Task 4: 部署与监控 **Files:** - Modify: Django设置文件 - Create: 监控脚本 - [ ] **Step 1: 配置缓存后端** 确保Django使用Redis或Memcached作为缓存后端,用于存储验证码token: ```python # settings.py CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/1', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', } } } ``` - [ ] **Step 2: 添加日志记录** 记录验证码生成和验证事件,用于监控和调试: ```python # views.py 中添加日志 import logging logger = logging.getLogger(__name__) class GenerateCaptchaView(APIView): def get(self, request): logger.info(f"验证码生成: IP={request.META.get('REMOTE_ADDR')}") # ... 生成逻辑 class VerifyCaptchaView(APIView): def post(self, request): logger.info(f"验证码验证: IP={request.META.get('REMOTE_ADDR')}, token={token[:10]}...") # ... 验证逻辑 ``` - [ ] **Step 3: 监控和告警** 设置监控指标: 1. 验证码生成频率 2. 验证成功率 3. 失败尝试次数 4. 异常请求模式 --- ## 执行选项 **计划已保存到 `docs/plans/2026-06-13-slider-captcha-backend-verification.md`。两种执行方式:** **1. Subagent-Driven(推荐)** - 我为每个Task分配一个子代理,任务间进行审查,快速迭代 **2. Inline Execution** - 在当前会话中执行任务,使用executing-plans,批量执行并设置检查点 **请选择执行方式?** **如果选择Subagent-Driven:** - **必需子技能:** 使用superpowers:subagent-driven-development - 每个Task一个子代理 + 两阶段审查 **如果选择Inline Execution:** - **必需子技能:** 使用superpowers:executing-plans - 批量执行并设置检查点供审查 --- ## 注意事项 1. **后端实现需要Django项目配合**:本计划假设后端使用Django REST Framework,需要后端开发人员配合实现。 2. **图片资源**:需要准备验证码背景图片,或使用图片生成服务。 3. **安全性考虑**:务必使用HTTPS,保护SECRET_KEY,定期轮换密钥。 4. **性能优化**:考虑使用CDN加速图片加载,缓存验证码数据。 5. **用户体验**:添加加载动画、错误提示、重试机制。