架構安全性:認證

**認證:**系統如何正確分辨出操作用戶的真實身份?

信息系統爲用戶提供服務之前,總是希望先弄清楚 “你是誰?”(認證)、“你能幹什麼?”(授權)以及“你如何證明?”(憑證)這三個基本問題。然而,這三個基本問題又並不像部分開發者認爲的那樣,只是一個“系統登錄” 功能,僅僅是校驗一下用戶名、密碼是否正確這麼簡單。賬戶和權限信息作爲一種必須最大限度保障安全和隱私,同時又要兼顧各個系統模塊甚至系統間共享訪問的基礎主數據,它的存儲、管理與使用都面臨一系列複雜的問題。

**架構安全性的經驗原則:**以標準規範爲指導、以標準接口去實現。安全涉及的問題很麻煩,但解決方案已相當成熟,對於 99% 的系統來說,在安全上不去做輪子,不去想發明創造,嚴格遵循標準就是最恰當的安全設計。

主流的三種認證方式:

HTTP 認證

HTTP 認證框架提出認證方案是希望能把認證 “要產生身份憑證” 的目的與 “具體如何產生憑證” 的實現分離開來,無論客戶端通過生物信息(指紋、人臉)、用戶密碼、數字證書抑或其他方式來生成憑證,都屬於是如何生成憑證的具體實現,都可以包容在 HTTP 協議預設的框架之內。

HTTP 認證框架的工作流程:

以 HTTP Basic 認證爲例來介紹認證是如何工作的。HTTP Basic 認證是一種主要以演示爲目的的認證方案,也應用於一些不要求安全性的場合,譬如家裏的路由器登錄等。Basic 認證產生用戶身份憑證的方法是讓用戶輸入用戶名和密碼,經過 Base64 編碼 “加密” 後作爲身份憑證。譬如請求資源GET /admin後,瀏覽器會收到來自服務端的如下響應:

HTTP/1.1 401 Unauthorized
Date: Mon, 24 Feb 2020 16:50:53 GMT
WWW-Authenticate: Basic realm="example from icyfenix.cn"

此時,瀏覽器必須詢問最終用戶,即彈出下圖所示的 HTTP Basic 認證對話框,要求提供用戶名和密碼。

用戶在對話框中輸入密碼信息,譬如輸入用戶名icyfenix,密碼123456,瀏覽器會將字符串icyfenix:123456編碼爲aWN5ZmVuaXg6MTIzNDU2,然後發送給服務端,HTTP 請求如下所示:

GET /admin HTTP/1.1
Authorization: Basic aWN5ZmVuaXg6MTIzNDU2

服務端接收到請求,解碼後檢查用戶名和密碼是否合法,如果合法就返回/admin的資源,否則就返回 403 Forbidden 錯誤,禁止下一步操作。注意 Base64 只是一種編碼方式,並非任何形式的加密,所以 Basic 認證的風險是顯而易見的。除 Basic 認證外,IETF 還定義了很多種可用於實際生產環境的認證方案,列舉如下。

HTTP 認證框架中的認證方案是允許自行擴展的,並不要求一定由 RFC 規範來定義,只要用戶代理(User Agent,通常是瀏覽器,泛指任何使用 HTTP 協議的程序)能夠識別這種私有的認證方案即可。因此,很多廠商也擴展了自己的認證方案。

Web 認證

