1. 程式人生 > >圖文詳解應用登入驗證碼的多種實現方案

圖文詳解應用登入驗證碼的多種實現方案

在本號的一系列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中的密文(主要是驗證碼文字),與使用者的輸入進行驗證比對。

這種做法的缺陷是顯而易見的:實際上就是將驗證碼文字在客戶端服務端之間走了一遍。雖然是加密後的驗證碼文字,但是有加密就必須有解密,否則無法驗證。所以更為穩妥的做法是為每一個使用者生成金鑰,並將金鑰儲存到資料庫裡面,在對應的階段內呼叫金鑰進行加密或者解密。

從密碼學的角度講,沒有一種對稱的加密演算法是絕對安全的。所以更重要的是保護好你的金鑰。正如沒有一把鎖頭是絕對安全的,更重要的是保護好你的鑰匙。

期待您的關注