探索Django验证码功能的实现 - DjangoStarter项目模板里的封装
前言
依然是最近在做的这个项目,用Django做后端,App上提交信息的时候需要一个验证码来防止用户乱提交,正好我的「DjangoStarter」项目脚手架也有封装了验证码功能,不过我发现好像里面只是把验证码作为admin后台登录的校验手段,并没有给出前后端分离项目的验证码相关接口。
所以本文介绍验证码功能实现的同时,也对「DjangoStarter」的验证码模块做一层封装,使其更方便使用~
用哪个库好呢
Python之禅:人生苦短,不造轮子
——鲁迅:我说的
我在「DjangoStarter」里选择的是django-simple-captcha
和django-multi-captcha-admin
这俩库,前者提供生成、存储验证码的功能;后者可以将验证码集成到Django Admin的登录页面里。
开始
所以我们现在具备了实现验证码功能的基础,那么该如何在前端获取验证码呢?
首先django-simple-captcha
这个库既然是要提供验证码功能,那肯定有相关接口吧,来看看官网文档?no,这文档也太简陋了
算了,直接看源码吧
在添加这个库到项目里的时候,需要配置这个路由:
path('captcha/', include('captcha.urls'))
那我们就从路由(captcha.urls
)开始看
它的路由配置代码是这样的
urlpatterns = [
url(
r"image/(?P<key>\w+)/$",
views.captcha_image,
name="captcha-image",
kwargs={"scale": 1},
),
url(
r"image/(?P<key>\w+)@2/$",
views.captcha_image,
name="captcha-image-2x",
kwargs={"scale": 2},
),
url(r"audio/(?P<key>\w+).wav$", views.captcha_audio, name="captcha-audio"),
url(r"refresh/$", views.captcha_refresh, name="captcha-refresh"),
]
可以看到有三种链接形式,分别是
image/xxx
audio/xxx
refresh
尝试
那很显然,刷新验证码的就是最后这个refresh
然后我试着在Postman里访问captcha/refresh/
,发现直接报404
What?这个链接明明存在的,咋回事
只能继续看看源码了
直接看这个 views.captcha_refresh()
方法的源码!
def captcha_refresh(request):
""" Return json with new captcha for ajax refresh request """
if not request.headers.get('x-requested-with') == 'XMLHttpRequest':
raise Http404
new_key = CaptchaStore.pick()
to_json_response = {
"key": new_key,
"image_url": captcha_image_url(new_key),
"audio_url": captcha_audio_url(new_key) if settings.CAPTCHA_FLITE_PATH else None,
}
return HttpResponse(json.dumps(to_json_response), content_type="application/json")
然后在源码里面看到了这个:
if not request.headers.get('x-requested-with') == 'XMLHttpRequest':
raise Http404
坑爹啊!
什么年代了,还搞jQuery的Ajax那一套是吧?
果断弃坑!
哦不,弃坑是不可能的,有现成的东西为啥不用,我直接自己重新封装一个不就好了?
重新封装一个模块
在contrib
目录下创建一个新的Python Package
,名字就叫captcha
好了
然后编辑contrib/captcha/__init__.py
文件
from captcha.conf import settings
from captcha.models import CaptchaStore
from captcha.helpers import captcha_audio_url, captcha_image_url
class CaptchaItem(object):
def __init__(self, key, image_url, audio_url):
self.key = key
self.image_url = image_url
self.audio_url = audio_url
def refresh() -> CaptchaItem:
"""
获取新的验证码
:return:
"""
key = CaptchaStore.pick()
return CaptchaItem(
key,
captcha_image_url(key),
captcha_audio_url(key) if settings.CAPTCHA_FLITE_PATH else None,
)
def verify(key: str, code: str) -> bool:
"""
检查输入的验证码是否正确
:param key:
:param code:
:return:
"""
# 清理过期的验证码记录
CaptchaStore.remove_expired()
try:
CaptchaStore.objects.get(response=code, hashkey=key).delete()
return True
except CaptchaStore.DoesNotExist:
return False
代码里面注释很清楚了,我可以不用解释了,哈哈
写个新的验证码接口
众所周知,「DjangoStarter」有一个默认的应用apps.core
,那我们就把验证码的接口写在这个app里面就好了
在apps/core/views.py
里增加代码
from drf_yasg2.utils import swagger_auto_schema
from rest_framework import permissions
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
@swagger_auto_schema(method='get', operation_summary='刷新验证码')
@permission_classes([permissions.AllowAny])
@api_view()
def refresh_captcha(request):
from contrib import captcha
captcha_item = captcha.refresh()
return Response({
"key": captcha_item.key,
"image_url": captcha_item.image_url,
"audio_url": captcha_item.audio_url,
})
然后编辑apps/core/urls.py
,添加一下路由配置
from . import views
urlpatterns = [
...
path('refresh_captcha', views.refresh_captcha),
]
OK搞定啦~!
测试一下看看,在Swagger或者Postman里请求一下这个接口:core/refresh_captcha
,得到结果
{
"message": "请求成功",
"code": 200,
"data": {
"key": "f5275573b0715d2fa9613a73f80a4161ed759061",
"image_url": "/captcha/image/f5275573b0715d2fa9613a73f80a4161ed759061/",
"audio_url": null
}
}
结果里除了我们期待的验证码图片地址,还有一个key,客户端在提交用户输入的验证码时,要把key一并提交,服务端才能验证这个提交是否有效。
检查验证码是否匹配
获取验证码有了,接下来要做的是检查用户输入的验证码是否正确
在前面的封装里,我们已经写了verify
函数,只需要传入验证码的key
和用户输入的code
就好~
正确的话会返回True
,并且把这条验证码的记录删除,不存在或者错误的话返回False
。
来一个例子吧,这个接口使用的是POST方法,参数在FormData里
from rest_framework import status
from rest_framework.response import Response
from drf_yasg2.utils import swagger_auto_schema
from drf_yasg2 import openapi
from contrib import captcha
@swagger_auto_schema(
method='post',
operation_summary='检查验证码',
manual_parameters=[
openapi.Parameter('code', openapi.IN_FORM, type=openapi.TYPE_STRING, description='验证码'),
openapi.Parameter('key', openapi.IN_FORM, type=openapi.TYPE_STRING, description='验证key'),
]
)
@api_view()
def verify_captcha(request):
code = request.POST.get('code')
key = request.POST.get('key')
if not (code and key):
return Response({'message': '请输入验证码'}, status=status.HTTP_400_BAD_REQUEST)
if captcha.verify(key, code):
return Response({'message': '验证码输入正确'})
else:
return Response({'message': '验证码错误'}, status=status.HTTP_403_FORBIDDEN)
高级用法
前面介绍的只是最基础的用法,可以根据实际需求来自定义生成验证码的行为,比如手动指定验证码有效期之类的
要自定义的话,首先得了解验证码生成的过程
先来看看数据库表是什么样的:
challenge | response | hashkey | expiration | id |
---|---|---|---|---|
LOKJ | lokj | 286f34637808d669f4fd55ebb1877f72d4ab7fa9 | 2022-04-08 15:32:41.328754 | 31 |
JDNA | jdna | fb1e57277df26cbd7c20f6a7887f0bed18972e5b | 2022-04-08 15:32:45.795259 | 32 |
可以看到有五个字段,其中expiration
字段就是指定过期时间了
之前封装生成验证码方法的时候,可以看到生成的时候是调用CaptchaStore.pick()
这个方法
其实这个CaptchaStore
是django-simple-captcha
这个库定义的一个Django Model
,作者在这个model里定义了pick
这个类方法(class method)来生成验证码,我们来看看源码实现
@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()
注意minimum_expiration = timezone.now() + datetime.timedelta
这行代码,它的作用是从配置中读取过期时间
所以我们其实也不用折腾,直接在设置里配置一下就好了
不过注意这里面captcha_settings
的引入方式是:from captcha.conf import settings as captcha_settings
它是对Django的settings包装了一层
具体源码就不展开了
反正我们在Django的settings里面配置CAPTCHA_GET_FROM_POOL_TIMEOUT=10
就好了,注意时间单位是分钟
参考资料
- Django Simple Captcha项目地址:https://github.com/mbi/django-simple-captcha
- Django Simple Captcha文档:http://django-simple-captcha.readthedocs.org/en/latest/
探索Django验证码功能的实现 - DjangoStarter项目模板里的封装的更多相关文章
- django验证码功能
1.目的 现在我们一般访问网页都需要输入验证码,比如博客园,有的甚至是通过手机验证码实时登录.这样做的目的主要还是为了防止其他人的恶意访问,比如爬虫,下面就来看看验证码是如何实现的 2.StringI ...
- DjangoRestFramework框架三种分页功能的实现 - 在DjangoStarter项目模板中封装
前言 继续Django后端开发系列文章.刚好遇到一个分页的需求,就记录一下. Django作为一个"全家桶"型的框架,本身啥都有,分页组件也是有的,但默认的分页组件没有对API开发 ...
- Win10系列:JavaScript 项目模板和项模板
使用Visual Studio 开发Windows应用商店应用时,通过其提供的模板可以帮助我们快速地创建一个应用.其中,在新建一个Windows应用商店应用程序项目时可以在项目模板中选择所需要的模板类 ...
- Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现
本文主要学习验证码功能的实现,为了项目BBS+Blog项目打下基础. 为了防止机器人频繁登陆网站或者破坏分子恶意登陆,很多用户登录和注册系统都提供了图形验证码功能. 验证码(CAPTCHA)是“Com ...
- 操作PDF文档功能的相关开源项目探索——iTextSharp 和PDFBox
原文 操作PDF文档功能的相关开源项目探索——iTextSharp 和PDFBox 很久没自己写写心得日志与大家分享了,一方面是自己有点忙,一方面是自己有点懒,没有及时总结.因为实践是经验的来源,总结 ...
- .NET Core实战项目之CMS 第十六章 用户登录及验证码功能实现
前面为了方便我们只是简单实现了基本业务功能的增删改查,但是登录功能还没有实现,而登录又是系统所必须的,得益于 ASP.NET Core的可扩展性因此我们很容易实现我们的登录功能.今天我将带着大家一起来 ...
- .Net Core 之 图形验证码 本文介绍.Net Core下用第三方ZKWeb.System.Drawing实现验证码功能。
本文介绍.Net Core下用第三方ZKWeb.System.Drawing实现验证码功能. 通过测试的系统: Windows 8.1 64bit Ubuntu Server 16.04 LTS 64 ...
- 使用JS来实现验证码功能
最近想为自己的Django博客添加验证码功能,本来想使用第三方库来实现的,不过考虑到添加第三方库对性能的影响,以及第三方库是否安全可靠的问题,还是用自己的代码来实现吧.反正用JS来实现验证码功能又不是 ...
- Tornado框架实现图形验证码功能
图形验证码是项目开发过程中经常遇到的一个功能,在很多语言中都有对应的不同形式的图形验证码功能的封装,python 中同样也有类似的封装操作,通过绘制生成一个指定的图形数据,让前端HTML页面通过链接获 ...
随机推荐
- Excel 使用 公式
1. 查询单元数据在另一列中是否存在 =VLOOKUP(E2,F:F,1,FALSE) 2.判断一列数据是否重复 =IF(COUNTIF(A:A,A2)>1,"重复",&qu ...
- Docker——概述
出现原因:开发接替运维的工作,将jar包连同(mysql,jdk)等环境上线 实现:java -> jar(环境) -> 打包项目带上环境(镜像) -> (Docker仓库:商店) ...
- Arcgis Server发布的带有透明度的地图服务,调用时不显示透明度问题
问题: 在发布道路地图时候设置地图透明度为50% 使用arcgis API for js 中 ArcGISDynamicMapServiceLayer 调用该地图时,发现透明效果不实现 如下图: 解决 ...
- 如何巧妙使用vim处理文件
一系列流程书写如何用vim巧妙处理文件 1. 创建两个待处理的文件 root@ubuntu:/home/watson/real-watson# touch left right 2. 简单增补文件内容 ...
- Java基础 - 注解详解
What - 什么是注解? Annontation是Java5开始引入的新特征,中文名称叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素( ...
- TTL、RS232、RS485、UART、串口的关系和常见半双工、全双工协议
串口(UART口).COM口.USB口.DB9.DB25是指的物理接口形式(硬件) TTL.RS-232.RS-485是指的电平标准(电平信号) 我们单片机嵌入式常用的串口有三种(TTL/RS-2 ...
- eclipse启动指定jvm的版本
参阅:https://www.eclipse.org/forums/index.php/t/1105435/ https://wiki.eclipse.org/Eclipse.ini#-vm_valu ...
- vue Cannot read property ‘tapPromise‘ of undefined
https://blog.csdn.net/qq379682421/article/details/111574290 https://github.com/SimenB/add-asset-html ...
- Redis 集群方案什么情况下会导致整个集群不可用?
有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了, 那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用.
- prometheus-存储
采集到的样本以时间序列的方式保存在内存(TSDB 时序数据库)中,并定时保存到硬盘中 prometheus一般会保留15天 prometheus按照block块的方式来存储数据,每2小时为一个时间单位 ...