圖文詳解應用登入驗證碼的多種實現方案
在本號的一系列Spring Security文章中,先後介紹了各種登入驗證及授權中的知識點,如:spring-security簡介並與shiro對比、 formLogin模式登入認證、動態資料登入驗證與許可權分配、賬戶多次登入失敗鎖定、RememberMe記住我功能,等等文章。筆者覺得以上的這些實際上都很簡單,我們沒有涉及到分散式應用。本節將以分散式的應用背景,講解驗證碼實現的多種方式。本小節先從理論的角度為大家講解,具體實現筆者還會再寫。
- session儲存驗證碼,不適用於分散式應用
- 共享session儲存驗證碼,適用於分散式應用
- 基於對稱演算法的驗證碼,適用於分散式應用
一、驗證碼的組成部分
驗證碼實際上和謎語有點像,分為謎面和謎底。謎面通常是圖片,謎底通常為文字。謎面用於展現,謎底用於校驗。
- 對於字元型驗證碼。比如:謎面是顯示字串"ABGH"的圖片,謎底是字串"ABGH"
- 對於計算類驗證碼。比如:謎面是“1+1=”的圖片,謎底是“2”
- 對於拖拽類的驗證碼。比如:謎面是一個拖拽式的拼圖,謎底是拼圖位置的座標
總之,不管什麼形式的謎面,最後使用者的輸入內容要和謎底進行驗證。
二、session儲存驗證碼
圖中藍色為服務端、澄粉色為客戶端。
這是一種最典型的驗證碼實現方式,實現方式也比較簡單。
- 應用服務端隨機的生成驗證碼文字
- 將驗證碼文字存到session裡面
- 根據驗證碼文字生成驗證碼圖片,響應給客戶端
- 檢查使用者輸入的內容與驗證碼謎底是否一致
這種實現方式的優點就是比較簡單,缺點就是:因為一套應用部署一個session,當我們把應用部署多套如:A、B、C,他們各自有一個session並且不共享。導致的結果就是驗證碼和圖片由A生成,但是驗證請求傳送到了B,這樣就不可能驗證通過。
三、共享session儲存驗證碼
在第二小節講到的問題,實際上不是驗證碼的問題,而是如何保證session唯一性或共享性的問題。主要的解決方案有兩種:
- 通常我們實現負載均衡應用的前端都是使用nginx或者haproxy,二者都可以配置負載均衡策略。其中一種策略就是:你的客戶端ip上一次請求的是A應用,你的下一次請求還轉發給A應用。這樣就保證了session的唯一性。但是這種方式有可能會導致A、B、C應用其中一個或兩個分配了大量的請求,而另外一個處理很少的請求,導致負載並不均衡。
- 另外一種非常通用的方式就是將分散式應用的session統一管理,也就是說原來A、B、C各自的session都存在自己的記憶體中,現在更改為統一儲存到一個地方,大家一起用。這樣就實現了session的唯一和共享,是實現分散式應用session管理的有效途徑。在Spring框架內,最成熟的解決方案就是spring session + redis 。可自行參考實現。
四、基於對稱演算法的驗證碼
可能出於主機資源的考慮,可能出於系統架構的考量,有些應用是無狀態的。
- 什麼是無狀態應用:就是不儲存使用者狀態的應用。
- 什麼是使用者狀態:比如當你登陸之後,在session中儲存的使用者的名稱、組織等等資訊。
- 所以可以簡單的理解,無狀態應用就是無session應用。當然這並不完全準確。
那麼對於這些無狀態的應用,我們就無法使用session,或者換個說法從團隊開發規範上就不讓使用session。那麼我們的驗證碼該怎麼做?
- 同樣,首先要生成隨機的驗證碼(謎底),但是不做任何儲存操作
- 將謎底(驗證碼文字)加上時間串、應用資訊等組成一個字串進行加密。必須是對稱加密,也就是說可以解密的加密演算法。
- 生成驗證碼圖片,並與加密後的密文,通過cookies一併返回給客戶端。
- 當用戶輸入驗證碼提交登入之後,服務端解密cookies中的密文(主要是驗證碼文字),與使用者的輸入進行驗證比對。
這種做法的缺陷是顯而易見的:實際上就是將驗證碼文字在客戶端服務端之間走了一遍。雖然是加密後的驗證碼文字,但是有加密就必須有解密,否則無法驗證。所以更為穩妥的做法是為每一個使用者生成金鑰,並將金鑰儲存到資料庫裡面,在對應的階段內呼叫金鑰進行加密或者解密。
從密碼學的角度講,沒有一種對稱的加密演算法是絕對安全的。所以更重要的是保護好你的金鑰。正如沒有一把鎖頭是絕對安全的,更重要的是保護好你的鑰匙。
期待您的關注
- 向您推薦博主的系列文件:《手摸手教您學習SpringBoot系列-16章97節》
- 本文轉載註明出處(必須帶連線,不能只轉文字):字母哥部落格。