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

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

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

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