django-simple-captcha
pip install django-simple-captcha
INSTALLED_APPS = [
……
'captcha',
]
python manage.py migrate
数据库迁移相关命令:
python manage.py makemigrations
是用于检测app/models.py的改动,console会告知create model xxx
python manage.py migrate
将上述改动翻译成sql并去数据库执行
验证码模型源码地址:venv/Lib/site-packages/captcha/models.py
from captcha.conf import settings as captcha_settings
from django.db import models
from django.utils import timezone
from six import python_2_unicode_compatible
from django.utils.encoding import smart_text
import datetime
import hashlib
import logging
import random
import time
# Heavily based on session key generation in Django
# Use the system (hardware-based) random number generator if it exists.
if hasattr(random, 'SystemRandom'):
randrange = random.SystemRandom().randrange
else:
randrange = random.randrange
MAX_RANDOM_KEY = 18446744073709551616 # 2 << 63
logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class CaptchaStore(models.Model):
challenge = models.CharField(blank=False, max_length=32)
response = models.CharField(blank=False, max_length=32)
hashkey = models.CharField(blank=False, max_length=40, unique=True)
expiration = models.DateTimeField(blank=False)
def save(self, *args, **kwargs):
self.response = self.response.lower()
if not self.expiration:
self.expiration = timezone.now() + datetime.timedelta(minutes=int(captcha_settings.CAPTCHA_TIMEOUT))
if not self.hashkey:
key_ = (
smart_text(randrange(0, MAX_RANDOM_KEY)) +
smart_text(time.time()) +
smart_text(self.challenge, errors='ignore') +
smart_text(self.response, errors='ignore')
).encode('utf8')
self.hashkey = hashlib.sha1(key_).hexdigest()
del(key_)
super(CaptchaStore, self).save(*args, **kwargs)
def __str__(self):
return self.challenge
def remove_expired(cls):
cls.objects.filter(expiration__lte=timezone.now()).delete()
remove_expired = classmethod(remove_expired)
@classmethod
def generate_key(cls, generator=None):
challenge, response = captcha_settings.get_challenge(generator)()
store = cls.objects.create(challenge=challenge, response=response)
return store.hashkey
@classmethod
def pick(cls):
if not captcha_settings.CAPTCHA_GET_FROM_POOL:
return cls.generate_key()
def fallback():
logger.error("Couldn't get a captcha from pool, generating")
return cls.generate_key()
# Pick up a random item from pool
minimum_expiration = timezone.now() + datetime.timedelta(minutes=int(captcha_settings.CAPTCHA_GET_FROM_POOL_TIMEOUT))
store = cls.objects.filter(expiration__gt=minimum_expiration).order_by('?').first()
return (store and store.hashkey) or fallback()
@classmethod
def create_pool(cls, count=1000):
assert count > 0
while count > 0:
cls.generate_key()
count -= 1
Django的captcha没有用session对验证码进行存储,而是在数据库中生成一张表进行存储。页面加载时数据库生成一条关于验证码的记录。当提交表单时post到服务器,此时服务器验证captcha_captchastore表中的hashkey对应的response是否与输入的验证码一致。(看起来验证过程是自动完成的,在封装好的第三方库,所以后台代码也不需要处理验证码的验证工作)
urlpatterns = [
path ('captcha/', include ('captcha.urls')), # 验证码
]
文档:
urlpatterns += [
url(r'^captcha/', include('captcha.urls')),
]
补充url和path的区别:
PATH(2.0):from django.urls import path——第三方框架/模块
URL(1.0):from django.urls import url——自定义模块
#forms.py
from django import forms
from captcha.fields import CaptchaField
class CaptchaTestForm(forms.Form):
captcha = CaptchaField()
# views.py
from user.forms import CaptchaTestForm
from django.shortcuts import render
def some_view(request):
if request.POST:
form = CaptchaTestForm(request.POST)
# Validate the form: the captcha field will automatically
# check the input
if form.is_valid():
human = True
else:
form = CaptchaTestForm()
return render(request,'captcha.html', locals())
render_to_response
在现在新的django里面已经不支持,换成render可以使用locals()
返回函数执行到此节点内的所有局部变量将上面新增的some_view方法加到urls路由
# user/urls.py
urlpatterns = [
path ('captcha/', include ('captcha.urls')), # 验证码
……
path('demo',views.some_view),
]
<form method='POST'>
{% csrf_token %}
{
{ form }}
<input type="submit" />
<button class='js-captcha-refresh'>button>
form>
参考文档:
一直不知道验证码在前后端分离的时候,接口设计怎么使用,比如需要传什么数据,但是前端传递入口又依赖于django的captcha库,后端才接触captcha,那前端要怎么作用呢?就做了一个像下面一样小白的测试,但是captcha_0(一段输入的哈希值吧)怎么前端怎么拿传给后台呢?
登录表单定义了三个属性(见下个目录的代码),但是从控制台看有四个数据,验证码占两个:captcha_0
(hashkey),captcha_1
(用户输入)。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录title>
head>
<body>
<div>
<form action="/user/formlogin" method="post">
{% csrf_token %}
<div>
{
{ login_form.user_account.label_tag }}
{
{ login_form.user_account }}
div>
<div>
{
{ login_form.password.label_tag }}
{
{ login_form.password }}
div>
<div>
{
{ login_form.captcha.errors }}
{
{ login_form.captcha.label_tag }}
{
{ login_form.captcha }}
div>
<input type="submit" value="确定">
form>
div>
body>
html>
# forms.py
from django import forms
from captcha.fields import CaptchaField
from django.core.exceptions import ValidationError
from user.models import User
# 登录表单
class UserForm (forms.Form):
user_account = forms.CharField (label="用户名", max_length=128, widget=forms.TextInput (attrs={
'class': 'form-control'}))
password = forms.CharField (label="密码", max_length=256,
widget=forms.PasswordInput (attrs={
'class': 'form-control'}))
captcha = CaptchaField (label='验证码') # 图片验证码+输入框
# 用户登录,生成token
# 接收表单数据
# username 用户名
# password 密码
# user 通过输入的用户名获取的数据库用户
# url:user/formlogin
# 返回JSON数据
@csrf_exempt
def form_login(request):
if request.method == "POST":
login_form = UserForm (request.POST) # 自定义登录表单
if login_form.is_valid (): # 确保用户名和密码都不为空
username = login_form.cleaned_data['user_account']
password = login_form.cleaned_data['password']
print (username + password)
try:
user = User.objects.get (user_account=username)
except:
return HttpResponse (json.dumps ({
'code': '301'})) # 用户不存在
if check_password (password, user.password):
# print ('数据库密码是:' + user.password)
temp = user
token = token_op.create_token (temp)
return HttpResponse (
json.dumps (
{
'code': '200', # 成功状态码
'token': token,
'user_id': user.user_id,
'user_account': user.user_account,
'user_url': user.user_url,
'user_name': user.user_name,
'user_gender': user.user_gender,
'email': user.email,
'user_phone': user.user_phone,
'user_credit': user.user_credit,
'status': user.status,
}
)
)
else:
return HttpResponse (json.dumps ({
'code': '302'})) # 密码不正确
else:
return HttpResponse (json.dumps ({
'code': '402'})) # 输入框未填完
else:
# login_form = UserForm ()
# return HttpResponse (json.dumps ({'code': '404'})) # 请求非POST类型
return render(request, 'user/login.html', locals())
path ('formlogin', loginViews.form_login), # html页面