前言
在Web应用开发中,验证码是防止恶意攻击和自动化脚本的重要安全机制。我来给大家演示使用Python 的 Django框架来完成一个学生管理系统。本文将详细介绍如何在Django框架中实现一个完整的登录验证码功能,包括验证码图片的动态生成、前端展示与刷新、以及后端验证流程。
通过本文的学习,你将掌握:
- 使用Pillow库生成带干扰元素的验证码图片
- Django Session存储验证码的原理与实践
- 前后端分离的验证码验证机制
一、验证码功能设计思路
1.1 整体架构
验证码功能的实现分为三个核心环节:
- 生成环节:服务端动态生成验证码图片,并将验证码文本存入Session
- 展示环节:前端通过img标签请求验证码图片,支持点击刷新
- 验证环节:用户提交表单时,比对输入验证码与Session中存储的值
| 环节 | 技术要点 | 存储位置 |
|---|---|---|
| 生成 | Pillow绘图、随机字符 | Session |
| 展示 | img标签、JavaScript刷新 | 前端 |
| 验证 | 表单提交、字符串比对 | 后端视图 |
1.2 技术选型
本方案采用以下技术栈:
- Pillow:Python图像处理库,用于生成验证码图片
- Django Session:存储验证码文本,支持跨请求持久化
- JavaScript:实现验证码图片的点击刷新功能
提示:Pillow是Python最流行的图像处理库,可通过
pip install Pillow安装。
二、验证码图片生成实现
2.1 创建验证码视图函数
首先,我们需要创建一个专门用于生成验证码图片的视图函数。这个函数将负责生成随机字符、绘制图片、添加干扰元素,并返回图片响应。
python
from django.http import HttpResponse
import random
import io
from PIL import Image, ImageDraw, ImageFont
def captcha_image(request):
"""生成验证码图片"""
# 生成随机验证码文本
chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
captcha_text = ''.join(random.choices(chars, k=4))
# 存入session供后续验证使用
request.session['captcha'] = captcha_text.lower()
# 创建120x48像素的图片
width, height = 120, 48
image = Image.new('RGB', (width, height), '#f8fafc')
draw = ImageDraw.Draw(image)
# 返回PNG格式图片
buffer = io.BytesIO()
image.save(buffer, 'PNG')
buffer.seek(0)
return HttpResponse(buffer.getvalue(), content_type='image/png')
这段代码实现了验证码生成的核心逻辑。我们使用random.choices从预定义字符集中随机选取4个字符,然后创建一个简单的图片并返回。
2.2 添加干扰元素
为了提高验证码的安全性,我们需要添加干扰线和干扰点,防止OCR自动识别。
python
# 绘制干扰线
for _ in range(3):
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='#e2e8f0', width=1)
# 绘制干扰点
for _ in range(50):
x = random.randint(0, width)
y = random.randint(0, height)
draw.point((x, y), fill='#cbd5e1')
干扰元素的设计原则是:既能有效干扰机器识别,又不会影响人眼辨识。我们使用浅灰色绘制干扰线和干扰点,与验证码文字形成对比。
2.3 绘制彩色验证码文字
为了让验证码更加美观且难以识别,我们为每个字符使用不同的颜色和随机位置。
python
# 尝试加载字体,失败则使用默认字体
try:
font = ImageFont.truetype("arial.ttf", 28)
except:
font = ImageFont.load_default()
# 定义可选颜色
colors = ['#2563eb', '#7c3aed', '#059669', '#dc2626']
# 逐个绘制字符
for i, char in enumerate(captcha_text):
x = 15 + i * 25 # 水平位置递增
y = random.randint(5, 12) # 垂直位置随机
draw.text((x, y), char, font=font, fill=random.choice(colors))
这段代码的关键点是:
- 字体加载:优先使用系统字体,失败则回退到默认字体
- 位置计算:每个字符水平间隔25像素,垂直位置随机偏移
- 颜色随机:从预定义的4种颜色中随机选择
三、URL路由配置
3.1 配置验证码URL
将验证码视图函数注册到URL配置中,使其可以通过特定路径访问。
python
# accounts/urls.py
from django.urls import path
from . import views
app_name = 'accounts'
urlpatterns = [
path('captcha/', views.captcha_image, name='captcha'),
# 其他路由...
]
配置完成后,验证码图片可以通过/accounts/captcha/路径访问。
3.2 验证码URL特点
验证码URL的设计需要考虑以下因素:
| 特性 | 说明 |
|---|---|
| 语义化 | 使用captcha作为路径,清晰表达功能 |
| 命名空间 | 使用name参数便于模板中反向解析 |
| 应用隔离 | 放在accounts应用下,与其他功能解耦 |
四、前端页面实现
4.1 HTML结构设计
登录页面的验证码区域需要包含输入框、图片展示和刷新按钮。
html
<div class="captcha-group">
<input type="text" name="captcha" class="form-control"
placeholder="请输入验证码" maxlength="4" required>
<img src="{% url 'accounts:captcha' %}"
id="captcha-img" class="captcha-img"
onclick="refreshCaptcha()" title="点击刷新">
<button type="button" class="btn btn-link" onclick="refreshCaptcha()">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
这个HTML结构的特点:
- 输入框:限制最大长度为4,与验证码长度一致
- 图片:使用Django模板语法动态生成URL
- 刷新按钮:提供备用的刷新方式
4.2 JavaScript刷新逻辑
点击验证码图片或刷新按钮时,需要重新请求验证码图片。
javascript
function refreshCaptcha() {
const img = document.getElementById('captcha-img');
// 添加时间戳参数防止浏览器缓存
img.src = '{% url "accounts:captcha" %}?t=' + new Date().getTime();
}
提示:添加时间戳参数是防止浏览器缓存的常用技巧,确保每次请求都能获取新的验证码。
4.3 CSS样式美化
验证码区域的样式需要与整体登录界面协调。
css
.captcha-group {
display: flex;
align-items: center;
gap: 12px;
}
.captcha-img {
height: 42px;
border-radius: 8px;
cursor: pointer;
border: 1px solid #e2e8f0;
transition: transform 0.2s;
}
.captcha-img:hover {
transform: scale(1.05);
}
样式设计要点:
- Flex布局:使输入框和图片水平排列
- 交互反馈:hover时轻微放大,提示可点击
- 圆角边框:与现代化UI风格保持一致
五、后端验证实现
5.1 自定义登录视图
创建自定义登录视图,在验证用户名密码之前先验证验证码。
python
from django.contrib.auth import authenticate, login
from django.contrib import messages
def custom_login(request):
"""自定义登录视图,带验证码验证"""
if request.method == 'POST':
# 获取用户输入的验证码
captcha_input = request.POST.get('captcha', '').lower().strip()
captcha_session = request.session.get('captcha', '')
# 验证码校验
if captcha_input != captcha_session:
messages.error(request, '验证码输入错误,请重新输入')
return render(request, 'accounts/login.html')
# 用户名密码验证
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
messages.success(request, f'登录成功,欢迎{user.real_name}!')
return redirect('dashboard:dashboard')
else:
messages.error(request, '用户名或密码错误')
return render(request, 'accounts/login.html')
5.2 验证流程说明
验证码验证的核心逻辑流程:
- 获取输入:从POST请求中获取用户输入,转换为小写并去除空格
- 比对Session:与Session中存储的验证码进行比对
- 错误处理:验证失败时显示错误信息,不暴露具体原因
- 成功继续:验证通过后继续用户名密码验证
| 验证阶段 | 失败提示 | 安全考虑 |
|---|---|---|
| 验证码 | 验证码输入错误 | 不提示具体是哪位错误 |
| 用户名密码 | 用户名或密码错误 | 不区分用户名还是密码错误 |
六、安全性增强建议
6.1 验证码有效期
为了防止验证码被截获重放攻击,建议设置验证码的有效期。
python
import time
# 生成验证码时记录时间戳
request.session['captcha_time'] = time.time()
# 验证时检查有效期(5分钟)
captcha_time = request.session.get('captcha_time', 0)
if time.time() - captcha_time > 300:
messages.error(request, '验证码已过期,请重新获取')
return render(request, 'accounts/login.html')
6.2 验证次数限制
为防止暴力破解,可以对验证失败次数进行限制。
python
# 记录失败次数
fail_count = request.session.get('login_fail_count', 0)
if fail_count >= 5:
messages.error(request, '失败次数过多,请稍后再试')
return render(request, 'accounts/login.html')
# 验证失败时增加计数
request.session['login_fail_count'] = fail_count + 1
6.3 其他安全措施
主要的安全增强措施包括:
- HTTPS传输:确保验证码和密码在传输过程中加密
- 验证码复杂度:根据安全需求调整验证码长度和字符集
- 一次性使用:验证成功后清除Session中的验证码
python
# 验证成功后清除验证码
if captcha_input == captcha_session:
del request.session['captcha']
七、完整代码整合
7.1 视图函数完整代码
将上述功能整合为完整的验证码视图函数:
python
def captcha_image(request):
"""生成验证码图片"""
chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
captcha_text = ''.join(random.choices(chars, k=4))
request.session['captcha'] = captcha_text.lower()
width, height = 120, 48
image = Image.new('RGB', (width, height), '#f8fafc')
draw = ImageDraw.Draw(image)
try:
font = ImageFont.truetype("arial.ttf", 28)
except:
font = ImageFont.load_default()
# 干扰线
for _ in range(3):
x1, y1 = random.randint(0, width), random.randint(0, height)
x2, y2 = random.randint(0, width), random.randint(0, height)
draw.line([(x1, y1), (x2, y2)], fill='#e2e8f0', width=1)
# 验证码文字
colors = ['#2563eb', '#7c3aed', '#059669', '#dc2626']
for i, char in enumerate(captcha_text):
draw.text((15 + i * 25, random.randint(5, 12)),
char, font=font, fill=random.choice(colors))
# 干扰点
for _ in range(50):
draw.point((random.randint(0, width), random.randint(0, height)),
fill='#cbd5e1')
buffer = io.BytesIO()
image.save(buffer, 'PNG')
buffer.seek(0)
return HttpResponse(buffer.getvalue(), content_type='image/png')
八、测试与调试
8.1 功能测试清单
在部署前,建议按以下清单逐项测试:
- 验证码图片能正常显示
- 点击图片能刷新验证码
- 正确验证码能通过验证
- 错误验证码显示错误提示
- 验证码不区分大小写
- Session正常工作
8.2 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 图片不显示 | Pillow未安装 | pip install Pillow |
| 验证总是失败 | Session未启用 | 检查Session中间件配置 |
| 图片模糊 | 字体问题 | 确保系统有可用字体 |
| 刷新无效 | 浏览器缓存 | 添加时间戳参数 |
总结
本文详细介绍了Django框架下登录验证码功能的完整实现方案。从验证码图片的动态生成、干扰元素的添加,到前端展示刷新和后端验证流程,覆盖了验证码功能的所有核心环节。
实现要点回顾:
- Pillow库:灵活的图像处理能力,支持绘制文字和干扰元素
- Session存储:简单可靠的验证码存储方案
- 前后端配合:JavaScript刷新机制与Django视图的无缝衔接
下一篇文章将介绍如何为学生管理系统添加科研成果与竞赛加分模块,实现学生综合素质的量化管理。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- Django官方文档:Django Session
- Pillow文档:Pillow Handbook
- 项目源码:学生管理系统GitHub仓库
请大家敬请期待下一期学生管理系统------对应的功能模块设计