如何在DjangoStarter模板中实现封装验证码功能?

2026-05-19 20:351阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2090个文字,预计阅读时间需要9分钟。

如何在DjangoStarter模板中实现封装验证码功能?

前言:最近在做的一个项目,用Django做后端,App上提交信息的时候需要用一个验证码来防止用户乱提交。正好我的DjangoStarter项目脚手架也封装了验证码功能,不过我觉得好像还不够好。

前言

依然是最近在做的这个项目,用Django做后端,App上提交信息的时候需要一个验证码来防止用户乱提交,正好我的「DjangoStarter」项目脚手架也有封装了验证码功能,不过我发现好像里面只是把验证码作为admin后台登录的校验手段,并没有给出前后端分离项目的验证码相关接口。

所以本文介绍验证码功能实现的同时,也对「DjangoStarter」的验证码模块做一层封装,使其更方便使用~

用哪个库好呢

Python之禅:人生苦短,不造轮子

——鲁迅:我说的

我在「DjangoStarter」里选择的是django-simple-captchadjango-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搞定啦~!

如何在DjangoStarter模板中实现封装验证码功能?

测试一下看看,在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()这个方法

其实这个CaptchaStoredjango-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项目地址:github.com/mbi/django-simple-captcha
  • Django Simple Captcha文档:django-simple-captcha.readthedocs.org/en/latest/
微信公众号:「程序设计实验室」 专注于互联网热门新技术探索与团队敏捷开发实践,包括架构设计、机器学习与数据分析算法、移动端开发、Linux、Web前后端开发等,欢迎一起探讨技术,分享学习实践经验。
标签:实现DjangoS

本文共计2090个文字,预计阅读时间需要9分钟。

如何在DjangoStarter模板中实现封装验证码功能?

前言:最近在做的一个项目,用Django做后端,App上提交信息的时候需要用一个验证码来防止用户乱提交。正好我的DjangoStarter项目脚手架也封装了验证码功能,不过我觉得好像还不够好。

前言

依然是最近在做的这个项目,用Django做后端,App上提交信息的时候需要一个验证码来防止用户乱提交,正好我的「DjangoStarter」项目脚手架也有封装了验证码功能,不过我发现好像里面只是把验证码作为admin后台登录的校验手段,并没有给出前后端分离项目的验证码相关接口。

所以本文介绍验证码功能实现的同时,也对「DjangoStarter」的验证码模块做一层封装,使其更方便使用~

用哪个库好呢

Python之禅:人生苦短,不造轮子

——鲁迅:我说的

我在「DjangoStarter」里选择的是django-simple-captchadjango-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搞定啦~!

如何在DjangoStarter模板中实现封装验证码功能?

测试一下看看,在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()这个方法

其实这个CaptchaStoredjango-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项目地址:github.com/mbi/django-simple-captcha
  • Django Simple Captcha文档:django-simple-captcha.readthedocs.org/en/latest/
微信公众号:「程序设计实验室」 专注于互联网热门新技术探索与团队敏捷开发实践,包括架构设计、机器学习与数据分析算法、移动端开发、Linux、Web前后端开发等,欢迎一起探讨技术,分享学习实践经验。
标签:实现DjangoS