注:本節涉及到前後端,這個系列的對比學習,還是專注在前端Vue和Blazor技術,所以就不擼碼了,下面主要學習概念。
我們知道,Http是無狀態協定,使用者端請求伺服器端,認證一次後,如果再次請求,又要重新認證,因為對伺服器端來說,使用者端的每次請求都是無差別的!另外,伺服器端的授權體系,一般使用基於RBAC角色許可權模型。角色資訊,我們可以在使用者端每次請求都,都查詢一次,但這樣比較消耗資源。最好的方式,是使用者端第一次請求時,就將角色傳給使用者端,之後使用者端每次請求,直接攜帶角色資訊。而這些問題,都需要使用Cookie、Session或者jwt來解決。
1、先說Cookie。下圖為Cookie在使用者端與伺服器端的應用邏輯圖。
如上圖所示:
- 使用者端首次請求伺服器時,攜帶引數(如使用者名稱和密碼),伺服器根據引數判斷是否合法。如合法,則在Response中頒發Cookies,如在.NET中,寫入Cookie,【Response.Cookies[「name」].value = 「functionMC」;Response.Cookies[「name」].Expires=DateTime.Now.AddDays(1);】,其中Expires屬性,為伺服器端設定的過期時間。
- 使用者端收到伺服器的Response後,將Cookie以健值對的方式儲存到瀏覽器中,如使用js直接操作DOM,【document.Cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT";】,使用者端也可以設定過期時間。如不設定,儲存在記憶體中,瀏覽器關閉時自動清除;如設定,則儲存在本地硬碟中,到期後自動清除。
- 之後,使用者端每次存取伺服器,都會在請求頭中攜帶相應站點的Cookie。伺服器端就可以讀取Cookie,如在.NET中,讀取Cookie,【string name = Request.Cookies[「name」].value】
- Cookie在安全性方面,易出現Cookie劫持和欺騙,大小和數量方面也受限制。所以在Cookie之後,出現了Session。
2、Session。下圖為Session在使用者端與伺服器端的應用邏輯圖
如上圖所示。
- 使用者端首次存取伺服器後,判斷是否合法,如果合法,則在伺服器的快取中建立一個鍵值對,鍵為SessionID,同時將SessionID作為Cookie返回給使用者端。使用者端再次請求時,請求頭攜帶SessionID,伺服器端根據SessionID,查詢快取資訊,根據快取資訊進行處理。
- 使用Session後,使用者端與伺服器端之間只傳遞SessionID,更多資訊在伺服器端快取中,這樣可以儲存更多資訊。Session可以設定有效期,也可以不設定。如不設定,則只在當前對談中有效,使用者端關閉後就失效,這樣也更安全。
- Session本身的含義,指使用者端與伺服器端之間的對談,背後實質指的是一種伺服器快取,如果請求不是很多,效率還是很高的
- Session可以有效利用伺服器端資源,不受使用者端限制,安全性更好。但是,當用戶端的並行請求比較多時,會很佔伺服器記憶體。如果是分散式,不同的Session儲存在不同的伺服器之間,而使用者端每次請求的路徑是隨配的,要解決分散式,我們就需要在每臺伺服器之間同步SessionID和快取資訊。最後,SessionID還是依賴於Cookie,Cookie的跨域、單點登陸難等問題,它也一樣有。所以,token的方案開始出現,它是目前最主流的方案,而其中最重要的標準,就是jwt。
3、jwt。下圖為token/jwt在使用者端和伺服器端的應用邏輯圖
如上圖所示,jwt重新將資訊儲存在了使用者端,節省了伺服器端資源,也沒有分散式的問題,同時在靈活性和安全性方面有了質的提升。
- 靈活性方面:不再限於Cookie,可以儲存到原生的Storage或IndexedDB,甚至於遠端資料庫裡,更加的自由。同時,它以JSON加密形式儲存在使用者端的,是跨語言的,原則上任何web形式都支援。
- 安全性方面:可以說有了質的提升,這才是jwt的核心。首先它可以完全不依賴於Cookie,它沒有跨域、單點登陸等問題;其次,它將加密和解密的過程,放在了伺服器上,即使資訊被截獲,也無法篡改。
- 更好的支援分散式:在Session方案中,我們需要在每個伺服器都進行快取同步。而jwt中,我們只要在每個伺服器都拷備一份金鑰即可,或者可以將金鑰統一儲存到一個Redis伺服器中,每個伺服器統一向Redis請求金鑰,這樣連拷貝金鑰的工作也可省去
下面簡單說明一下jwt的構成、生成、加密和解密過程:
- jwt由Header、Playload和Signature三部分組成,以符號「.」連線成字串。其中Header是一個json物件的Base64編碼,主要有簽名的演演算法資訊;Playload也是一個json物件的Base64編碼,這是傳輸的主體內容,比如使用者角色、所在部門等資訊都可以存放在裡面。我們知道Base64不具有加密能力,可以說Header和Playload是明文傳輸的,在Playload裡面,千萬不能放敏感資訊。而Signature才是jwt安全體系的核心
- jwt是在伺服器端生成的,我們定義好Header和Playload後,在伺服器端我們有一個金鑰Secret,這個金鑰是一個沒有規律的字串。我們使用Header裡的雜湊演演算法,加上金鑰、Header和Playload,就可以算出簽名Signature。Signature是不可匿的,無法破解,破解也沒意義。
- 使用者端再次請求時,伺服器端獲得請求頭攜帶的jwt後,取出Header、Playload、Signature(A)。對Header進行Base64解碼,獲取其中的雜湊演演算法,然後重新使用這個雜湊演演算法,加上金鑰、Header和Playload重新算出一個簽名Signature(B),然後比對Signature(A)和Signature(B)是否一致,如果一致,則請求是合法的,伺服器端放行,並使用Playload中的資訊進行計算處理。
- 伺服器端的金鑰Secret,是計算簽名的安全核心,一定要儲存好,最好定期更新。