让 Django 验证码“动”起来:深度解析验证码动态刷新的前后端闭环
让 Django 验证码“动”起来:深度解析验证码动态刷新的前后端闭环
在前后端分离与用户体验至上的今天,一个“点击刷新验证码”的小功能,却常常让开发者踩遍 404、CORS、AJAX 验证等坑。本文带你从模板语法到 Django 源码,彻底拆解 django-simple-captcha 动态刷新的正确姿势,并给出“不升级库”与“升级库”两套实战方案,让你十分钟内拥有丝滑验证码体验。
1. 需求缘起:为什么要“无刷新”验证码?
- 用户体验:整页刷新会丢失表单已填写内容,增加跳出率。
- 安全考量:验证码使用一次即失效,必须异步拉取新图片。
- 前后端分离:越来越普遍的场景下,需要纯 API 方式换取新验证码。
2. 技术栈与版本信息
| 组件 | 版本(本文示例) | 备注 |
|---|---|---|
| Django | 3.2+ | 4.x 亦适用 |
| django-simple-captcha | 0.5.x(旧版) | 0.5.17 之前强制 AJAX |
| 前端 | 原生 JS | 无需框架,Vue/React 同理 |
3. 路由匹配:404 不一定是“没找到”
很多开发者看到 Page not found 就以为 URL 写错,实则 Django 已经正确匹配:
captcha/ refresh/$ [name='captcha-refresh']
但视图内部主动 raise Http404,罪魁祸首是:
# captcha/views.py(0.5.x)
if not request.headers.get("x-requested-with") == "XMLHttpRequest":
raise Http404
旧版库只允许 AJAX 请求,否则一律 404。
4. 前端代码:两行 header 搞定刷新
// 1. 绑定刷新按钮
document.getElementById('refresh-captcha').addEventListener('click', e => {
e.preventDefault();
// 2. 请求新验证码
fetch("/captcha/refresh/", {
method: "GET",
headers: {"X-Requested-With": "XMLHttpRequest"} // 关键!
})
.then(r => r.json())
.then(data => {
// 3. 更新图片与隐藏 key
document.querySelector('img.captcha').src = data.image_url;
document.querySelector('input[name="captcha_0"]').value = data.key;
})
.catch(console.error);
});
模板片段(register.html):
<div class="form-group">
<label>验证码</label>
{{ form.captcha }}
<a href="#" id="refresh-captcha">看不清?点击刷新</a>
</div>
5. 后端验证:无需改动,即插即用
django-simple-captcha 的 CaptchaField 会自动把 captcha_0(key)与 captcha_1(用户输入)拿去校验,前端只负责换图与换 key,后端零成本。
6. 升级派:一劳永逸的“官方推荐”
pip install -U django-simple-captcha
新版本已移除 is_ajax() 判断,普通 GET 也能 200,前端可直接:
fetch("/captcha/refresh/").then(res => res.json())...
升级后别忘了:
python manage.py migrate # 0.5→0.6 字段有变动
pip freeze > requirements.txt
7. 常见坑位速查表
| 现象 | 根因 | 快速修复 |
|---|---|---|
| 404 且路由已匹配 | 缺少 X-Requested-With |
加 header 或升级库 |
| CORS 报错 | 前端端口≠后端端口 | 开 corsheaders 或同域代理 |
| 刷新后验证失败 | 隐藏 key 未更新 | 把 data.key 写回 captcha_0 |
| 图片缓存 | 浏览器复用旧图 | 在 image_url 后拼时间戳(官方 URL 已带随机 key,一般无需) |
8. 延伸:如何“本地重写”刷新接口(不想升级)
在项目内新建 my captcha/views.py:
from captcha.views import captcha_refresh as _orig
from django.http import HttpResponse
import json
def captcha_refresh(request):
# 去掉 AJAX 限制,其余逻辑不变
return _orig(request)
urls.py 里用 myapp.views.captcha_refresh 覆盖即可。
9. 结语:小功能,大体验
一次看似简单的“点击刷新”,背后却涉及路由匹配、视图鉴权、AJAX 标识与前端缓存等多个细节。掌握 django-simple-captcha 的刷新机制后,你可以:
- 零刷新替换验证码
- 轻松扩展短信/邮箱验证码混合模式
- 为后续接入 Redis 缓存、滑块验证码打下坚实基础
把这篇文章收藏起来,下次再遇到 404,再也不用“整个页面 reload”了!
10. 附录:一键复制版前端组件
<script>
/* 通用验证码刷新器
* 依赖:django-simple-captcha 0.5+
*/
function bindCaptchaRefresh(triggerId) {
document.getElementById(triggerId).addEventListener('click', e => {
e.preventDefault();
fetch("/captcha/refresh/", {
headers: {"X-Requested-With": "XMLHttpRequest"}
})
.then(r => r.json())
.then(d => {
document.querySelector('img.captcha').src = d.image_url;
document.querySelector('input[name="captcha_0"]').value = d.key;
});
});
}
bindCaptchaRefresh('refresh-captcha');
</script>
Happy coding!

浙公网安备 33010602011771号