15 KiB
15 KiB
滑块验证码系统使用指南
概述
滑块验证码是一种基于用户交互行为的验证机制,通过让用户拖动滑块到指定位置来判断操作是否为真人发起。本系统包含三个核心部分:
- 核心工具模块 (
utils/slider_captcha.py) - 验证码生成、位置验证、轨迹分析 - API接口层 (
user/views/slider_captcha.py) - 提供RESTful API供前端调用 - 前端交互组件 (
static/js/slider-captcha.js) - 用户拖拽交互界面
系统架构
┌─────────────────┐ GET /slider-captcha/generate/ ┌─────────────────┐
│ 前端浏览器 │ ─────────────────────────────────────> │ Django后端 │
│ (HTML/Canvas) │ │ (APIView) │
│ │ 返回: {captcha_key, bg_image, │ │
│ │ slider_image, y_position} │ │
└────────┬────────┘ <──────────────────────────────────────┴────────┬────────┘
│ │
│ 用户拖拽滑块 │ Redis缓存
│ │ captcha_key →
│ POST /slider-captcha/verify/ │ {x_position,
│ {captcha_key, x_position, trajectory: [...]} │ timestamp}
│ │
v v
┌─────────────────┐ ─────────────────────────────────────> ┌─────────────────┐
│ 前端浏览器 │ 验证结果: {verified: true/false} │ Django后端 │
│ (JS组件) │ │ (utils模块) │
│ │ <────────────────────────────────────── │ 位置验证 │
│ │ │ 轨迹分析 │
└─────────────────┘ └─────────────────┘
API 接口文档
1. 生成滑块验证码
请求
GET /user/slider-captcha/generate/
Content-Type: application/json
响应 (200 OK)
{
"code": 0,
"message": "success",
"data": {
"captcha_key": "550e8400-e29b-41d4-a716-446655440000",
"bg_image": "data:image/png;base64,iVBORw0KGgoAAAANS...",
"slider_image": "data:image/png;base64,iVBORw0KGgoAAAANS...",
"y_position": 50
}
}
字段说明:
captcha_key: 验证码唯一标识,用于后续验证请求。UUID格式bg_image: 背景图片。base64编码的PNG格式,data URIslider_image: 滑块图片。base64编码的PNG格式,需要用户拖动到背景图的凹槽位置y_position: 滑块在背景图中的垂直位置(像素)
2. 验证滑块位置
请求
POST /user/slider-captcha/verify/
Content-Type: application/json
{
"captcha_key": "550e8400-e29b-41d4-a716-446655440000",
"x_position": 150,
"trajectory": [
{"x": 0, "y": 150, "t": 0},
{"x": 50, "y": 152, "t": 100},
{"x": 100, "y": 148, "t": 200},
{"x": 150, "y": 150, "t": 300}
]
}
请求参数:
captcha_key(必填): 从generate接口获取的验证码标识x_position(必填, integer): 用户拖动滑块到的最终水平位置(像素)trajectory(可选, array): 拖拽过程轨迹点数组,用于机器人检测- 每个轨迹点包含
x(水平位置),y(垂直位置),t(时间戳,毫秒)
- 每个轨迹点包含
响应 (200 OK)
{
"code": 0,
"message": "success",
"data": {
"verified": true
}
}
错误响应 (400 Bad Request)
{
"code": 20022,
"message": "缺少必要参数",
"data": null
}
验证码过期错误响应:
{
"code": 20021,
"message": "验证码已过期,请重新获取",
"data": null
}
后端验证逻辑详解
1. 位置验证
from utils.slider_captcha import verify_slider_captcha
# 验证滑块位置
result = verify_slider_captcha(captcha_key, x_position)
# result: True (位置在误差范围内) 或 False (位置错误)
机制:
- 系统允许±5像素的误差容差(
TOLERANCE = 5) - 正确位置存储在Redis中,key格式为
slider_captcha_{captcha_key} - 验证成功后缓存立即删除(一次性使用)
- 验证码有效期为5分钟(
CAPTCHA_TIMEOUT = 300秒)
2. 轨迹分析(防机器人)
from utils.slider_captcha import verify_slider_trajectory
# 带轨迹验证
trajectory = [
{'x': 0, 'y': 150, 't': 0},
{'x': 50, 'y': 152, 't': 100},
{'x': 100, 'y': 148, 't': 200},
{'x': 150, 'y': 151, 't': 300},
]
result = verify_slider_trajectory(captcha_key, x_position, trajectory)
# result: True (轨迹符合人类操作特征) 或 False (轨迹可疑)
检测原理:
- 速度变化检测 - 人类操作有自然的加速/减速,机器人操作速度恒定
- 微小停顿检测 - 人类操作过程中常有毫秒级的微小停顿
- 轨迹点数量 - 至少需要5个轨迹点(
TRAJECTORY_MIN_POINTS = 5) - 时间范围检查 - 总操作时间不超过10秒(
TRAJECTORY_MAX_DURATION = 10000毫秒)
前端集成指南
1. 引入静态资源
在需要滑块验证码的页面中引入:
<link rel="stylesheet" href="{% static 'css/slider-captcha.css' %}">
<script src="{% static 'js/slider-captcha.js' %}"></script>
2. 添加HTML容器
<div id="slider-captcha-container"></div>
3. 初始化组件
const captcha = new SliderCaptcha({
containerId: 'slider-captcha-container',
apiUrl: '/user/slider-captcha/',
onSuccess: function(captchaKey) {
console.log('滑块验证成功:', captchaKey);
// 验证成功后,隐藏滑块验证码,提交表单
document.getElementById('captcha-key').value = captchaKey;
},
onError: function() {
console.log('滑块验证失败');
// 显示错误信息,1.5秒后自动刷新新验证码
},
onRefresh: function() {
console.log('用户点击刷新');
}
});
4. 在表单提交中使用
document.getElementById('login-form').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = {
account: document.getElementById('account').value,
password: document.getElementById('password').value,
slider_captcha_key: captcha.captchaKey, // 从组件获取
slider_captcha_x: captcha.finalXPosition
};
const response = await fetch('/user/login/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const data = await response.json();
if (data.code === 0) {
// 登录成功
window.location.href = '/dashboard/';
} else {
// 显示错误,刷新验证码
captcha.refresh();
}
});
在后端视图中集成滑块验证
1. 在现有视图中添加滑块验证码保护
参考 user.py 中的 LoginView 实现:
from rest_framework.views import APIView
from rest_framework import status
from utils.captcha import check_captcha_required, record_failure, reset_failures
from utils.slider_captcha import verify_slider_captcha, SliderCaptchaError
from utils.response_codes import ResponseCode, create_standardized_error_response
class MyProtectedView(APIView):
permission_classes = [AllowAny]
def _get_identifier(self, request):
"""获取客户端标识(用于失败计数)"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
return x_forwarded_for.split(',')[0].strip()
return request.META.get('REMOTE_ADDR')
def post(self, request):
operation = 'my_operation'
identifier = self._get_identifier(request)
# 步骤1: 检查是否需要滑块验证
if check_captcha_required(operation, identifier):
captcha_key = request.data.get('slider_captcha_key')
captcha_x = request.data.get('slider_captcha_x')
if not captcha_key or captcha_x is None:
return create_standardized_error_response(
code=ResponseCode.CAPTCHA_REQUIRED,
status_code=status.HTTP_400_BAD_REQUEST
)
# 步骤2: 验证滑块
try:
verified = verify_slider_captcha(captcha_key, int(captcha_x))
if not verified:
record_failure(operation, identifier)
return create_standardized_error_response(
code=ResponseCode.CAPTCHA_ERROR,
status_code=status.HTTP_400_BAD_REQUEST
)
except SliderCaptchaError:
return create_standardized_error_response(
code=ResponseCode.CAPTCHA_EXPIRED,
status_code=status.HTTP_400_BAD_REQUEST
)
# 步骤3: 执行业务逻辑
result = do_something(request.data)
if result.success:
# 成功:重置失败计数
reset_failures(operation, identifier)
return create_standardized_response(
data=result.data,
code=ResponseCode.SUCCESS,
status_code=status.HTTP_200_OK
)
else:
# 失败:记录失败次数(下次可能需要验证码)
record_failure(operation, identifier)
return create_standardized_error_response(
code=ResponseCode.ERROR,
message=result.error_message,
status_code=status.HTTP_400_BAD_REQUEST
)
2. 验证码触发阈值配置
在 utils/captcha.py 中定义了默认阈值:
CAPTCHA_THRESHOLD = 3 # 失败3次后开始需要验证码
CAPTCHA_TRIGGER_TTL = 600 # 触发状态持续10分钟
安全特性总结
| 特性 | 说明 |
|---|---|
| 一次性使用 | 每个 captcha_key 验证后立即失效,防止重放攻击 |
| 过期机制 | 验证码有效期为5分钟,过期后需重新生成 |
| 位置容差 | 允许±5像素的误差,提升用户体验的同时保持安全性 |
| 轨迹分析 | 分析拖拽速度、停顿等特征,识别自动化脚本 |
| 失败计数 | 连续失败达到阈值后强制要求验证码,防止暴力破解 |
| Redis存储 | 使用缓存存储验证码状态,高性能且易于扩展 |
测试指南
运行滑块验证码相关测试
cd chunyu_project
# 运行所有滑块验证码相关测试
python manage.py test user.tests.test_slider_captcha --keepdb -v 2
python manage.py test user.tests.test_slider_captcha_api --keepdb -v 2
python manage.py test user.tests.test_login_with_slider --keepdb -v 2
# 同时运行所有测试
python manage.py test user.tests.test_slider_captcha user.tests.test_slider_captcha_api user.tests.test_login_with_slider --keepdb -v 2
测试覆盖范围
| 测试类 | 测试数 | 覆盖内容 |
|---|---|---|
TestGenerateSliderCaptcha |
4 | 验证码生成、字段完整性、缓存存储 |
TestVerifySliderCaptcha |
4 | 正确位置通过、错误位置拒绝、过期处理、容差验证 |
TestVerifySliderTrajectory |
2 | 正常轨迹通过、可疑轨迹拒绝 |
TestSliderCaptchaAPIView |
6 | API端点测试、请求格式验证、响应格式验证 |
TestLoginWithSliderCaptcha |
7 | 登录集成测试、验证触发、成功失败流程 |
| 总计 | 24 |
在测试中Mock滑块验证
当编写使用滑块验证码的功能测试时,可以使用 unittest.mock 来隔离验证逻辑:
from django.test import TestCase
from unittest.mock import patch
from rest_framework.test import APIClient
class MyViewTest(TestCase):
def setUp(self):
self.client = APIClient()
@patch('utils.captcha.check_captcha_required')
@patch('utils.slider_captcha.verify_slider_captcha')
def test_with_valid_captcha(self, mock_verify, mock_check):
# 模拟需要验证码
mock_check.return_value = True
# 模拟验证通过
mock_verify.return_value = True
response = self.client.post('/user/my-endpoint/', {
'slider_captcha_key': 'test-key',
'slider_captcha_x': 150,
# 其他参数...
}, format='json')
# 断言verify_slider_captcha被正确调用
mock_verify.assert_called_once_with('test-key', 150)
故障排查
常见问题
问题1: 验证码验证总是返回false
- 检查
x_position是否为整数类型(而非字符串) - 确认
captcha_key与生成时的值一致 - 检查滑块图片的实际位置与背景凹槽位置是否对应
问题2: 验证时返回"验证码已过期"
- 检查是否在5分钟内完成验证
- 检查是否已使用过该
captcha_key(一次性使用) - 检查Redis缓存是否正常运行
问题3: 测试中的patch不生效
- 确保patch路径是
utils.slider_captcha.verify_slider_captcha而非user.views.user.verify_slider_captcha - 使用
format='json'参数发送POST请求
文件位置索引
| 模块 | 路径 |
|---|---|
| 核心工具 | utils/slider_captcha.py |
| API视图 | user/views/slider_captcha.py |
| 登录集成 | user/views/user.py |
| URL配置 | user/urls.py |
| 前端JS | static/js/slider-captcha.js |
| 前端CSS | static/css/slider-captcha.css |
| 核心测试 | user/tests/test_slider_captcha.py |
| API测试 | user/tests/test_slider_captcha_api.py |
| 登录集成测试 | user/tests/test_login_with_slider.py |