验证码是为了防止被暴力破解攻击,下面介绍三种生成验证码方式。
安装绘图依赖,利用的是画图模块?PIL
?以及随机模块?random
?在后台生成一个图片和一串随机数,然后保存在内存中。
pip install pillow
code.py
注意需要指定 Monaco.ttf 字体 ,我这里直接放在项目根目录:font_file='Monaco.ttf'
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', font_size=28):
code = []
img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
def rndChar():
"""
生成随机字母
:return:
"""
# return str(random.randint(0, 9))
return chr(random.randint(65, 90))
def rndColor():
"""
生成随机颜色
:return:
"""
return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
# 写文字
font = ImageFont.truetype(font_file, font_size)
for i in range(char_length):
char = rndChar()
code.append(char)
h = random.randint(0, 4)
draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
# 写干扰点
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
# 写干扰圆圈
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
# 画干扰线
for i in range(5):
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=rndColor())
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
return img, ''.join(code)
if __name__ == '__main__':
img, code_str = check_code()
print(code_str)
views.py
LoginForm类:配置登录表单,涵盖用户名、密码和验证码。返回给模板HTML用于渲染页面。
image_code 函数:调用pillow函数,生成图片,设置60秒写入到自己的session中(以便于后续获取验证码再进行校验)
login函数:验证码这块代码主要是校验从前端传过来的验证码是否跟存在session中的验证码一致,如果一直则继续执行下面代码。否则抛出验证码错误异常。
class LoginForm(BootstrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput,
required=True
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(render_value=True),
required=True,
)
code = forms.CharField(
label="验证码",
widget=forms.TextInput,
required=True
)
def image_code(request):
""" 生成图片验证码 """
# 调用pillow函数,生成图片
img, code_string = check_code()
# 写入到自己的session中(以便于后续获取验证码再进行校验)
request.session['image_code'] = code_string
# 给Session设置60s超时
request.session.set_expiry(60)
stream = BytesIO()
img.save(stream, 'png')
return HttpResponse(stream.getvalue())
def login(request):
if request.method == 'GET':
form = LoginForm()
return render(request, 'login.html', {'form': form})
form = LoginForm(data = request.POST)
if form.is_valid():
# 验证码的校验
user_input_code = form.cleaned_data.pop('code')
code = request.session.get('image_code', "")
if code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, 'login.html', {'form': form})
# 查询数据库匹配用户名密码是否正确
user_obj = models.UserInfo.objects.filter(**form.cleaned_data).first()
print(user_obj)
if not user_obj:
form.add_error('password', '用户名或密码错误')
return render(request, 'login.html', {'form': form})
request.session['info'] = {'id': user_obj.id, 'username': user_obj.username}
# 设置 session 过期时间为30分钟
request.session.set_expiry(60 * 30)
return redirect('/user/list/')
return render(request, 'login.html', {'form': form})
login.html
{{ form.code }}:验证码。
{{ form.code.errors.0 }}:验证码输出错误时返回错误提示。
在 img 标签设置 onclick事件,当用户单击验证码图片生成一个新的验证码。
相当于向服务器发送请求:http://localhost:8000/image/code/?temp=随机数
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户管理系统</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<style>
.account {
width: 400px;
border: 1px solid #dddddd;
border-radius: 5px;
box-shadow: 5px 5px 20px #aaa;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 20px 40px;
}
.account h2 {
margin-top: 10px;
text-align: center;
}
</style>
</head>
<body>
<div class="account">
<h2>用户登录</h2>
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.username.errors.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.password.errors.0 }}</span>
</div>
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
{{ form.code }}
<span style="color: red;">{{ form.code.errors.0 }}</span>
</div>
<div class="col-xs-5">
<img id="image_code" src="/image/code/" onclick="refreshImg(this)" style="width: 125px;" />
</div>
</div>
</div>
<input type="submit" value="登 录" class="btn btn-primary">
</form>
</div>
</body>
<script>
function refreshImg(ths) {
ths.src = '/image/code/?temp=' + Math.random();
}
</script>
</html>
path('login/', views.login),
path('image/code/', views.image_code),
check_code.py
?保存在项目任意位置即可,只需在视图函数中导入即可。Monaco.ttf
?字体不可或缺,放置在静态文件中即可,但是需要修改?check_code.py
?中的字体引入路径。django-simple-captcha模块
pip install django-simple-captcha
在 settings.py添加以下内容
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'ums.apps.UmsConfig',
'captcha',
]
更新数据库表,
在 urls.py?
中添加?captcha?
对应的路由
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('captcha', include('captcha.urls')), # 验证码
]
Django 中通常都是由 Form 生成表单,而验证码一般也伴随注册登录表单,因此需要在?forms.py
?中添加验证码的字段。
from django import forms
from captcha.fields import CaptchaField # 一定要导入这行
class UserForm(forms.Form):
username = forms.CharField(
label='用户名', # 在表单里表现为 label 标签
max_length=128,
widget=forms.TextInput(attrs={'class': 'form-control'}) # 添加 css 属性
)
captcha = CaptchaField(
label='验证码',
required=True,
error_messages={
'required': '验证码不能为空'
}
)
from django.shortcuts import render
from app.forms import UserForm
def index(request):
register_form = UserForm(request.POST)
if register_form.is_valid():
pass
register_form = UserForm()
return render(request, 'index.html', {'register_form': register_form})
<html>
<head></head>
<body>
<form action='#' method='post'>
{{ register_form.captcha.label_tag }}
{{ register_form.captcha }} {{
</form>
</body>
</html>
使用django-simple-captcha第三方库会生成验证码并存储到自带的captcha表中。
官方下载源码包,并安装?geetest
?模块Geetest CAPTCHA: Protect website, APIs and mobile apps from bots
访问官网,选择:技术文档 —?行为验证 — 选择服务端部署为?Python
?—?使用 git 或直接下载。
gt3-python-sdk
?文件。
pip install geetest
pip install requests # 有可能还需要 requests 模块
<!-- 引入封装了failback的接口--initGeetest -->
<script src="http://static.geetest.com/static/tools/gt.js"></script>
login.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}">
<style>
.login-col {
margin-top: 100px;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="well col-md-6 col-md-offset-3 login-col">
<h3 class="text-center">登录</h3>
<form>
{% csrf_token %}
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" class="form-control" id="username" placeholder="用户名" name="username">
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" class="form-control" id="password" placeholder="密码" name="password">
</div>
<!--极验科技滑动验证码-->
<div class="form-group">
<!-- 放置极验的滑动验证码 -->
<div id="popup-captcha"></div>
</div>
<!--记住我-->
<div class="checkbox">
<label>
<input type="checkbox"> 记住我
</label>
</div>
<!--登录按钮-->
<button type="button" class="btn btn-primary btn-block" id="login-button">提交</button>
<!--错误信息-->
<span class="login-error"></span>
</form>
</div>
</div>
</div>
</body>
</html>
JS 代码主要分为两部分,
第一部分是获取表单的 value 值,向后台发送 Ajax 请求,以验证用户名及密码是否正确,若有错误将错误信息显示出来。
第二部分向后台获取验证码所需相关参数。?
<script src="{% static 'js/jquery-3.3.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.js' %}"></script>
<!-- 引入封装了failback的接口--initGeetest -->
<script src="http://static.geetest.com/static/tools/gt.js"></script>
<script>
var handlerPopup = function (captchaObj) {
// 成功的回调
captchaObj.onSuccess(function () {
var validate = captchaObj.getValidate();
var username = $('#username').val();
var password = $('#password').val();
console.log(username, password);
$.ajax({
url: "/accounts/login2/", // 进行二次验证
type: "post",
dataType: 'json',
data: {
username: username,
password: password,
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
geetest_challenge: validate.geetest_challenge,
geetest_validate: validate.geetest_validate,
geetest_seccode: validate.geetest_seccode
},
success: function (data) {
console.log(data);
if (data.status) {
// 有错误,在页面上显示
$('.login-error').text(data.msg);
} else {
// 登录成功
location.href = data.msg;
}
}
});
});
// 当点击登录按钮时,弹出滑动验证码窗口
$("#login-button").click(function () {
captchaObj.show();
});
// 将验证码加到id为captcha的元素里
captchaObj.appendTo("#popup-captcha");
// 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
};
$('#username, #password').focus(function () {
// 将之前的错误清空
$('.login-error').text('');
});
// 验证开始需要向网站主后台获取id,challenge,success(是否启用failback)
$.ajax({
url: "/accounts/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存
type: "get",
dataType: "json",
success: function (data) {
// 使用initGeetest接口
// 参数1:配置参数
// 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件
initGeetest({
gt: data.gt,
challenge: data.challenge,
product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注
// 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
views.py
from django.shortcuts import render, redirect, HttpResponse
from django.http import JsonResponse
from geetest import GeetestLib
def login2(request):
if request.method == 'POST':
ret = {'status': False, 'msg': None}
username = request.POST.get('username')
password = request.POST.get('password')
print(username, password)
# 获取极验,滑动验证码相关参数
gt = GeetestLib(pc_geetest_id, pc_geetest_key)
challenge = request.POST.get(gt.FN_CHALLENGE, '')
validate = request.POST.get(gt.FN_VALIDATE, '')
seccode = request.POST.get(gt.FN_SECCODE, '')
status = request.session[gt.GT_STATUS_SESSION_KEY]
user_id = request.session["user_id"]
print(gt, challenge, validate, seccode, status)
if status:
result = gt.success_validate(challenge, validate, seccode, user_id)
else:
result = gt.failback_validate(challenge, validate, seccode)
if result:
# 验证码正确
# 利用auth模块做用户名和密码的校验
user_obj = auth.authenticate(username=username, password=password)
if user_obj:
# 用户名密码正确
# 给用户做登录
auth.login(request, user_obj)
ret["msg"] = "/accounts/home/"
# return redirect('accounts:home')
else:
# 用户名密码错误
ret["status"] = True
ret["msg"] = "用户名或密码错误!"
else:
ret["status"] = True
ret["msg"] = "验证码错误"
return JsonResponse(ret)
return render(request, "accounts/login2.html")
# 请在官网申请ID使用,示例ID不可使用
pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"
# 处理极验 获取验证码的视图
def get_geetest(request):
user_id = 'test'
gt = GeetestLib(pc_geetest_id, pc_geetest_key)
status = gt.pre_process(user_id)
request.session[gt.GT_STATUS_SESSION_KEY] = status
request.session["user_id"] = user_id
response_str = gt.get_response_str()
return HttpResponse(response_str)
from django.urls import path
from accounts import views
app_name = 'accounts'
urlpatterns = [
path('home/', views.home, name='home'),
# 极验滑动验证码 获取验证码的url
path('pc-geetest/register/', views.get_geetest, name='get_geetest'),
path('login2/', views.login2, name='login2'),
]
check_code.py
?保存在项目任意位置即可,只需在视图函数中导入即可。Monaco.ttf
?字体不可或缺,放置在静态文件中即可,但是需要修改?check_code.py
?中的字体引入路径。需要源代码评论区可以给我留言!
如果本文对你有帮助,记得点赞关注,你的支持是我最大的动力!
如果您有任何疑问或建议,欢迎在评论区留言,一起探讨交流!