鬆哥最近在研究 Spring Security 源碼,發現了不少好玩的代碼,抽空寫幾篇文章和小夥伴們分享一下。java
不少人吐槽 Spring Security 比 Shiro 重量級,這個重量級不是憑空來的,重量有重量的好處,就是它提供了更爲強大的防禦功能。算法
好比鬆哥最近看到的一段代碼:數據庫
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; } catch (UsernameNotFoundException ex) { mitigateAgainstTimingAttack(authentication); throw ex; } catch (InternalAuthenticationServiceException ex) { throw ex; } catch (Exception ex) { throw new InternalAuthenticationServiceException(ex.getMessage(), ex); } }
這段代碼位於 DaoAuthenticationProvider 類中,爲了方便你們理解,我來簡單說下這段代碼的上下文環境。後端
當用戶提交用戶名密碼登陸以後,Spring Security 須要根據用戶提交的用戶名去數據庫中查詢用戶,這塊若是你們不熟悉,能夠參考鬆哥以前的文章:設計模式
查到用戶對象以後,再去比對從數據庫中查到的用戶密碼和用戶提交的密碼之間的差別。具體的比對工做,能夠參考Spring Boot 中密碼加密的兩種姿式!一文。跨域
而上面這段代碼就是 Spring Security 根據用戶登陸時傳入的用戶名去數據庫中查詢用戶,並將查到的用戶返回。方法中還有一個 authentication 參數,這個參數裏邊保存了用戶登陸時傳入的用戶名/密碼信息。緩存
那麼這段代碼有什麼神奇之處呢?安全
咱們來一行一行分析。session
源碼梳理
1
首先方法一進來調用了 prepareTimingAttackProtection 方法,從方法名字上能夠看出,這個是爲計時攻擊的防護作準備,那麼什麼又是計時攻擊呢?別急,鬆哥一會來解釋。咱們先來吧流程走完。prepareTimingAttackProtection 方法的執行很簡單,以下:框架
private void prepareTimingAttackProtection() { if (this.userNotFoundEncodedPassword == null) { this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD); } }
該方法就是將常量 USER_NOT_FOUND_PASSWORD 使用 passwordEncoder 編碼以後(若是不瞭解 passwordEncoder,能夠參考 Spring Boot 中密碼加密的兩種姿式!一文),將編碼結果賦值給 userNotFoundEncodedPassword 變量。
2
接下來調用 loadUserByUsername 方法,根據登陸用戶傳入的用戶名去數據庫中查詢用戶,若是查到了,就將查到的對象返回。
3
若是查詢過程當中拋出 UsernameNotFoundException 異常,按理說直接拋出異常,接下來的密碼比對也不用作了,由於根據用戶名都沒查到用戶,此次登陸確定是失敗的,沒有必要進行密碼比對操做!
可是你們注意,在拋出異常以前調用了 mitigateAgainstTimingAttack 方法。這個方法從名字上來看,有緩解計時攻擊的意思。
咱們來看下該方法的執行流程:
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) { if (authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword); } }
能夠看到,這裏首先獲取到登陸用戶傳入的密碼即 presentedPassword,而後調用 passwordEncoder.matches 方法進行密碼比對操做,原本該方法的第二個參數是數據庫查詢出來的用戶密碼,如今數據庫中沒有查到用戶,因此第二個參數用 userNotFoundEncodedPassword 代替了,userNotFoundEncodedPassword 就是咱們一開始調用 prepareTimingAttackProtection 方法時賦值的變量。這個密碼比對,從一開始就註定了確定會失敗,那爲何還要比對呢?
計時攻擊
這就引入了咱們今天的主題--計時攻擊。
計時攻擊是旁路攻擊的一種,在密碼學中,旁道攻擊又稱側信道攻擊、邊信道攻擊(Side-channel attack)。
這種攻擊方式並不是利用加密算法的理論弱點,也不是暴力破解,而是從密碼系統的物理實現中獲取的信息。例如:時間信息、功率消耗、電磁泄露等額外的信息源,這些信息可被用於對系統的進一步破解。
旁路攻擊有多種不一樣的分類:
- 緩存攻擊(Cache Side-Channel Attacks),經過獲取對緩存的訪問權而獲取緩存內的一些敏感信息,例如攻擊者獲取雲端主機物理主機的訪問權而獲取存儲器的訪問權。
- 計時攻擊(Timing attack),經過設備運算的用時來推斷出所使用的運算操做,或者經過對比運算的時間推定數據位於哪一個存儲設備,或者利用通訊的時間差進行數據竊取。
- 基於功耗監控的旁路攻擊,同一設備不一樣的硬件電路單元的運做功耗也是不同的,所以一個程序運行時的功耗會隨着程序使用哪種硬件電路單元而變更,據此推斷出數據輸出位於哪個硬件單元,進而竊取數據。
- 電磁攻擊(Electromagnetic attack),設備運算時會泄漏電磁輻射,通過得當分析的話可解析出這些泄漏的電磁輻射中包含的信息(好比文本、聲音、圖像等),這種攻擊方式除了用於密碼學攻擊之外也被用於非密碼學攻擊等竊聽行爲,如TEMPEST 攻擊。
- 聲學密碼分析(Acoustic cryptanalysis),經過捕捉設備在運算時泄漏的聲學信號捉取信息(與功率分析相似)。
- 差異錯誤分析,隱密數據在程序運行發生錯誤並輸出錯誤信息時被發現。
- 數據殘留(Data remanence),可以使理應被刪除的敏感數據被讀取出來(例如冷啓動攻擊)。
- 軟件初始化錯誤攻擊,現時較爲少見,行錘攻擊(Row hammer)是該類攻擊方式的一個實例,在這種攻擊實現中,被禁止訪問的存儲器位置旁邊的存儲器空間若是被頻繁訪問將會有狀態保留丟失的風險。
- 光學方式,即隱密數據被一些視覺光學儀器(如高清晰度相機、高清晰度攝影機等設備)捕捉。
全部的攻擊類型都利用了加密/解密系統在進行加密/解密操做時算法邏輯沒有被發現缺陷,可是經過物理效應提供了有用的額外信息(這也是稱爲「旁路」的原因),而這些物理信息每每包含了密鑰、密碼、密文等隱密數據。
而上面 Spring Security 中的那段代碼就是爲了防止計時攻擊。
具體是怎麼作的呢?假設 Spring Security 從數據庫中沒有查到用戶信息就直接拋出異常了,沒有去執行 mitigateAgainstTimingAttack 方法,那麼黑客通過大量的測試,再通過統計分析,就會發現有一些登陸驗證耗時明顯少於其餘登陸,進而推斷出登陸驗證時間較短的都是不存在的用戶,而登陸耗時較長的是數據庫中存在的用戶。
如今 Spring Security 中,經過執行 mitigateAgainstTimingAttack 方法,不管用戶存在或者不存在,登陸校驗的耗時不會有明顯差異,這樣就避免了計時攻擊。
可能有小夥伴會說,passwordEncoder.matches 方法執行能耗費多少時間呀?這要看你怎麼計時了,時間單位越小,差別就越明顯:毫秒(ms)、微秒(µs)、奈秒(ns)、皮秒(ps)、飛秒(fs)、阿秒(as)、仄秒(zs)。
另外,Spring Security 爲了安全,passwordEncoder 中引入了一個概念叫作自適應單向函數,這種函數故意執行的很慢而且消耗大量系統資源,因此很是有必要進行計時攻擊防護。
關於自適應單向函數,這是另一個故事了,鬆哥抽空再和小夥伴們聊~
好啦,今天就先和小夥伴們聊這麼多,小夥伴們決定有收穫的話,記得點個在看鼓勵下鬆哥哦~
不知不覺,Spring Security 系列已經連載了 49 篇啦,感興趣的小夥伴也能夠看看本系列其餘文章哦:
- 挖一個大坑,Spring Security 開搞!
- 鬆哥手把手帶你入門 Spring Security,別再問密碼怎麼解密了
- 手把手教你定製 Spring Security 中的表單登陸
- Spring Security 作先後端分離,咱就別作頁面跳轉了!通通 JSON 交互
- Spring Security 中的受權操做原來這麼簡單
- Spring Security 如何將用戶數據存入數據庫?
- Spring Security+Spring Data Jpa 強強聯手,安全管理只有更簡單!
- Spring Boot + Spring Security 實現自動登陸功能
- Spring Boot 自動登陸,安全風險要怎麼控制?
- 在微服務項目中,Spring Security 比 Shiro 強在哪?
- SpringSecurity 自定義認證邏輯的兩種方式(高級玩法)
- Spring Security 中如何快速查看登陸用戶 IP 地址等信息?
- Spring Security 自動踢掉前一個登陸用戶,一個配置搞定!
- Spring Boot + Vue 先後端分離項目,如何踢掉已登陸用戶?
- Spring Security 自帶防火牆!你都不知道本身的系統有多安全!
- 什麼是會話固定攻擊?Spring Boot 中要如何防護會話固定攻擊?
- 集羣化部署,Spring Security 要如何處理 session 共享?
- 鬆哥手把手教你在 SpringBoot 中防護 CSRF 攻擊!so easy!
- 要學就學透徹!Spring Security 中 CSRF 防護源碼解析
- Spring Boot 中密碼加密的兩種姿式!
- Spring Security 要怎麼學?爲何必定要成體系的學習?
- Spring Security 兩種資源放行策略,千萬別用錯了!
- 鬆哥手把手教你入門 Spring Boot + CAS 單點登陸
- Spring Boot 實現單點登陸的第三種方案!
- Spring Boot+CAS 單點登陸,如何對接數據庫?
- Spring Boot+CAS 默認登陸頁面太醜了,怎麼辦?
- 用 Swagger 測試接口,怎麼在請求頭中攜帶 Token?
- Spring Boot 中三種跨域場景總結
- Spring Boot 中如何實現 HTTP 認證?
- Spring Security 中的四種權限控制方式
- Spring Security 多種加密方案共存,老破舊系統整合利器!
- 神奇!本身 new 出來的對象同樣也能夠被 Spring 容器管理!
- Spring Security 配置中的 and 到底該怎麼理解?
- 一文搞定 Spring Security 異常處理機制!
- 寫了這麼多年代碼,這樣的登陸方式仍是頭一回見!
- Spring Security 居然能夠同時存在多個過濾器鏈?
- Spring Security 能夠同時對接多個用戶表?
- 在 Spring Security 中,我就想從子線程獲取用戶登陸信息,怎麼辦?
- 深刻理解 FilterChainProxy【源碼篇】
- 深刻理解 SecurityConfigurer 【源碼篇】
- 深刻理解 HttpSecurity【源碼篇】
- 深刻理解 AuthenticationManagerBuilder 【源碼篇】
- 花式玩 Spring Security ,這樣的用戶定義方式你可能沒見過!
- 深刻理解 WebSecurityConfigurerAdapter【源碼篇】
- 盤點 Spring Security 框架中的八大經典設計模式
- Spring Security 初始化流程梳理
- 爲何你使用的 Spring Security OAuth 過時了?鬆哥來和你們捋一捋!
- 一個詭異的登陸問題