Files
chunyu_project/docs/slider-captcha-usage.md
T
2026-08-05 23:59:15 +08:00

430 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 滑块验证码系统使用指南
## 概述
滑块验证码是一种基于用户交互行为的验证机制,通过让用户拖动滑块到指定位置来判断操作是否为真人发起。本系统包含三个核心部分:
1. **核心工具模块** (`utils/slider_captcha.py`) - 验证码生成、位置验证、轨迹分析
2. **API接口层** (`user/views/slider_captcha.py`) - 提供RESTful API供前端调用
3. **前端交互组件** (`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)**
```json
{
"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 URI
- `slider_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)**
```json
{
"code": 0,
"message": "success",
"data": {
"verified": true
}
}
```
**错误响应 (400 Bad Request)**
```json
{
"code": 20022,
"message": "缺少必要参数",
"data": null
}
```
验证码过期错误响应:
```json
{
"code": 20021,
"message": "验证码已过期,请重新获取",
"data": null
}
```
---
## 后端验证逻辑详解
### 1. 位置验证
```python
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. 轨迹分析(防机器人)
```python
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 (轨迹可疑)
```
**检测原理**:
1. **速度变化检测** - 人类操作有自然的加速/减速,机器人操作速度恒定
2. **微小停顿检测** - 人类操作过程中常有毫秒级的微小停顿
3. **轨迹点数量** - 至少需要5个轨迹点(`TRAJECTORY_MIN_POINTS = 5`)
4. **时间范围检查** - 总操作时间不超过10秒(`TRAJECTORY_MAX_DURATION = 10000`毫秒)
---
## 前端集成指南
### 1. 引入静态资源
在需要滑块验证码的页面中引入:
```html
<link rel="stylesheet" href="{% static 'css/slider-captcha.css' %}">
<script src="{% static 'js/slider-captcha.js' %}"></script>
```
### 2. 添加HTML容器
```html
<div id="slider-captcha-container"></div>
```
### 3. 初始化组件
```javascript
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. 在表单提交中使用
```javascript
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](file:///c:/Users/12914/Desktop/vscode/chunyu_project/user/views/user.py) 中的 `LoginView` 实现:
```python
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` 中定义了默认阈值:
```python
CAPTCHA_THRESHOLD = 3 # 失败3次后开始需要验证码
CAPTCHA_TRIGGER_TTL = 600 # 触发状态持续10分钟
```
---
## 安全特性总结
| 特性 | 说明 |
|-----|-----|
| **一次性使用** | 每个 `captcha_key` 验证后立即失效,防止重放攻击 |
| **过期机制** | 验证码有效期为5分钟,过期后需重新生成 |
| **位置容差** | 允许±5像素的误差,提升用户体验的同时保持安全性 |
| **轨迹分析** | 分析拖拽速度、停顿等特征,识别自动化脚本 |
| **失败计数** | 连续失败达到阈值后强制要求验证码,防止暴力破解 |
| **Redis存储** | 使用缓存存储验证码状态,高性能且易于扩展 |
---
## 测试指南
### 运行滑块验证码相关测试
```bash
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` 来隔离验证逻辑:
```python
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](file:///c:/Users/12914/Desktop/vscode/chunyu_project/utils/slider_captcha.py) |
| API视图 | [user/views/slider_captcha.py](file:///c:/Users/12914/Desktop/vscode/chunyu_project/user/views/slider_captcha.py) |
| 登录集成 | [user/views/user.py](file:///c:/Users/12914/Desktop/vscode/chunyu_project/user/views/user.py) |
| URL配置 | [user/urls.py](file:///c:/Users/12914/Desktop/vscode/chunyu_project/user/urls.py) |
| 前端JS | [static/js/slider-captcha.js](file:///c:/Users/12914/Desktop/vscode/chunyu_project/static/js/slider-captcha.js) |
| 前端CSS | [static/css/slider-captcha.css](file:///c:/Users/12914/Desktop/vscode/chunyu_project/static/css/slider-captcha.css) |
| 核心测试 | [user/tests/test_slider_captcha.py](file:///c:/Users/12914/Desktop/vscode/chunyu_project/user/tests/test_slider_captcha.py) |
| API测试 | [user/tests/test_slider_captcha_api.py](file:///c:/Users/12914/Desktop/vscode/chunyu_project/user/tests/test_slider_captcha_api.py) |
| 登录集成测试 | [user/tests/test_login_with_slider.py](file:///c:/Users/12914/Desktop/vscode/chunyu_project/user/tests/test_login_with_slider.py) |