基于Python實現(xiàn)原生的登錄驗證碼
目錄
1、概述
2、驗證碼實現(xiàn)的演進過程
2.1 路由及頁面
2.2 視圖函數(shù)中驗證碼的推導
2.3 登錄驗證中使用驗證碼
2.4 前端頁面點擊自動刷新
3、效果展示
4、小結(jié)

1、概述
在前面的文章中,我有分享到?vue+drf+第三方滑動驗證碼的接入實現(xiàn)?[1](文中也留了坑分享圖片驗證碼功能的實現(xiàn)),即本文將要分享的是基于python實現(xiàn)原生的登錄驗證碼
通常的驗證碼,人眼看上去更像是一張小圖片
在html語法中,嵌入一張圖片一般用img標簽實現(xiàn),而img標簽對應的src一般有以下幾種寫法
圖片的本地路徑 圖片的 url 圖片的二進制數(shù)據(jù)(base64 編碼)
其中前兩種方法都需要外部具有實際存在的圖片,第三種方法則是將圖片進行編碼后填充到img標簽的src下
2、驗證碼實現(xiàn)的演進過程
2.1 路由及頁面
為了實現(xiàn)驗證碼的功能,需要開設一個url單獨處理驗證碼功能,修改全局路由
urlpatterns?=?[
????......
????url(r'^login/',?views.login,?name='login'),
????#?圖片驗證碼
????url(r'^get_code/',?views.get_code,?name='gc'),
????...
]
然后修改前端登錄頁面login.html的驗證碼部分
...
<div?class="form-group">
????<label?for="">驗證碼label>
????<div?class="row">
????????<div?class="col-md-6">
????????????<input?type="text"?name="code"?id="id_code"?class="form-control">
????????div>
????????<div?class="col-md-6">
????????????<img?src="/get_code/"?alt=""?width="430"?height="35"?id="id_img">
????????div>
????div>
div>
<input?type="button"?class="btn?btn-success"?value="登陸"?id="id_commit">
...
2.2 視圖函數(shù)中驗證碼的推導
2.2.1 圖片發(fā)送到前端
前端界面實現(xiàn)了一個簡單的包含驗證碼的登錄框,首先定義一個視圖函數(shù)將后端的測試圖片以二進制的形式發(fā)送到前端進行測試
def?get_code(request):
????#?直接獲取后端現(xiàn)成的圖片二進制數(shù)據(jù)發(fā)送給前端
????with?open(r'static/img/test.jpg','rb')?as?f:
????????data?=?f.read()
????return?HttpResponse(data)
2.2.2 引入動態(tài)圖片
為了操作圖片,主要利用的是pillow模塊
pip3?install?pillow
主要用到了Image、ImageDraw、ImageFont
Image:生成圖片 ImageDraw:在圖片上定義內(nèi)容 ImageFont:控制字體樣式
因此,利用pillow模塊動態(tài)產(chǎn)生圖片的方法為
import?random
from?PIL?import?Image,?ImageDraw,?ImageFont
def?get_random():
????return?random.randint(0,?255),?random.randint(0,?255),?random.randint(0,?255)
def?get_code(request):
????#?利用pillow模塊動態(tài)產(chǎn)生圖片
????#?img_obj?=?Image.new('RGB',(430,35),'green')??#?RGB組合、圖片尺寸、顏色
????img_obj?=?Image.new('RGB',(430,35),get_random())??#?通過色值指定顏色
????#?先將圖片對象保存起來
????with?open('xxx.png','wb')?as?f:
????????img_obj.save(f,'png')
????#?再將圖片對象讀取出來
????with?open('xxx.png','rb')?as?f:
????????data?=?f.read()
????return?HttpResponse(data)
2.2.3 內(nèi)存管理模塊圖片
上面將圖片對象保存在讀取有些麻煩,文件存儲繁瑣IO操作效率低 ,可以借助于內(nèi)存管理器模塊實現(xiàn)
其中又分為BytesIO和StringIO兩種
BytesIO:臨時存儲數(shù)據(jù),返回的時候數(shù)據(jù)是二進制 StringIO:臨時存儲數(shù)據(jù),返回的時候數(shù)據(jù)是字符串
內(nèi)存管理對象就相當于上面的文件句柄
import?random
from?PIL?import?Image,?ImageDraw,?ImageFont
def?get_random():
????return?random.randint(0,?255),?random.randint(0,?255),?random.randint(0,?255)
def?get_code(request):
????img_obj?=?Image.new('RGB',?(430,?35),?get_random())
????io_obj?=?BytesIO()??#?生成一個內(nèi)存管理器對象??你可以看成是文件句柄
????img_obj.save(io_obj,'png')
????return?HttpResponse(io_obj.getvalue())??#?從內(nèi)存管理器中讀取二進制的圖片數(shù)據(jù)返回給前端
這樣一來,圖片的生成以及返回就比較友好了
2.2.4 完整圖片驗證碼
上面解決了圖片如何傳遞到前端頁面的問題,剩下的就是如何生成對應的隨機驗證碼了
例如隨機驗證碼為五位數(shù)的隨機驗證碼,包含數(shù)字、小寫字母、大寫字母
import?random
from?PIL?import?Image,?ImageDraw,?ImageFont
def?get_random():
????return?random.randint(0,?255),?random.randint(0,?255),?random.randint(0,?255)
def?get_code(request):
????#?寫圖片驗證碼
????img_obj?=?Image.new('RGB',?(430,?35),?get_random())
????img_draw?=?ImageDraw.Draw(img_obj)??#?產(chǎn)生一個畫筆對象
????#?引入本地的字體文件,指定字體樣式及字體大小
????img_font?=?ImageFont.truetype('static/font/222.ttf',?30)
????#?隨機驗證碼?五位數(shù)的隨機驗證碼??數(shù)字?小寫字母?大寫字母
????code?=?''
????for?i?in?range(5):
????????random_upper?=?chr(random.randint(65,?90))??#?隨機大寫字母
????????random_lower?=?chr(random.randint(97,?122))??#?隨機小寫字母
????????random_int?=?str(random.randint(0,?9))??#?隨機數(shù)字
????????#?每次從上面三個里面隨機選擇一個
????????tmp?=?random.choice([random_lower,?random_upper,?random_int])
????????#?將產(chǎn)生的隨機字符串寫入到圖片上,需要調(diào)整每個字體所在的坐標變換
????????img_draw.text((i?*?60?+?60,?-2),?tmp,?get_random(),?img_font)
????????#?拼接隨機字符串
????????code?+=?tmp
????print(code)
????#?隨機驗證碼在登陸的視圖函數(shù)里面需要要比對,所以要找地方存起來并且其他視圖函數(shù)也能拿到
????request.session['code']?=?code
????io_obj?=?BytesIO()
????img_obj.save(io_obj,?'png')
????return?HttpResponse(io_obj.getvalue())
這里有一點,在寫入隨機文字的時候一個個寫而不是生成好了之后再寫,是因為一個個寫能夠控制每個字體的間隙。而生成好之后再寫的話,間隙就沒法控制了
2.3 登錄驗證中使用驗證碼
上面將每次生成的驗證碼存儲到了session中,這樣在前端傳過來的驗證碼,登錄校驗時就可以進行比對了
......
def?login(request):
????if?request.method?==?'POST':
????????back_dic?=?{'code':?1000,?'msg':?''}
????????username?=?request.POST.get('username')
????????password?=?request.POST.get('password')
????????code?=?request.POST.get('code')
????????#?1?先校驗驗證碼是否正確??自己決定是否忽略大小寫?統(tǒng)一轉(zhuǎn)大寫或小寫再比較
????????if?request.session.get('code').upper()?==?code.upper():
????????????#?校驗用戶名和密碼是否正確
????????????user_obj?=?auth.authenticate(request,username=username,password=password)
????????????if?user_obj:
????????????????#?保存用戶狀態(tài)
????????????????auth.login(request,user_obj)
????????????????back_dic['url']?=?'/home/'
????????????else:
????????????????back_dic['code']?=?2000
????????????????back_dic['msg']?=?'用戶名或密碼錯誤'
????????else:
????????????back_dic['code']?=?3000
????????????back_dic['msg']?=?'驗證碼錯誤'
????????return?JsonResponse(back_dic)
????return?render(request,?'login.html')
2.4 前端頁面點擊自動刷新
最后,還留下一點小問題,前端在輸入驗證碼錯誤后不會自動刷新,如果點擊驗證碼也不會進行刷新,只能通過刷新登錄頁面才能刷新驗證碼,因此需要想辦法讓用戶在點擊驗證碼時自動刷新(單獨觸發(fā)驗證碼的視圖函數(shù))
每次在點擊時,修改對應src的值即可,可以通過一小段js實現(xiàn)
...
???????<div?class="form-group">
????????????<label?for="">驗證碼label>
????????????<div?class="row">
????????????????<div?class="col-md-6">
????????????????????<input?type="text"?name="code"?id="id_code"?class="form-control">
????????????????div>
????????????????<div?class="col-md-6">
????????????????????<img?src="/get_code/"?alt=""?width="430"?height="35"?id="id_img">
????????????????div>
????????????div>
????????div>
????????<input?type="button"?class="btn?btn-success"?value="登陸"?id="id_commit">
...
<script>
????$("#id_img").click(function?()?{
????????//?獲取標簽之前的src
????????let?oldVal?=?$(this).attr('src');
????????$(this).attr('src',oldVal?+=?'?')
????})
script>
3、效果展示
最終前端的驗證碼效果如圖

4、小結(jié)
本文基于python以及相關(guān)的庫原生實現(xiàn)了登錄驗證碼邏輯~
其實寫本文也是因為之前有過想法但是一段時間就忘了,最近通過某銀行手機銀行app重置登錄密碼的時候,發(fā)現(xiàn)其驗證碼顯示效果和本文效果竟然神似~,于是撿起來寫了此文
其手機app驗證碼效果如下

See you ~
參考資料
https://www.ssgeek.com/post/vuedrfdi-san-fang-hua-dong-yan-zheng-ma-de-jie-ru-shi-xian/
? 歡迎進群一起進行技術(shù)交流
? 加群方式:公眾號消息私信“加群”或加我好友再加群均可
