到我們點擊項目首頁的注冊時,會彈出一個注冊頁面,里面需要我們后台提供圖形驗證碼以及手機驗證碼
下面我們來依次處理
圖形驗證碼的處理
我們先來簡單的做個分析
首先當我們點擊注冊時,我們需要給到通過瀏覽器給服務器發送一個隨機碼來進行下次請求時雙方的校驗,用瀏覽器的Javascript生成UUID來解決
,通過GET請求發送(通過兩個方面來考慮,第一個這個本身不需要加密,第二個是通過img標簽里src默認為GET)
當我們給服務器發送了UUID之后,服務器對UUID進行保存,然后校驗UUID,然后生成圖片驗證碼(圖片驗證碼分為三個部分,生成的圖片驗證碼名字,圖片里的驗證碼文本,畫上了驗證碼的圖片),我們需要把UUID保存在redis中,且作為保存值的鍵,然后提取圖片驗證碼的驗證碼文本作為保存的值。然后我們再給瀏覽器返回畫上了驗證碼的圖片就可以了。這樣當用戶通過返回的圖形驗證碼圖片里的文字輸入了文本驗證碼后再次給到服務器,這里瀏覽器還需要將UUID和輸入文本的
文本驗證碼一起給到服務器。因為服務器需要通過給到的UUID作為取值的鍵來取上次保存的數據,也是做進一步的校驗和實現狀態保存,你是上次給我UUID的那個瀏覽器。只有當兩次請求給到服務器的UUID一致時才能夠進行上次生成圖片驗證碼文本的提取
大致分析完之后我們就開始寫代碼
瀏覽器前端的處理:
我們來到我們首頁的HTML文件,打開我們項目下的index.html,然后找到我們圖形驗證碼的相關內容,
最初文件的img標簽, src 里面有一堆數據,我們先復制一下最初的img標簽,復制了然后注釋掉,然后再進行刪除
然后Ctrl加左鍵點擊函數來到函數的內容頁面,這里面就有我們圖片驗證碼的相關內容:
最初的文件里function generateImageCode() {}里面是空的,這里我把代碼補充了,代碼我也復制再下面並做個簡單的解釋;
function generateImageCode() {
imageCodeId = generateUUID() #生成UUID,文件的最下面有生成UUID的函數,這里只是函數的調用和用新的變量來對imageCOdeId變量進行覆蓋
var url = "/passport/imageCode?imageCodeId="+ imageCodeId #自定義一個URL地址的變量,后台先創建對應的視圖,然后再填寫對應的視圖URL加上?kv
將UUID通過GET請求給到服務器后台
$(".get_pic_code").attr("src",url) #將構造好的URL給到url
}
文件后面生成UUID的函數
后台創造新的視圖函數:
首先在我們的moduls文件夾里面再創建一個新的Python文件夾(passport)用來存放我們登錄注冊相關的相關內容,然后建立一個叫views 的Python文件來存放相關的視圖函數
然后就是在__init__里面寫入藍圖三步,這里我們需要在實例化的時候加入url_prefix,也就是前綴,一定要記得在前綴最開始加上反斜杠/
完成之后再在我們項目的info的__init__文件夾,然后導入注冊藍圖
完成之后來到我們的視圖函數頁面進行視圖函數的編寫,這里先簡單的編寫一下路由和函數就可以了,后面我們再進行補全,這里主要是為了補全我們前端的URL地址:
當我們完成對前端文件的修改后,再次執行主文件並點擊主頁面的注冊,然后后端查看請求就可以看到我們的UUID 了,當點擊圖片驗證碼時會刷新請求出現攜帶不同的UUID的請求 :
這樣我們圖形驗證碼前端HTML文件的修改就完成了
后端代碼的處理(對應視圖函數的編寫):
當我們去寫項目的視圖函數時需要在我們函數的文本注釋里面寫上我們的思路以及相應的實現步驟,這樣方便我們后續代碼的編寫,這是十分重要的。
我簡單的分析一下圖片里的步驟,並對沒一步 的代碼進行一個拆分
我們需要接收UUID來作為保存圖片驗證碼 的值以及實現第二次交互時的狀態保持,也就是用戶填寫好驗證碼之后進行校驗,這就是第一步:
接收參數需要在flask里面導入request
# 1.接受參數(imageCodeId)
imageCodeId = request.args.get("imageCodeId") (接收的get請求的數據,post為from)
對UUID進行簡單的校驗,判斷是否存在這就是第二步:
# 2.校驗參數
# 2.1校驗imageCodeId是否存在
if not imageCodeId: #通過取反來判斷為空,並進行自定義異常的拋出
abort(403)
拋出自定義異常需要flask里面導入abort
然后生成我們的圖片驗證碼,這一步需要第三工具包來實現:
首先在我們的info文件夾里建立一個我們存放第三方工具的新文件夾utils,然后將我們生成圖片驗證碼的文件放在里面
在生成圖片驗證碼的captcha.py文件里面有測試用例的編寫,是可以執行並進行查看是否有對應的生成,是可以執行的,記得執行進行測試確認是否能用。
控制台里生成的內容分別為,本次生成的圖片驗證碼編號(也就是名字),本次生成的文字文檔,圖片的二進制格式
無誤后在編寫視圖函數的views文件里面進行導入
注意這里的導入的三個captcha 的對應,第一個是captcha是文件夾,第二個是Python文件,第三個是文件里面的實例化變量
然后就可以寫我們生成的第三步,生成我們的圖形驗證碼,並進行拆包用三個變量來接收我們每一次生成的圖片驗證碼的內容:
# 3.生成圖形驗證碼證碼 name, imageCode, image = captcha.generate_captcha() #這里是對元祖進行了拆分,每個變量存入不同的內容,分別是本次生成的圖片驗證碼編號(也就是名字),
本次生成的文字文檔(驗證碼文本),圖片的二進制格式(圖片)
完成圖片驗證碼的生成之后就可以進行第四步的處理
首先導入我們創建好的連接redis數據庫的變量,以及存放着我們數據設置的文件 constants,它里面有我們對數據的一些設置
然后用我們的數據庫變量來進行第四步的操作
try: #因為存在連接不上數據庫的問題所以需要先進行異常捕捉並進行處理 redis_store.set("imageCode"+imageCodeId,imageCode,ex=constants.IMAGE_CODE_REDIS_EXPIRES) #ex設置的是數據保存的有效時間
#鍵進行了我們接收的UUID值和前綴的拼接,值為生成圖片驗證碼 的文本驗證碼
except Exception as e :
current_app.logger.error(e) #這里需要從falsk導入current_app來寫入本次異常信息並進行自定義異常的處理
abort(500)
完成之后我們完成最后一步返回結果,也就是給瀏覽器發送我們本次的二進制圖片,並交給前端進行渲染
res=make_response(image) #這里我們需要對返回相應的相應頭Content-Type進行處理,不然我們在頁面查看源代碼會是亂碼,這里我們也需要從flask導入make_response
res.headers["Content-Type"] = "image/jpg" return res
完成之后我們圖片驗證碼 的生成以及后續通過對比校驗用戶的填寫是否正確就完成了,打開主頁面的注冊就會顯示我們的圖片驗證碼每次刷新也會不同:
完整的代碼:
@passport_blue.route("/imageCode",methods=["GET"])
def image_code():
"""
提供圖片驗證碼
1.接受參數 (imageCodeId)
2.校驗參數
2.1 校驗imageCodeId是否存在
3.生成圖形驗證碼
4.將文字的圖形驗證碼保存到redis
5.返回結果
:return:
"""
# 1.接受參數(imageCodeId)
imageCodeId = request.args.get("imageCodeId")
# 2.校驗參數
# 2.1校驗imageCodeId是否存在
if not imageCodeId:
abort(403)
# 3.生成圖形驗證碼證碼把存到redis
name, imageCode, image = captcha.generate_captcha()
print(imageCode)
# 4.將文字的圖形驗證碼保存到rides
try:
redis_store.set("imageCode"+imageCodeId,imageCode,ex=constants.IMAGE_CODE_REDIS_EXPIRES)
except Exception as e :
current_app.logger.error(e)
abort(500)
# 5.返回結果
res=make_response(image)
res.headers["Content-Type"] = "image/jpg"
return res
短信驗證碼的處理
短信驗證碼的處理需要通過下圖中的接口設計來實現,因為我們需要通過第三方給用戶發送短信,所以有一個規范。
前端代碼的處理
首先我們還是先來處理前端的HTML,首先找到短信驗證碼的相關代碼
然后Ctrl加鼠標左鍵點擊進入函數的文件后找到手機驗證碼對應的函數,然后進行修改,下面是手機驗證碼的函數代碼
// 發送短信驗證碼
function sendSMSCode() {
// 校驗參數,保證輸入框有數據填寫
$(".get_code").removeAttr("onclick");
var mobile = $("#register_mobile").val();
if (!mobile) {
$("#register-mobile-err").html("請填寫正確的手機號!");
$("#register-mobile-err").show();
$(".get_code").attr("onclick", "sendSMSCode();");
return;
}
var imageCode = $("#imagecode").val();
if (!imageCode) {
$("#image-code-err").html("請填寫驗證碼!");
$("#image-code-err").show();
$(".get_code").attr("onclick", "sendSMSCode();");
return;
}
var params = {
'mobile':mobile, #用戶填寫的手機號,需要給到服務器
'image_code':imageCode, # 用戶填寫的短信驗證碼,需要給到服務器
'image_code_id':imageCodeId #用戶本次生成圖形驗證碼的UUID
};
// TODO 發送短信驗證碼
$.ajax({ #規范的接口設計,包括請求地址以及方式和參數
url:'/passport/sms_code', // 請求地址
type:'post', // 請求方法
data:JSON.stringify(params),// 請求參數
contentType:'application/json',// 數據類型
success:function (response) { // 回調函數
if (response.errno == '0') {
// 發送短信驗證碼成功
alert(response.errmsg);
} else {
alert(response.errmsg);
}
}
});
}
前端頁面代碼的修改完成之后,編寫后端的代碼。
后端代碼的處理(對應視圖函數的編寫):
和圖片驗證碼一樣,我們需要在passport文件夾的views文件里寫入對應的視圖函數,也就是寫在我們圖片驗證碼的后面。
首先寫入對應的視圖函數名和URL地址 ,這里我們規定了只接收post請求,這是按照一開始的接口規范來設置的。
完成之后我們也需要先寫入注釋文檔,說明函數的作用以及分析思路並寫入實現的步驟順序。
首先該函數的作用為:注冊時短信驗證碼的發送
然后先做個簡單的分析
第一步肯定還是接收參數,這次我們根據接口設計的圖片得知我們有三個參數需要接收
1,用戶填寫的手機號
2,用戶填寫的短信驗證碼
3,瀏覽器生成的UUID (這個是用來校驗圖形驗證碼)
第二步校驗參數,首先檢查是否傳全,然后分別檢驗
第三步當數據全且無誤時,在后台生成我們的4位數的短信驗證碼
第四步 將驗證碼保存到我們的數據庫中
第五步通過第三方給用戶發送我們后天生成的驗證碼
第六步返回結果
""" #函數的注釋文檔
注冊時短信發送
1. 接收參數(mobile,image_code,image_code_id)
2. 校驗參數
2.1 判斷參數是否齊全(mobile,image_code,image_code_id)
2.2 校驗手機號是否正確,正則
2.3 查看redis 是否有image_code_id
否:返回json數據
2.4 校驗用戶填寫的驗證碼是否正確
否:返回json數據
3.生成短信驗證碼
4.將短信驗證碼保存到redis
5.發送手機短信
否:返回json數據
6.返回結果
:return:
"""
下面我們還是通過編寫每一步的代碼來實現整個短信驗證碼的功能
第一步
# 1. 接收參數(mobile,image_code,image_code_id)
mobile=request.json.get("mobile") #接收json數據對應的參數
image_code=request.json.get("image_code")
image_code_id=request.json.get("image_code_id")
所有接收的變量的命名是通過最開始的端口設計規范來寫的,以及規定了發送的是json數據,所以我們對json 數據進行接收.request.json,這里還是用了flask的request包
第二步
# 2. 校驗參數
# 2.1 判斷參數是否齊全(mobile,image_code,image_code_id)
if not all([mobile,image_code,image_code_id]):
return jsonify(errno=RET.PARAMERR,errmsg="參數缺失")
# 2.2 校驗手機號是否正確,正則
mobile=mobile.strip() #去空
if not re.findall("^1[3|4|5|7|8][0-9]{9}$",mobile):
return jsonify(errno=RET.PARAMERR,errmsg="手機號格式有誤")
# 2.3 查看redis 是否有image_code_id
try: #對數據庫連接進行異常捕捉
image_code_server = redis_store.get("imageCode" + image_code_id)
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR,errmsg="數據庫連接失敗")
# 否:返回json數據
if not image_code_server: #沒有找到對應鍵值對json數據的返回
return jsonify(errno=RET.NODATA,errmsg="未找到該圖片驗證碼")
# 2.4 校驗用戶填寫的驗證碼是否正確
if image_code_server.strip().lower() != image_code.strip().lower(): #去空strip,小寫lower
return jsonify(errno=RET.PARAMERR,errmsg="圖片驗證碼填寫錯誤")
# 否:返回json數據
首先判斷所有參數是否齊全,通過if not 取反即可,然后是通過手機號的正則寫法來判斷手機號是否正確,這里返回json數據也是通過最開始的接口設計,
通過flask導入jsonify就可以給前端返回json數據
通過規范我們得知需要返回兩個參數,分別是 errno(錯誤碼),errmsg(錯誤信息) ,錯誤信息有我們自己填寫字符串來進行本次錯誤的描述,而錯誤碼在我們之前復制
到項目的response_code.py的文件中
它里面有一個記錄了errno的類RET,以及每個類屬性的中文注釋
我們將它導入到我們的視圖文件views中,然后通過調用類屬性的方法也就是對象.的形式來進行相關的調用
當我們進行第三方操作時,就需要進行異常捕捉,數據庫有時候會連接不上
當我們查看redis 是否有image_code_id,也就是redis保存的文字驗證碼是否存在,這里需要先查看所UUID的值拼接上
前綴所產生的鍵是否存在,當和最開始拼接的保存文字驗證碼的鍵一致時,就可以取出里面的文字驗證碼來對用戶輸入的
文字驗證碼進行校驗,所以首先我們先判斷鍵值對是否存在,然后再通過取出值來判斷用戶輸入的文字驗證碼是否正確,也就是
image_code.這里我們為了用戶體驗進行了字符串去空和小寫的設置,這一步也是很重要的,手機號也進行了去空。這樣我們接收的三個數據
也就分別校驗完畢了
第三步
3.生成短信驗證碼
smsCode="%04d" %random.randint(0, 9999)
這一步通過了導入random隨機數包來生成最大位數為4位的隨機數,然后通過格式化輸出中不滿足位數自動補0的方法來進行完成驗證碼的生成。
第四步
# 4.將短信驗證碼保存到redis #對數據庫連接異常進行捕捉
try:
redis_store.set("SMScode:"+mobile,smsCode,ex=constants.SMS_CODE_REDIS_EXPIRES) #ex 設置有效時長
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR,errmsg="數據庫連接失敗")
當我們使用第三方工具時都需要進行異常捕捉,因為我們不確定我們與第三方之間的連接是否正常,問題隨時都有可能發生,所以進行異常捕捉的處理
這里我們也對鍵進行了拼接,前綴+用戶的手機號,然后設置了保存的有效時長,方法同保存的圖片驗證驗證碼時長一樣,然后對錯誤json數據的返回
第五步
# 5.發送手機短信
# try:
# sms_res = CCP().send_template_sms(mobile, [smsCode, constants.SMS_CODE_REDIS_EXPIRES / 60], 1) #復制測試時文件的代碼,進行相關的修改
# except Exception as e :
# current_app.logger.error(e)
# return jsonify(errno=RET.THIRDERR,errmsg="發送短信失敗")
sms_res = 0
if sms_res != 0 :
return jsonify(errno=RET.THIRDERR,errmsg="發送短信失敗")
# 否:返回json數據
if sms_res != 0 : #通過查看元文件代碼可以發現發送成功會返回結果0,
失敗為-1,所以還需要對結果進行校驗,這里的問題有可能是第三方不能給用戶發送短信所造成的
return jsonify(errno=RET.THIRDERR,errmsg="發送短信失敗")
這里需要導入我們第二個第三方工具包
首先在info里面再創建一個新的文件夾libs來存放第三方平台
libs用來存放第三方平台,然后utils用來存放第三方文件
將我們的第三方平台的文件夾復制到里面,還是先進入到我們要使用的sms.py里面執行進行測試
如果成功后就導入到我們views文件里面
這里我們導入的是大寫的CCP,一個沒有實例化的類,然后進行手機短信的發送和相關異常的代碼的處理。
過來就行這樣手機驗證碼的處理就完成了
第六步
# 6.返回結果
return jsonify(errno=RET.OK,errmsg="發送成功")
這里也需要按照格式返回json數據,這里返回的結果為正常的errno,也就是OK。
這樣短信驗證碼的處理就完成了,但是還有兩個問題需要解決
發送短信驗證碼中問題的解決
CRSF防護
當我們開啟了CRSF防護時,每次瀏覽器給服務器發送post請求時,也就是比較私密的請求數據時就會提示,這里我們只是先將CRSF防護關閉,
到了項目的最后來進行統一的處理
所以第一個問題CRSF防護問題的解決就是先關掉它,在info的__init__文件里里注釋掉
第二個問題是當我們通過DEBUG模式打斷點接收數據會發現,我們保存到redis的文本驗證碼數據類型和用戶輸入的文本驗證碼數據類型會不一樣,兩個
不同的數據類型的變量是沒有辦法進行對比的,我們需要改變數據類型來讓它們保持一致並進行比較,
我們接收到的用戶輸入的驗證碼數據類型為str,而我們之前保持到redis當中的文本驗證碼數據類型為字節,在redis的保存中,一般保存數據為二進制,所以我們
需要對保存在redis中的文本驗證碼的數據類型進行設置,讓它以文本類型進行保存,這樣方便我們做對比
首先我們創建的redis連接對象的變量代碼,再最后加上一個參數,它會將返回的數據類型進行轉碼。設置為TRUE就行
decode_responses=True
完整的代碼為
redis_store=StrictRedis(host=configs[config_name].REDIS_HOST,port=configs[config_name].REDIS_PORT,decode_responses=True)
設置完成后重啟就可以了,這樣數據類型就一樣了。
還有可以問題為第三方台
當我們使用第三方平台時需要注冊並設置可以接收短信的手機號,最開始我們只能設置三個,也就是有三個用戶可以完成注冊,這樣是不行的,所以我們需要將我們的第五步注釋掉
並使它返回的結果為0,我們可以直接通過后台拿到我們生成好的驗證碼直接復制到注冊頁面來進行后續用戶的注冊操作
放幾張第三方平台數據的截圖和使用(通過sms.py文件):
完整的代碼:
@passport_blue.route("/sms_code",methods=["POST"])
def sms_code():
"""
注冊時短信發送
1. 接收參數(mobile,image_code,image_code_id)
2. 校驗參數
2.1 判斷參數是否齊全(mobile,image_code,image_code_id)
2.2 校驗手機號是否正確,正則
2.3 查看redis 是否有image_code_id
否:返回json數據
2.4 校驗用戶填寫的驗證碼是否正確
否:返回json數據
3.生成短信驗證碼
4.將短信驗證碼保存到redis
5.發送手機短信
否:返回json數據
6.返回結果
:return:
"""
# 1. 接收參數(mobile,image_code,image_code_id)
mobile=request.json.get("mobile")
image_code=request.json.get("image_code")
image_code_id=request.json.get("image_code_id")
# 2. 校驗參數
# 2.1 判斷參數是否齊全(mobile,image_code,image_code_id)
if not all([mobile,image_code,image_code_id]):
return jsonify(errno=RET.PARAMERR,errmsg="參數缺失")
# 2.2 校驗手機號是否正確,正則
mobile=mobile.strip()
if not re.findall("^1[3|4|5|7|8][0-9]{9}$",mobile):
return jsonify(errno=RET.PARAMERR,errmsg="手機號格式有誤")
# 2.3 查看redis 是否有image_code_id
try:
image_code_server = redis_store.get("imageCode" + image_code_id)
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR,errmsg="數據庫連接失敗")
# 否:返回json數據
if not image_code_server:
return jsonify(errno=RET.NODATA,errmsg="未找到該圖片驗證碼")
# 2.4 校驗用戶填寫的驗證碼是否正確
if image_code_server.strip().lower() != image_code.strip().lower():
return jsonify(errno=RET.PARAMERR,errmsg="圖片驗證碼填寫錯誤")
# 否:返回json數據
# 3.生成短信驗證碼
smsCode="%04d" %random.randint(0, 9999)
print(smsCode)
# 4.將短信驗證碼保存到redis
try:
redis_store.set("SMScode:"+mobile,smsCode,ex=constants.SMS_CODE_REDIS_EXPIRES)
except Exception as e :
current_app.logger.error(e)
return jsonify(errno=RET.DBERR,errmsg="數據庫連接失敗")
# 5.發送手機短信
# try:
# sms_res = CCP().send_template_sms(mobile, [smsCode, constants.SMS_CODE_REDIS_EXPIRES / 60], 1)
# except Exception as e :
# current_app.logger.error(e)
# return jsonify(errno=RET.THIRDERR,errmsg="發送短信失敗")
sms_res = 0
if sms_res != 0 :
return jsonify(errno=RET.THIRDERR,errmsg="發送短信失敗")
# 否:返回json數據
# 6.返回結果
return jsonify(errno=RET.OK,errmsg="發送成功")