WebAuthn 規範涵蓋了 “註冊” 與“認證”兩大流程,先來介紹註冊流程,它大致可以分爲以下步驟:

  1. 用戶進入系統的註冊頁面,這個頁面的格式、內容和用戶註冊時需要填寫的信息均不包含在 WebAuthn 標準的定義範圍內。

  2. 當用戶填寫完信息,點擊 “提交註冊信息” 的按鈕後,服務端先暫存用戶提交的數據,生成一個隨機字符串(規範中稱爲 Challenge)和用戶的 UserID(在規範中稱作憑證 ID),返回給客戶端。

  3. 客戶端的 WebAuthn API 接收到 Challenge 和 UserID,把這些信息發送給驗證器(Authenticator),驗證器可理解爲用戶設備上 TouchID、FaceID、實體密鑰等認證設備的統一接口。

  4. 驗證器提示用戶進行驗證,如果支持多種認證設備,還會提示用戶選擇一個想要使用的設備。驗證的結果是生成一個密鑰對(公鑰和私鑰),由驗證器存儲私鑰、用戶信息以及當前的域名。然後使用私鑰對 Challenge 進行簽名,並將簽名結果、UserID 和公鑰一起返回客戶端。

  5. 瀏覽器將驗證器返回的結果轉發給服務器。

  6. 服務器覈驗信息,檢查 UserID 與之前發送的是否一致,並用公鑰解密後得到的結果與之前發送的 Challenge 相比較,一致即表明註冊通過,由服務端存儲該 UserID 對應的公鑰。

以上步驟的時序如圖所示:

登錄流程與註冊流程類似,如果你理解了註冊流程,就很容易理解登錄流程了。登錄流程大致可以分爲以下步驟:

  1. 用戶訪問登錄頁面,填入用戶名後即可點擊登錄按鈕。

  2. 服務器返回隨機字符串 Challenge、用戶 UserID。

  3. 瀏覽器將 Challenge 和 UserID 轉發給驗證器。

  4. 驗證器提示用戶進行認證操作。由於在註冊階段驗證器已經存儲了該域名的私鑰和用戶信息,所以如果域名和用戶都相同的話,就不需要生成密鑰對了,直接以存儲的私鑰加密 Challenge,然後返回給瀏覽器。

  5. 服務端接收到瀏覽器轉發來的被私鑰加密的 Challenge,以此前註冊時存儲的公鑰進行解密,如果解密成功則宣告登錄成功。

WebAuthn 採用非對稱加密的公鑰、私鑰替代傳統的密碼,這是非常理想的認證方案,私鑰是保密的,只有驗證器需要知道它,連用戶本人都不需要知道,也就沒有人爲泄漏的可能;公鑰是公開的,可以被任何人看到或存儲。公鑰可用於驗證私鑰生成的簽名,但不能用來簽名,除了得知私鑰外,沒有其他途徑能夠生成可被公鑰驗證爲有效的簽名,這樣服務器就可以通過公鑰是否能夠解密來判斷最終用戶的身份是否合法。

WebAuthn 還一攬子地解決了傳統密碼在網絡傳輸上的風險,無論密碼是否在客戶端進行加密、如何加密,對防禦中間人攻擊來說都是沒有意義的。更值得誇讚的是 WebAuthn 爲登錄過程帶來極大的便捷性,不僅註冊和驗證的用戶體驗十分優秀,而且徹底避免了用戶在一個網站上泄漏密碼,所有使用相同密碼的網站都受到攻擊的問題,這個優點使得用戶無須再爲每個網站想不同的密碼。

當前的 WebAuthn 還很年輕,普及率暫時還很有限,但相信幾年之內它必定會發展成 Web 認證的主流方式,被大多數網站和系統所支持。

認證的實現

在今時今日,實際活躍於 Java 安全領域的是兩個私有的(私有的意思是不由 JSR 所規範的,即沒有 java/javax.* 作爲包名的)的安全框架:Apache Shiro 和 Spring Security。

相較而言,Shiro 更便捷易用,而 Spring Security 的功能則要複雜強大一些。無論是單體架構還是微服務架構的 Fenix's Bookstore,筆者都選擇了 Spring Security 作爲安全框架,這個選擇與功能、性能之類的考量沒什麼關係,就只是因爲 Spring Boot、Spring Cloud 全家桶的緣故。這裏不打算羅列代碼來介紹 Shiro 與 Spring Security 的具體使用,如感興趣可以參考 Fenix's Bookstore 的源碼倉庫。只從目標上看,兩個安全框架提供的功能都很類似,大致包括以下四類:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/WZW_589yeI_lfyJmbc2kwg