<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          探索Django驗(yàn)證碼功能的實(shí)現(xiàn) - DjangoStarter項(xiàng)目模板里的封裝

          共 6381字,需瀏覽 13分鐘

           ·

          2022-04-10 12:32

          前言

          依然是最近在做的這個(gè)項(xiàng)目,用Django做后端,App上提交信息的時(shí)候需要一個(gè)驗(yàn)證碼來(lái)防止用戶亂提交,正好我的「DjangoStarter」項(xiàng)目腳手架也有封裝了驗(yàn)證碼功能,不過(guò)我發(fā)現(xiàn)好像里面只是把驗(yàn)證碼作為admin后臺(tái)登錄的校驗(yàn)手段,并沒(méi)有給出前后端分離項(xiàng)目的驗(yàn)證碼相關(guān)接口。

          所以本文介紹驗(yàn)證碼功能實(shí)現(xiàn)的同時(shí),也對(duì)「DjangoStarter」的驗(yàn)證碼模塊做一層封裝,使其更方便使用~

          用哪個(gè)庫(kù)好呢

          Python之禪:人生苦短,不造輪子

          ——魯迅:我說(shuō)的

          我在「DjangoStarter」里選擇的是django-simple-captchadjango-multi-captcha-admin這倆庫(kù),前者提供生成、存儲(chǔ)驗(yàn)證碼的功能;后者可以將驗(yàn)證碼集成到Django Admin的登錄頁(yè)面里。

          開(kāi)始

          所以我們現(xiàn)在具備了實(shí)現(xiàn)驗(yàn)證碼功能的基礎(chǔ),那么該如何在前端獲取驗(yàn)證碼呢?

          首先django-simple-captcha這個(gè)庫(kù)既然是要提供驗(yàn)證碼功能,那肯定有相關(guān)接口吧,來(lái)看看官網(wǎng)文檔?no,這文檔也太簡(jiǎn)陋了

          算了,直接看源碼吧

          在添加這個(gè)庫(kù)到項(xiàng)目里的時(shí)候,需要配置這個(gè)路由:

          path('captcha/',?include('captcha.urls'))

          那我們就從路由(captcha.urls)開(kāi)始看

          它的路由配置代碼是這樣的

          urlpatterns?=?[
          ????url(
          ????????r"image/(?P\w+)/$",
          ????????views.captcha_image,
          ????????name="captcha-image",
          ????????kwargs={"scale":?1},
          ????),
          ????url(
          ????????r"image/(?P\w+)@2/$",
          ????????views.captcha_image,
          ????????name="captcha-image-2x",
          ????????kwargs={"scale":?2},
          ????),
          ????url(r"audio/(?P\w+).wav$",?views.captcha_audio,?name="captcha-audio"),
          ????url(r"refresh/$",?views.captcha_refresh,?name="captcha-refresh"),
          ]

          可以看到有三種鏈接形式,分別是

          • image/xxx
          • audio/xxx
          • refresh

          嘗試

          那很顯然,刷新驗(yàn)證碼的就是最后這個(gè)refresh

          然后我試著在Postman里訪問(wèn)captcha/refresh/,發(fā)現(xiàn)直接報(bào)404

          What?這個(gè)鏈接明明存在的,咋回事

          只能繼續(xù)看看源碼了

          直接看這個(gè) 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")

          然后在源碼里面看到了這個(gè):

          if?not?request.headers.get('x-requested-with')?==?'XMLHttpRequest':?
          ????raise?Http404

          坑爹??!

          什么年代了,還搞jQuery的Ajax那一套是吧?

          果斷棄坑!

          哦不,棄坑是不可能的,有現(xiàn)成的東西為啥不用,我直接自己重新封裝一個(gè)不就好了?

          重新封裝一個(gè)模塊

          contrib目錄下創(chuàng)建一個(gè)新的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:
          ????"""
          ????獲取新的驗(yàn)證碼

          ????: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:
          ????"""
          ????檢查輸入的驗(yàn)證碼是否正確

          ????:param?key:
          ????:param?code:
          ????:return:
          ????"""

          ????#?清理過(guò)期的驗(yàn)證碼記錄
          ????CaptchaStore.remove_expired()
          ????try:
          ????????CaptchaStore.objects.get(response=code,?hashkey=key).delete()
          ????????return?True
          ????except?CaptchaStore.DoesNotExist:
          ????????return?False

          代碼里面注釋很清楚了,我可以不用解釋了,哈哈

          寫(xiě)個(gè)新的驗(yàn)證碼接口

          眾所周知,「DjangoStarter」有一個(gè)默認(rèn)的應(yīng)用apps.core,那我們就把驗(yàn)證碼的接口寫(xiě)在這個(gè)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='刷新驗(yàn)證碼')
          @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搞定啦~!

          測(cè)試一下看看,在Swagger或者Postman里請(qǐng)求一下這個(gè)接口:core/refresh_captcha,得到結(jié)果

          {
          ??"message":?"請(qǐng)求成功",
          ??"code":?200,
          ??"data":?{
          ????"key":?"f5275573b0715d2fa9613a73f80a4161ed759061",
          ????"image_url":?"/captcha/image/f5275573b0715d2fa9613a73f80a4161ed759061/",
          ????"audio_url":?null
          ??}
          }

          結(jié)果里除了我們期待的驗(yàn)證碼圖片地址,還有一個(gè)key,客戶端在提交用戶輸入的驗(yàn)證碼時(shí),要把key一并提交,服務(wù)端才能驗(yàn)證這個(gè)提交是否有效。

          檢查驗(yàn)證碼是否匹配

          獲取驗(yàn)證碼有了,接下來(lái)要做的是檢查用戶輸入的驗(yàn)證碼是否正確

          在前面的封裝里,我們已經(jīng)寫(xiě)了verify函數(shù),只需要傳入驗(yàn)證碼的key和用戶輸入的code就好~

          正確的話會(huì)返回True,并且把這條驗(yàn)證碼的記錄刪除,不存在或者錯(cuò)誤的話返回False

          來(lái)一個(gè)例子吧,這個(gè)接口使用的是POST方法,參數(shù)在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='檢查驗(yàn)證碼',
          ????manual_parameters=[
          ????????openapi.Parameter('code',?openapi.IN_FORM,?type=openapi.TYPE_STRING,?description='驗(yàn)證碼'),
          ????????openapi.Parameter('key',?openapi.IN_FORM,?type=openapi.TYPE_STRING,?description='驗(yàn)證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':?'請(qǐng)輸入驗(yàn)證碼'},?status=status.HTTP_400_BAD_REQUEST)

          ????if?captcha.verify(key,?code):
          ????????return?Response({'message':?'驗(yàn)證碼輸入正確'})
          ????else:
          ????????return?Response({'message':?'驗(yàn)證碼錯(cuò)誤'},?status=status.HTTP_403_FORBIDDEN)

          高級(jí)用法

          前面介紹的只是最基礎(chǔ)的用法,可以根據(jù)實(shí)際需求來(lái)自定義生成驗(yàn)證碼的行為,比如手動(dòng)指定驗(yàn)證碼有效期之類的

          要自定義的話,首先得了解驗(yàn)證碼生成的過(guò)程

          先來(lái)看看數(shù)據(jù)庫(kù)表是什么樣的:

          challengeresponsehashkeyexpirationid
          LOKJlokj286f34637808d669f4fd55ebb1877f72d4ab7fa92022-04-08 15:32:41.32875431
          JDNAjdnafb1e57277df26cbd7c20f6a7887f0bed18972e5b2022-04-08 15:32:45.79525932

          可以看到有五個(gè)字段,其中expiration字段就是指定過(guò)期時(shí)間了

          之前封裝生成驗(yàn)證碼方法的時(shí)候,可以看到生成的時(shí)候是調(diào)用CaptchaStore.pick()這個(gè)方法

          其實(shí)這個(gè)CaptchaStoredjango-simple-captcha這個(gè)庫(kù)定義的一個(gè)Django Model,作者在這個(gè)model里定義了pick這個(gè)類方法(class method)來(lái)生成驗(yàn)證碼,我們來(lái)看看源碼實(shí)現(xiàn)

          @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這行代碼,它的作用是從配置中讀取過(guò)期時(shí)間

          所以我們其實(shí)也不用折騰,直接在設(shè)置里配置一下就好了

          不過(guò)注意這里面captcha_settings的引入方式是:from captcha.conf import settings as captcha_settings

          它是對(duì)Django的settings包裝了一層

          具體源碼就不展開(kāi)了

          反正我們?cè)贒jango的settings里面配置CAPTCHA_GET_FROM_POOL_TIMEOUT=10就好了,注意時(shí)間單位是分鐘

          參考資料

          • Django Simple Captcha項(xiàng)目地址:https://github.com/mbi/django-simple-captcha
          • Django Simple Captcha文檔:http://django-simple-captcha.readthedocs.org/en/latest/


          瀏覽 57
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  射久久久久久 | 国产青娱乐在线视频 | 五月天婷婷电影 | 国产成人精品AV在线观 | 不卡的a∨ |