1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        探索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)防止用戶(hù)亂提交,正好我的「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里訪(fǎng)問(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,客戶(hù)端在提交用戶(hù)輸入的驗(yàn)證碼時(shí),要把key一并提交,服務(wù)端才能驗(yàn)證這個(gè)提交是否有效。

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

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

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

        正確的話(huà)會(huì)返回True,并且把這條驗(yàn)證碼的記錄刪除,不存在或者錯(cuò)誤的話(huà)返回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)證碼有效期之類(lèi)的

        要自定義的話(huà),首先得了解驗(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è)類(lèi)方法(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)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国模一区二区三区四区 | 宝贝腿张大点就不疼了bl | 国内操逼网站 | 午夜爽爽爽| 91视频在线观看18 | 日韩国产成人无码A片 | 午夜成人福利视频网站 | 日韩国产欧美一区二区人妻 | 无码免费在线视频 | 打炮影院视频色网 |