web認證機制
引言
以前對認證這方面的認識一直不太深刻,不清楚為什麼需要token這種認證,為什麼不簡單使用session儲存使用者登入資訊等。最近讀了幾篇大牛的部落格才對認證機制方面有了進一步瞭解。
- Basic Auth
這種認證直接順應HTTP協議的無狀態性,每次執行業務的時候,都暴力地附帶username與password引數,並將其傳送給伺服器進行驗證。儘管在伺服器端可以優雅地使用AOP技術(如攔截器或動態代理)對所有controller進行前置的登入驗證。但如果每次驗證都要查資料庫的話,建立連線與查詢操作勢必會增大開銷。如果伺服器端不做任何記憶(有狀態性)處理的話,那麼這種方式就已經沒有其他辦法可以優化了。
- Cookie/Session Auth
上面已經點到,只要伺服器端稍加一些記憶處理(記錄哪些使用者登入過)即可大大優化這個過程:只需要在使用者第一次登入系統的時候,將對應的username放入一個類似與Set<String>的資料結構中。只要登入一次(保證不退出),那麼當用戶第二次訪問controller的時候,只需要查詢Set<String>中是否有該username即可。但這種方式仍有不足,即每次還是必須要求客戶端傳username過來,否則伺服器端不知道是誰就無法判斷了。
要優雅地解決上述問題,就要得益於後來HTTP協議中出現的Cookie與Session技術了。當瀏覽器利用HTTP協議訪問伺服器的時候,伺服器會為其自動建立一個其獨有的session物件。session在基本的資料結構上類似於鍵值對Map,但不同的是它還提供了若干操作方法,且可以設定時效。既然一個瀏覽器唯一對應了一個session,那就好辦了,使用者第一次登入驗證成功後,就可把使用者名稱寫入session中表徵當前處於該瀏覽器上的使用者已經登入,以後訪問controller只用查session中是否有username鍵即可,若有放行,若沒有則阻止。如下圖所示:
- Token Auth
目前被眾多公司廣泛採用的是token認證,它的認證過程都是圍繞著一個名為token字串展開的,其認證流程如下:
第一次登入
使用者攜帶username與password請求第一次登入(為保證安全性通常採用HTTPS協議傳輸);
伺服器接收到後,查詢資料庫看使用者是否存在且密碼(MD5加密後)是否匹配,若匹配,則在使用者表中查詢該使用者資訊(角色、許可權、狀態等);
從配置檔案中讀取簽名的私鑰,並整合上一步得到的使用者資訊生成token(可採用第三方庫JWT lib);
將token寫入cookie並重定向到前端。
登入後訪問業務
使用者攜帶從cookie(若為移動終端,可以是資料庫或檔案系統)取出的token訪問需登入及特定許可權的業務;
請求首先被認證攔截器攔截,並獲取到傳來的token值;
根據配置檔案中的簽名私鑰,結合JWT lib進行解密與解碼;
驗證簽名是否正確(若不正確JWT會丟擲異常)、token是否過期與接收方是否是自己(由自己判斷)等。若通過則證明使用者已登入,進入許可權驗證階段;
通過許可權驗證框架(shiro、spring security等)驗證使用者是否具有訪問該業務的許可權,若有則返回相應資料。
認證方式比較
1.cookie支援問題
session和cookie其實是緊密相聯的。瀏覽器與伺服器首次建立連線的時候,伺服器會自動生成一個會話號sid,並寫入響應報文的首部欄位<Set-cookie>中,返回給客戶端讓其存入cookie。之後每次的HTTP請求報文中均會在首部寫入cookie中的sid,伺服器接收到後根據sid取出對應session,再進一步根據username鍵是否存在判斷登入與否。
可以看出cookie/session認證要求客戶端必須支援cookie技術,但很顯然,客戶端並不是只能為瀏覽器,還可以是PC桌面、移動終端等其它平臺,對於這些平臺,我們無法保證他們都能支援cookie技術。而token認證只認token這個字串值,至於前端是瀏覽器採用cookie存的token還是Android終端用資料庫存的token都無所謂,只要拿到token值即可進行驗證。
2.session共享問題
session是無法在多臺伺服器之間共享的,特別在分散式部署環境(即多臺部署了同一系統的Web伺服器叢集)下將帶來很多同步、一致性問題。比如下面這個場景:
使用者請求登入,HTTP請求被轉發到了伺服器A,在A上完成認證後將登入狀態記錄到了session;
使用者後續請求其他需登入的業務,HTTP請求被轉發到了非A的伺服器上,這時由於這些伺服器上的session並非A上的session,所以其上就沒有登入狀態記錄,所以業務操作將被拒絕!
很顯然這時採用cookie/session認證就很棘手了,需要自己去維護同步、狀態一致等問題。而token根本不會依賴session,所有伺服器都是一致地採用私鑰+解密演算法分析簽名的正確性。
3 時間/空間開銷問題
如果session不僅是要儲存使用者名稱,還要儲存時效時間、登入時間等各種狀態資訊(特別是大型系統),那麼一旦登入的使用者數激增,伺服器的記憶體消耗也將急劇增大。而token認證是完全將狀態存入了token值中,再利用加解密演算法將狀態取出,用時間複雜度換取了空間複雜度,記憶體開銷大大減小,時間效率有降低。
4 第三方授權問題
採用傳統認證方式,若要訪問業務,一定要先登入。假如這時一個第三方應用希望獲取該使用者在本系統的一些資源(如頭像、暱稱、簽名等),則一定要先接受登入攔截器認證才可放行,這時如果第三方應用也通過使用者名稱+密碼登入的形式來獲取資訊的話,勢必會暴露使用者在本系統的資訊,很不安全!
而一種更巧妙的做法是,先記錄第三方應用的AppID與其url地址,然後跳轉到本系統登入頁面進行登入,認證成功後,經本系統的認證伺服器生成access_token,攜帶該引數並重定向到url地址所在頁面。此後第三方應用即可憑藉該access_token的許可權範圍,訪問所需的本系統的資源。
可以看出,無論是本系統自己憑藉token訪問自己的資源,還是第三方應用憑藉access_token訪問本系統資源,依靠的都是token這個憑據,走的都是統一的一套流程,而傳統方式,需要額外寫一套,可維護性很不好。關於第三方認證文章可以參考理解Ouath2.0。
結語
雖然token認證優勢非常明顯,但仍然需要考慮如下問題:如何抵禦跨站指令碼攻擊(XSS)、如何防範重放攻擊(Replay Attacks)、如何防範MITM (Man-In-The-Middle)攻擊等。對此本文就不再做詳細敘述了。