几种开发时安全验证的实现
額,一個突然的交流讓我想起來我耽擱許久各種驗證的實現遲遲沒做過 趁著這個機會就搞了一下 分為三部分:郵箱驗證,短信驗證,圖片驗證碼
文章完整代碼
郵箱驗證
這個部分是主要參考的經典書籍-狗書 思路就是根據用戶某些信息通過JSON Web簽名生成token,然后再發送郵件驗證,經典思路 生成和驗證函數都加載在模型中
itsdangerous中文文檔
這里介紹了幾種簽名方式
token生成和驗證 TimedJSONWebSignatureSerializer,看這個表面詞的意思可以看出這里序列化加入了當前時間 這也是實現設置過期時間的依據吧 查看itsdangerous源碼可以看到具體的加密方式
from itsdangerous import (TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired) ... class User(db.Model):__tablename__ = 'user'id = db.Column(db.Integer,primary_key=True)name=db.Column(db.String(64),unique=True,index=True)def genter_auth_token(self,expiration=300): #設置有效期s=Serializer(current_app.config['SECRET_KEY'],expires_in=expiration)return s.dumps({'code':self.name}) #將用戶名當作簽名對象def verify_auth_token(token):s=Serializer(current_app.config['SECRET_KEY'])try:data=s.loads(token) #加載數據except BadSignature:return Noneexcept SignatureExpired:return Nonereturn data 復制代碼同時這里itdangerous類的簽名方式都可以接收一個salt 文檔中這樣描述了salt的作用:
itsdangerous中的鹽,是為了一個截然不同的目的而產生的。你可以將它視為成命名空間 假設你想簽名兩個鏈接。你的系統有個激活鏈接,用來激活一個用戶賬戶,并且你有一個升級鏈接,可以讓一個用戶賬戶升級為付費用戶,這兩個鏈接使用email發送。在這兩種情況下,如果你簽名的都是用戶ID,那么該用戶可以在激活賬戶和升級賬戶時,復用URL的可變部分。現在你可以在你簽名的地方加上更多信息(如升級或激活的意圖),但是你也可以用不同的鹽
即只有使用相同鹽的序列化器才能成功把值加載出來
def genter_auth_token(self,expiration=300):s=Serializer(current_app.config['SECRET_KEY'],salt='activate-salt',expires_in=expiration)return s.dumps({'code':self.name}) def verify_auth_token(token):s=Serializer(current_app.config['SECRET_KEY'],salt='activate-salt') 復制代碼圖片驗證碼
這個驗證碼可以直接調用一些平臺的智能驗證,也可以用另一種 另一個也許是比較傳統的思路,就是自己生成的圖片水印,保存驗證碼 python和php里都有相應的圖片操作方法,這里就寫下python的
流程就是生成任意的數字,保存,添加圖片水印
這里肯定要用的python強大的圖片處理庫PIL,其中用到了 加線條,濾鏡等增加干擾 下面是完整代碼,該做注釋的地方我已經加了注釋 看代碼之前,最好先好好看下PIL官方文檔,和一些基本概念 部分我參考的博文也貼在了文末
#!/usr/bin/env python #coding=utf-8 import os import random from flask import Flask,send_from_directory from PIL import Image,ImageFont,ImageDraw,ImageFilterapp=Flask(__name__) app.debug=Trueclass picture:def __init__(self):self.size = (240,60)self.mode="RGB"self.color="white"self.font = ImageFont.truetype("C:\Windows\Fonts\Arial.ttf", 36) #設置字體大小def randChar(self):basic='23456789abcdefghijklmnpqrstwxyzABCDEFGHIJKLMNPQRSTWXYZ'return basic[random.randint(0,len(basic)-1)] #隨機字符def randBdColor(self):return (random.randint(64,255),random.randint(64,255),random.randint(64,255)) #背景def randTextColor(self):return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127)) #隨機顏色def proPicture(self):new_image=Image.new(self.mode,self.size,self.color) #創建新圖像有三個默認參數:尺寸,顏色,模式drawObject=ImageDraw.Draw(new_image) #創建一個可以對image操作的對象line_num = random.randint(4,6) # 干擾線條數for i in range(line_num):#size=(240,60)begin = (random.randint(0, self.size[0]), random.randint(0, self.size[1]))end = (random.randint(0, self.size[0]), random.randint(0, self.size[1]))drawObject.line([begin, end], self.randTextColor())for x in range(240):for y in range(60):tmp = random.randint(0,50)if tmp>30: #調整干擾點數量drawObject.point((x,y),self.randBdColor())randchar='' for i in range(5):rand=self.randChar()randchar+=randdrawObject.text([50*i+10,10],rand,self.randTextColor(),font=self.font) #寫入字符new_image = new_image.filter(ImageFilter.SHARPEN) # 濾鏡 return new_image,randchar def get_file(filename):return send_from_directory(os.getcwd(),filename) def index():test=picture()image,code=test.proPicture()image.save('new.jpg')url="http://127.0.0.1:5000/new.jpg"return '<img src='+url+' /><br/>'+"圖中的code為:"+code #這里有緩存,需要CTRL+F5才會有效果if __name__=="__main__":app.run() 復制代碼另外,有的前輩會再加入了扭曲圖像增加分辨難度
# 圖形扭曲參數 params = [1 - float(random.randint(1, 2)) / 100, 0, 0, 0, 1 - float(random.randint(1, 10)) / 100, float(random.randint(1, 2)) / 500, 0.001, float(random.randint(1, 2)) / 500 ] img = img.transform(size, Image.PERSPECTIVE, params) # 創建扭曲 復制代碼這里有篇文章詳細的介紹了下:
對當前圖像進行透視變換,產生給定尺寸的新圖像。 變量data是一個8元組(a,b,c,d,e,f,g,h),包括一個透視變換的系數。對于輸出圖像中的每個像素點,新的值來自于輸入圖像的位置的(a x + b y + c)/(g x + h y + 1), (d x+ e y + f)/(g x + h y + 1)像素,使用最接近的像素進行近似
這個的源定義就牽涉到了一個仿射變換,涉及一些數學的計算 看得我有點懵逼,就沒加到我的代碼中,先留坑
這個地方現在很多網站會使用另一種回答問題的方式,這個方法的實現 我個人感覺也是應該也是相同的手段,只是將隨機的字符串改為問題,將驗證方式改為答案 不過這里或許要把問題和答案存進數據庫,更方便點,也才能實現
短信驗證
有時候想自己是不是出生太晚了。。。。。想寫的東西,都能搜到很好的博文,如下:flask開發restful api系列(5)-短信驗證碼 這里云通訊是文中所用平臺的開發文檔,不過平臺可以自由選擇,結果都是一樣 這里就簡化一下前輩的代碼,把關于驗證碼處理的重點代碼擼了出來,用到了Redis,我也趁機學了一波,的確挺好用的
import redis import randomphonenumber=188888888 #這里可以利用正則過濾一下電話號碼,比如: #/^(13[0-9]|14[5-9]|15[0-9]|16[6]|17[0-8]|18[0-9]|19[8-9])\d{8}$/conn=redis.StrictRedis(host='127.0.0.1',port=6379)def producCode():verifyCode=str(random.randint(100000,999999))pipe=conn.pipeline() #添加管道,可以一次連接執行多次命令pipe.set("phone%s"%phonenumber,verifyCode)pipe.expire("phone%s"%phonenumber,60) #設置過期時間一分鐘pipe.execute()def checkCode():pipe=conn.pipeline() #添加管道,可以一次連接執行多次命令pipe.set('postNum%s'%phonenumber,'0')validate_number = request.get_json().get('validate_number')pipe.incr('postNum%s'%phonenumber) #記錄提交次數防止爆破if conn.get('postNum%s'%phonenumber)>3:pass...if validate_number != validate_number_in_redis:return jsonify({'code': 0, 'message': '驗證沒有通過'})pipe.set('is_validate:%s' % phone_number, '1') #通過驗證碼設置value為1pipe.expire('is_validate:%s' % phone_number, 120)pipe.execute()return jsonify({'code': 1, 'message': '驗證通過'}) def postMessage():result=conn.get("phone%s"%phonenumber)#此時如果通過驗證碼,result為1,否則為0...#剩下的其他操作 復制代碼這里提到了泄露接口導致驗證碼爆破的情況,我也添加了一些代碼 另外就是在某些功能模塊,也易出現漏洞,比如修改資料處,驗證碼不僅僅要與phone一致 也要檢查用戶名的一致性,要不然如果只是通過驗證碼,用戶修改為自己的號碼,驗證碼手機號都通過驗證 (感覺一般人不會出現這種錯誤)
而你的代碼又是直接傳入用戶名進行修改操作,這將可能導致任意用戶重置密碼 或者你的代碼直接將phone作為索引進行修改
參考文章: 狗書authentication
雜項之圖像處理pillow
PIL一些基本概念
PIL中的Image模塊
Python PIL ImageDraw和ImageFont模塊學習
Python圖像處理庫PIL的ImageFilter模塊介紹
Redis中文文檔
redis-py
總結
以上是生活随笔為你收集整理的几种开发时安全验证的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 瀑布流第二种方式————基于ajax方式
- 下一篇: 使用Bootstrap插件datapic