一種基於memcache或redis緩存架構的驗證碼

 

 

本文出處:http://blog.csdn.net/chaijunkun/article/details/8996794,轉載請註明。由於本人不定期會整理相關博文,會對相應內容作出完善。因此強烈建議在原始出處查看此文。

重要提示:本方案及實施方法已經申請專利,專利公示地址:http://epub.sipo.gov.cn/tdcdesc.action?strWhere=CN106878024A

 

項目背景

 

項目中要求加入一個驗證碼功能,相信大家都不陌生,偷懶的方式基本上完全使用開源的框架,例如java中使用的jcaptcha和kaptcha。閱讀了源代碼之後發現驗證碼的存儲都放在了session中。如果你要做的項目是一個小型站點(可能只有一臺或者很少的幾臺服務器)那很容易做到,例如session同步或者將請求按照某種哈希始終重寫到特定機器上。但是一個大型網站,具有幾十甚至上百臺服務器,讓它們之間同步session,開銷是巨大的,甚至是不可行的。於是我就開始研究新的實現方法。

 

 

思路

 

 

首先我想到了緩存。互聯網項目開發目前比較流行的緩存服務分別是memcache和redis。兩種都是非關係型緩存,即按照<key, value>方式存取,操作速度是傳統關係型數據庫查詢的幾十甚至上百倍。性能上面沒問題,二者的區別這裏不談,我利用的是它們的共性——緩存時效性。我們知道,驗證碼也是有時效的。那麼現在要解決的兩個問題:

 

1.如何生成和判別驗證碼
2.生成了驗證碼字符後如何轉換爲相應的圖片。

 

 

解決

 

1.首先有一個接口,調用之後返回一個加密的字符串,這個字符串的明文是一個精心設計的數據結構,包含了“驗證碼的內容”和“請求該密文的時間”,我們把這個加密的字符串稱之爲token。在返回這串密文的同時,該接口將動態生成的數據結構取哈希作爲key,而數據結構本身作爲value放入到緩存當中,有效時間可以設置爲需要的值,假設30秒;

 

 

2.得到token之後,我們利用這個token去請求另外一個接口來獲取相應的圖片。這個接口能夠對token進行解密,從而從中獲取到驗證碼內容和原始的請求時間。如果發現token中原始的請求時間距離現在超過了預設值(上面提到的30秒),則不予返回任何東西;如果距離現在仍在有效時間內,則從緩存的圖片黑名單中去尋找該token是否請求過驗證碼圖片。例如可以採用key格式爲:img_black_hash(token),如果命中了,也什麼都不返回,如果沒命中,那就按照token中的驗證碼字符生成相應的圖片,並將該token放入到圖片黑名單中,注意放入黑名單中的緩存有效期一定要比驗證碼有效期明顯長一些(例如60秒)。下次客戶端再拿token來請求驗證碼圖片時先從圖片黑名單裏查找,命中就不返回,即便沒命中,也由於token中的時間已經過了有效期而什麼也得不到。

 

 

3.客戶端拿到了token,用戶也通過被擾亂塗鴉圖片輸入了相應的驗證碼,客戶端就把明文和token一起提交到服務器。

 

先解密,解密失敗了當然就是數據被篡改了,鑑定失敗;
解密成功,同樣去緩存中查詢該token是否被驗證過,如果命中了,說明被驗證過,鑑定失敗;這裏使用的黑名單與步驟2中的黑名單不是同一個,可以採用key格式爲verified_hash(token),緩存有效期要求和步驟2一樣,也要比驗證碼有效期明顯長一些;
如果沒命中,抽取解密後的數據,匹配當前時間和原始請求token的時間,時間尚在有效期內,則嘗試匹配用戶輸入的驗證碼和之前請求的驗證碼內容。匹配成功則鑑定成功,匹配失敗則鑑定失敗。無論匹配是否成功,都將該token放入黑名單,防止再次驗證。

 

 

分析

 

通過以上步驟,可以很好地實現驗證碼脫離session。而因爲引入了黑名單和超時機制,可以很好地抵禦重放攻擊。

 

memcache不支持集羣怎麼辦?這個很好解決。舉個例子:你有8臺緩存服務器分別cache[0]~cache[7],通過hash(token)來得到一個哈希值,然後模8取餘,結果肯定是介於0~7之間的整數,那接下來就去操作相應的cache[n]就可以了。

 

memcache和redis的PK。說到優劣,很多人其實已經想到了。memcache作爲一個純內存緩存,當掉電或者程序崩潰時,所有緩存的數據都將消失。redis可以將緩存的數據寫入到文件,即便是掉電或者程序崩潰,在恢復之後仍然可以繼續提供服務。(儘管驗證碼服務對時間很敏感,有效期就那麼短短的30秒,但如果你操作速度足夠夠快,也許能不至於數據全丟)

 

存在的問題。確實,這個架構對於時間是很敏感的。實現token生成、圖片生成、驗證碼鑑定這三個接口可以不位於同一臺機器,可以拆分也可負載均衡,但對時間的要求稍高,幾臺機器的系統時間相差不宜太大。最好定時同步標準時間,或者在內網搭建一臺時間服務器來給它們授時。不管怎樣,這要比同步session的開銷要小很多了。

 

token數據結構的設計。我做的設計很簡單,驗證碼寬度是4位,字符空間是阿拉伯數字和英文大小寫。標記token生成的時間採用的是距離1970年1月1日0時的毫秒數。爲了高併發下的不重複,後面還可以加入幾位隨機數。最終格式可以是:[captcha]_[timestamp]_[random]。例如:5Ais_1369930314548_2137。不同字段以下劃線分隔。

 

關於加密算法。這裏我使用了XXTEA算法+Base64編碼。XXTEA算法非常簡潔高效,運算速度快,是一種實用的對稱加密算法。爲了保證加密後的結果能以字符串的形式傳輸,在加密之後進行了Base64編碼。在解密時將密文先經過Base64去編碼,然後再解密。

 

圖片如何生成。我們一直在討論如何生成token和鑑定token,圖片的生成是一個難點,通過比較,我將kaptcha的一部分功能單獨拿出來使用,將字符轉圖片的代碼提取出來了,再加上自己寫了點字符旋轉代碼:)

 

寫在最後

 

都說無圖無真相,對於程序員來說是無碼無真相。接下來我就貢獻出我寫的代碼,供大家參考。我實現的版本是memcache版本,用redis的童鞋替換底層緩存服務即可。歡迎多提寶貴意見,共同學習。

下載地址:http://download.csdn.net/detail/chaijunkun/5485725

2016年8月2日更新:新增加Redis版的實現代碼。下載地址:http://download.csdn.net/detail/chaijunkun/9592103

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章