在本號的一系列Spring Security文章中,前後介紹了各類登陸驗證及受權中的知識點,如:spring-security簡介並與shiro對比、 formLogin模式登陸認證、動態數據登陸驗證與權限分配、帳戶屢次登陸失敗鎖定、RememberMe記住我功能,等等文章。筆者以爲以上的這些實際上都很簡單,咱們沒有涉及到分佈式應用。本節將以分佈式的應用背景,講解驗證碼實現的多種方式。本小節先從理論的角度爲你們講解,具體實現筆者還會再寫。前端
- session存儲驗證碼,不適用於分佈式應用
- 共享session存儲驗證碼,適用於分佈式應用
- 基於對稱算法的驗證碼,適用於分佈式應用
1、驗證碼的組成部分
驗證碼實際上和謎語有點像,分爲謎面和謎底。謎面一般是圖片,謎底一般爲文字。謎面用於展示,謎底用於校驗。nginx
- 對於字符型驗證碼。好比:謎面是顯示字符串"ABGH"的圖片,謎底是字符串"ABGH"
- 對於計算類驗證碼。好比:謎面是「1+1=」的圖片,謎底是「2」
- 對於拖拽類的驗證碼。好比:謎面是一個拖拽式的拼圖,謎底是拼圖位置的座標
總之,無論什麼形式的謎面,最後用戶的輸入內容要和謎底進行驗證。redis
2、session存儲驗證碼
圖中藍色爲服務端、澄粉色爲客戶端。算法
這是一種最典型的驗證碼實現方式,實現方式也比較簡單。spring
- 應用服務端隨機的生成驗證碼文字
- 將驗證碼文字存到session裏面
- 根據驗證碼文字生成驗證碼圖片,響應給客戶端
- 檢查用戶輸入的內容與驗證碼謎底是否一致
這種實現方式的優勢就是比較簡單,缺點就是:由於一套應用部署一個session,當咱們把應用部署多套如:A、B、C,他們各自有一個session而且不共享。致使的結果就是驗證碼和圖片由A生成,可是驗證請求發送到了B,這樣就不可能驗證經過。數據庫
3、共享session存儲驗證碼
在第二小節講到的問題,實際上不是驗證碼的問題,而是如何保證session惟一性或共享性的問題。主要的解決方案有兩種:segmentfault
- 一般咱們實現負載均衡應用的前端都是使用nginx或者haproxy,兩者均可以配置負載均衡策略。其中一種策略就是:你的客戶端ip上一次請求的是A應用,你的下一次請求還轉發給A應用。這樣就保證了session的惟一性。可是這種方式有可能會致使A、B、C應用其中一個或兩個分配了大量的請求,而另一個處理不多的請求,致使負載並不均衡。
- 另一種很是通用的方式就是將分佈式應用的session統一管理,也就是說原來A、B、C各自的session都存在本身的內存中,如今更改成統一存儲到一個地方,你們一塊兒用。這樣就實現了session的惟一和共享,是實現分佈式應用session管理的有效途徑。在Spring框架內,最成熟的解決方案就是spring session + redis 。可自行參考實現。
4、基於對稱算法的驗證碼
可能出於主機資源的考慮,可能出於系統架構的考量,有些應用是無狀態的。安全
- 什麼是無狀態應用:就是不保存用戶狀態的應用。
- 什麼是用戶狀態:好比當你登錄以後,在session中保存的用戶的名稱、組織等等信息。
- 因此能夠簡單的理解,無狀態應用就是無session應用。固然這並不徹底準確。
那麼對於這些無狀態的應用,咱們就沒法使用session,或者換個說法從團隊開發規範上就不讓使用session。那麼咱們的驗證碼該怎麼作?springboot
- 一樣,首先要生成隨機的驗證碼(謎底),可是不作任何存儲操做
- 將謎底(驗證碼文字)加上時間串、應用信息等組成一個字符串進行加密。必須是對稱加密,也就是說能夠解密的加密算法。
- 生成驗證碼圖片,並與加密後的密文,經過cookies一併返回給客戶端。
- 當用戶輸入驗證碼提交登陸以後,服務端解密cookies中的密文(主要是驗證碼文字),與用戶的輸入進行驗證比對。
這種作法的缺陷是顯而易見的:實際上就是將驗證碼文字在客戶端服務端之間走了一遍。雖然是加密後的驗證碼文字,可是有加密就必須有解密,不然沒法驗證。因此更爲穩妥的作法是爲每個用戶生成密鑰,並將密鑰保存到數據庫裏面,在對應的階段內調用密鑰進行加密或者解密。cookie
從密碼學的角度講,沒有一種對稱的加密算法是絕對安全的。因此更重要的是保護好你的密鑰。正如沒有一把鎖頭是絕對安全的,更重要的是保護好你的鑰匙。
期待您的關注