Cookie、Session、Token與JWT解析

認證、授權與憑證

什么是認證(Authentication)?

  • 通俗地講就是驗證當前用戶的身份是否合法的過程,即你是誰?證明“你是你自己”(比如:你每天上下班打卡,都需要通過指紋打卡,當你的指紋和系統里錄入的指紋相匹配時,就打卡成功)
  • 互聯網中的認證:用戶名密碼登錄;郵箱發送登錄鏈接;手機號接收驗證碼。只要你能收到郵箱/驗證碼,就默認你是賬號的主人!認證主要是為了保護系統的隱私數據與資源。
  • 拓展:什么是會話?認證通過后,為了避免用戶每次操作都進行認證(除銀行轉賬等),可以將用戶信息保存在會話中,會話就是系統為了保存當前用戶的登錄狀態所提供的機制,常見的有基于Session和token的方式,具體見下文。

什么是授權(Authorization)?

  • 簡單來講就是誰(who)對什么(what)進行了什么操作(how)。認證是保證用戶的合法性,授權則是為了更細粒度的對隱私數據的劃分。*授權是在認證通過后,控制不同的用戶訪問不同的資源。
  • 用戶授予第三方應用訪問該用戶某些資源的權限。比如,你在安裝手機應用的時候,APP 會詢問是否允許授予權限(訪問相冊、地理位置等權限);你在訪問微信小程序時,當登錄時,小程序會詢問是否允許授予權限(獲取昵稱、頭像、地區、性別等個人信息)
  • 實現授權的方式有:業界通常基于R(role/resource)BAC實現授權:(1)基于角色的訪問控制(2)基于資源(權限)的訪問控制,系統設計時定義好某項操作的權限標識,系統擴展性好。

什么是憑證(Credentials)

  • 實現認證和授權的前提是需要一種媒介(證書) 來標記訪問者的身份。
  • 例如:在戰國時期,商鞅變法,發明了照身帖。照身帖由官府發放,是一塊打磨光滑細密的竹板,上面刻有持有人的頭像和籍貫信息。國人必須持有,如若沒有就被認為是黑戶,或者間諜之類的。在現實生活中,每個人都會有一張專屬的居民身份證,是用于證明持有人身份的一種法定證件。通過身份證,我們可以辦理手機卡/銀行卡/個人貸款/交通出行等等,這就是認證的憑證。
  • 在互聯網應用中,一般網站會有兩種模式,游客模式和登錄模式。游客模式下,可以正常瀏覽網站上面的文章,一旦想要點贊/收藏/分享文章,就需要登錄或者注冊賬號。當用戶登錄成功后,服務器會給該用戶使用的瀏覽器頒發一個令牌(token),這個令牌用來表明你的身份,每次瀏覽器發送請求時會帶上這個令牌,就可以使用游客模式下無法使用的功能。

Cookie與Session

什么是Cookie?

  • HTTP 是無狀態的協議(對于事務處理沒有記憶能力,每次客戶端和服務端會話完成時,服務端不會保存任何會話信息):每個請求都是完全獨立的,服務端無法確認當前訪問者的身份信息,無法分辨上一次的請求發送者和這一次的發送者是不是同一個人。所以服務器與瀏覽器為了進行會話跟蹤(知道是誰在訪問我),就必須主動的去維護一個狀態,這個狀態用于告知服務端前后兩個請求是否來自同一瀏覽器。而這個狀態需要通過 cookie 或者 session 去實現。
  • cookie 存儲在客戶端: cookie 是服務器發送到用戶瀏覽器并保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶并發送到服務器上。
  • cookie 是不可跨域的: 每個 cookie 都會綁定單一的域名,無法在別的域名下獲取使用,一級域名和二級域名之間是允許共享使用的靠的是 domain)

cookie 重要的屬性

什么是 Session

  • session 是另一種記錄服務器和客戶端會話狀態的機制,即告訴服務端前后兩個請求是否來自同一個客戶端(瀏覽器),知道誰在訪問我。因為http本身是無狀態協議,這樣,無法確定你的本次請求和上次請求是不是你發送的。如果要進行類似論壇登陸相關的操作,就實現不了了。
  • session 是基于 cookie 實現的,session 存儲在服務器端,sessionId 會被存儲到客戶端的cookie 中。ps:還有一種是瀏覽器禁用了cookie或不支持cookie,這種可以通過URL重寫的方式發到服務器;

session 認證流程(如上圖):

  • 用戶第一次請求服務器的時候,服務器根據用戶提交的相關信息,創建對應的 Session
  • 請求返回時將此 Session 的唯一標識信息 SessionID 返回給瀏覽器
  • 瀏覽器接收到服務器返回的 SessionID 信息后,會將此信息存入到 Cookie 中,同時 Cookie 記錄此 SessionID 屬于哪個域名
  • 當用戶第二次訪問服務器的時候,請求會自動判斷此域名下是否存在 Cookie 信息,如果存在自動將 Cookie 信息也發送給服務端,服務端會從 Cookie 中獲取 SessionID,再根據 SessionID 查找對應的 Session 信息,如果沒有找到說明用戶沒有登錄或者登錄失效,如果找到 Session 證明用戶已經登錄可執行后面操作。

根據以上流程可知,SessionID 是連接 Cookie 和 Session 的一道橋梁,大部分系統也是根據此原理來驗證用戶登錄狀態。

基于Session的認證機制由Servlet規范定制,Servlet容器已經實現,用戶通過HttpSession的操作方法可以實現:

Cookie 和 Session 的區別

  • 安全性:Session 是存儲在服務器端的,Cookie 是存儲在客戶端的。所以 Session 相比 Cookie 安全,
  • 存取值的類型不同:Cookie 只支持存字符串數據,想要設置其他類型的數據,需要將其轉換成字符串,Session 可以存任意數據類型。
  • 有效期不同: Cookie 可設置為長時間保持,比如我們經常使用的默認登錄功能,Session 一般失效時間較短,客戶端關閉(默認情況下)或者 Session 超時都會失效。
  • 存儲大小不同: 單個 Cookie 保存的數據不能超過 4K,Session 可存儲數據遠高于 Cookie,但是當訪問量過多,會占用過多的服務器資源。

拓展:Session痛點

看起來通過 cookie + session 的方式是解決了問題, 但是我們忽略了一個問題,上述情況能正常工作是因為我們假設 server 是單機工作的,但實際在生產上,為了保障高可用,一般服務器至少需要兩臺機器,通過負載均衡的方式來決定到底請求該打到哪臺機器上。

假設登錄請求打到了 A 機器,A 機器生成了 session 并在 cookie 里添加 sessionId 返回給了瀏覽器,那么問題來了:下次添加購物車時如果請求打到了 B 或者 C,由于 session 是在 A 機器生成的,此時的 B,C 是找不到 session 的,那么就會發生無法添加購物車的錯誤,就得重新登錄了,此時請問該怎么辦。主要有以下三種方式:

(1 )session 復制

A 生成 session 后復制到 B, C,這樣每臺機器都有一份 session,無論添加購物車的請求打到哪臺機器,由于 session 都能找到,故不會有問題

這種方式雖然可行,但缺點也很明顯:

  • 同一樣的一份 session 保存了多份,數據冗余
  • 如果節點少還好,但如果節點多的話,特別是像阿里,微信這種由于 DAU 上億,可能需要部署成千上萬臺機器,這樣節點增多復制造成的性能消耗也會很大。

(2)session 粘連

這種方式是讓每個客戶端請求只打到固定的一臺機器上,比如瀏覽器登錄請求打到 A 機器后,后續所有的添加購物車請求也都打到 A 機器上,Nginx 的 sticky 模塊可以支持這種方式,支持按 ip 或 cookie 粘連等等,如按 ip 粘連方式如下

這樣的話每個 client 請求到達 Nginx 后,只要它的 ip 不變,根據 ip hash 算出來的值會打到固定的機器上,也就不存在 session 找不到的問題了,當然不難看出這種方式缺點也是很明顯,對應的機器掛了怎么辦?

(3)session 共享

這種方式也是目前各大公司普遍采用的方案,將 session 保存在 redis,memcached 等中間件中,請求到來時,各個機器去這些中間件取一下 session 即可。

缺點其實也不難發現,就是每個請求都要去 redis 取一下 session,多了一次內部連接,消耗了一點性能,另外為了保證 redis 的高可用,必須做集群,當然了對于大公司來說, redis 集群基本都會部署,所以這方案可以說是大公司的首選了。

Token(令牌)與 JWT(跨域認證)

Token概述(no session!)

通過上文分析我們知道通過在服務端共享 session 的方式可以完成用戶的身份定位,但是不難發現也有一個小小的瑕疵:搞個校驗機制我還得搭個 redis 集群?大廠確實 redis 用得比較普遍,但對于小廠來說可能它的業務量還未達到用 redis 的程度,所以有沒有其他不用 server 存儲 session 的用戶身份校驗機制呢,使用token!

簡單來說:首先請求方輸入自己的用戶名,密碼,然后 server 據此生成 token,客戶端拿到 token 后會保存到本地(token存儲在瀏覽器端),之后向 server 請求時在請求頭帶上此 token 即可(server有校驗機制,檢驗token合法性,同時server通過token中攜帶的uid確定是誰在訪問它)。

可以看到 token 主要由三部分組成:

  • header:指定了簽名算法
  • payload:可以指定用戶 id,過期時間等非敏感數據
  • Signature: 簽名,server 根據 header 知道它該用哪種簽名算法,再用密鑰根據此簽名算法對 head + payload 生成簽名,這樣一個 token 就生成了。

當 server 收到瀏覽器傳過來的 token 時,它會首先取出 token 中的 header + payload,根據密鑰生成簽名,然后再與 token 中的簽名比對,如果成功則說明簽名是合法的,即 token 是合法的。而且你會發現 payload 中存有我們的 userId,所以拿到 token 后直接在 payload 中就可獲取 userid,避免了像 session 那樣要從 redis 去取的開銷。

你會發現這種方式確實很妙,只要 server 保證密鑰不泄露,那么生成的 token 就是安全的,因為如果偽造 token 的話在簽名驗證環節是無法通過的,就此即可判定 token 非法。

可以看到通過這種方式有效地避免了 token 必須保存在 server 的弊端,實現了分布式存儲,不過需要注意的是,token 一旦由 server 生成,它就是有效的,直到過期,無法讓 token 失效,除非在 server 為 token 設立一個黑名單,在校驗 token 前先過一遍此黑名單,如果在黑名單里則此 token 失效,但一旦這樣做的話,那就意味著黑名單就必須保存在 server,這又回到了 session 的模式,那直接用 session 不香嗎。所以一般的做法是當客戶端登出要讓 token 失效時,直接在本地移除 token 即可,下次登錄重新生成 token 就好。

另外需要注意的是 token 一般是放在 header 的 Authorization 自定義頭里,不是放在 Cookie 里的,這主要是為了解決跨域不能共享 Cookie 的問題

總結:token解決什么問題(為什么要用token)?

  • 完全由應用管理,可以避開同源策略
  • 支持跨域訪問,cookie不支持, Cookie 跨站是不能共享的,這樣的話如果你要實現多應用(多系統)的單點登錄(SSO),使用 Cookie 來做需要的話就很困難了。但如果用 token 來實現 SSO 會非常簡單,只要在 header 中的 authorize 字段(或其他自定義)加上 token 即可完成所有跨域站點的認證。
  • token是無狀態的,可以在多個服務器間共享
  • token可以避免CSRF攻擊(跨站請求攻擊)
  • 易于擴展,在移動端原生請求是沒有 cookie 之說的,而 sessionid 依賴于 cookie,sessionid 就不能用 cookie 來傳了,如果用 token 的話,由于它是隨著 header 的 authoriize 傳過來的,也就不存在此問題,換句話說token 天生支持移動平臺,可擴展性好

拓展1:那啥是CSRF呢?

攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站并運行一些操作(如發郵件,發消息,甚至財產操作如轉賬和購買商品)。由于瀏覽器曾經認證過(cookie 里帶來 sessionId 等身份認證的信息),所以被訪問的網站會認為是真正的用戶操作而去運行。

那么如果正常的用戶誤點了上面這張圖片,由于相同域名的請求會自動帶上 cookie,而 cookie 里帶有正常登錄用戶的 sessionid,類似上面這樣的轉賬操作在 server 就會成功,會造成極大的安全風險

CSRF 攻擊的根本原因在于對于同樣域名的每個請求來說,它的 cookie 都會被自動帶上,這個是瀏覽器的機制決定的!

至于完成一次CSRF攻擊必要的兩個步驟:

1、首先登了一個正常的網站A,并且在本地生成了cookie
2、在cookie有效時間內,訪問了危險網站B(就獲取了身份信息)

Q:那我不訪問危險網站就完了唄?
A:危險網站也許只是個存在漏洞的可信任網站!

Q:那我訪問完正常網站,關了瀏覽器就好了呀?
A:即使關閉瀏覽器,cookie也不保證一定立即失效,而且關閉瀏覽器并不能結束會話,session的生命周期跟這些都沒關系。

拓展2:同源策略?

  • 就是不同源的客戶端腳本在沒有明確授權情況下,不準讀寫對方的資源!
  • 同源就是:協議、域名與端口號都相同。
  • 同源策略是由 Netscape 提出的著名安全策略,是瀏覽器最核心、基本的安全功能,它限制了一個源中加載腳本與來自其他源中資源的交互方式。

拓展3:什么是跨域,如何解決?

  • 當瀏覽器執行腳本時會檢查是否同源,只有同源的腳本才會執行,如果不同源即為跨域。

  • 產生原因:它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript實施的安全限制。

  • 解決方案

    • nginx(靜態服務器)反向代理解決跨域(前端常用),a明確訪問c代理服務器,但是不知道c的內容從哪里來,c反向從別的地方拿來數據。(忽略的是目標地址),瀏覽器可以訪問a,而服務器之間不存在跨域問題,瀏覽器先訪問a的服務器c,讓c服務器作為代理去訪問b服務器,拿到之后再返回數據給a。
    • jsonp:通常為了減輕web服務器的負載,我們把js、css、圖片等靜態資源分離到另一臺獨立域名的服務器上,在html頁面中再通過相應的標簽從不同域名下加載靜態資源,而被瀏覽器允許。
    • 添加響應頭

拓展4:易于擴展?

  • 比如有多臺服務器,使用負載均衡,第一次登錄轉發到了A,A中seesion緩存了用戶的登錄信息,第二次登錄轉發到了B,這時候就丟失了登錄狀態,當然這樣也是有解決方案可以共享session,但token只需要所有的服務器使用相同的解密手段即可。

拓展5:無狀態?

  • 服務端不保存客戶端請求者的任何信息,客戶端每次請求必須自備描述信息,通過這些信息來識別客戶端身份。服務端只需要確認該token是否是自己親自簽發即可,簽發和驗證都在服務端進行。

拓展6:什么是單點登錄?

  • 所謂單點登錄,是指在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。

Acesss Token

  • 訪問資源接口(API)時所需要的資源憑證
  • 簡單 token 的組成: uid(用戶唯一的身份標識)、time(當前時間的時間戳)、sign(簽名,token 的前幾位以哈希算法壓縮成的一定長度的十六進制字符串)

特點:

  • 服務端無狀態化、可擴展性好
  • 支持移動端設備
  • 安全性高
  • 支持跨程序調用

token 的身份驗證流程:

  1. 客戶端使用用戶名跟密碼請求登錄
  2. 服務端收到請求,去驗證用戶名與密碼
  3. 驗證成功后,服務端會簽發一個 token 并把這個 token 發送給客戶端
  4. 客戶端收到 token 以后,會把它存儲起來,比如放在 cookie 里或者 localStorage 里
  5. 客戶端每次向服務端請求資源的時候需要帶著服務端簽發的 token
  6. 服務端收到請求,然后去驗證客戶端請求里面帶著的 token ,如果驗證成功,就向客戶端返回請求的數據

注意點

  • 每一次請求都需要攜帶 token,需要把 token 放到 HTTP 的 Header 里
  • 基于 token 的用戶認證是一種服務端無狀態的認證方式,服務端不用存放 token 數據。用解析 token 的計算時間換取 session 的存儲空間,從而減輕服務器的壓力,減少頻繁的查詢數據庫
  • token 完全由應用管理,所以它可以避開同源策略

Refresh Token

  • 另外一種 token——refresh token
  • refresh token 是專用于刷新 access token 的 token。如果沒有 refresh token,也可以刷新 access token,但每次刷新都要用戶輸入登錄用戶名與密碼,會很麻煩。有了 refresh token,可以減少這個麻煩,客戶端直接用 refresh token 去更新 access token,無需用戶進行額外的操作。

兩者區別

  • Access Token 的有效期比較短,當 Acesss Token 由于過期而失效時,使用 Refresh Token 就可以獲取到新的 Token,如果 Refresh Token 也失效了,用戶就只能重新登錄了。
  • Refresh Token 及過期時間是存儲在服務器的數據庫中,只有在申請新的 Acesss Token 時才會驗證,不會對業務接口響應時間造成影響,也不需要向 Session 一樣一直保持在內存中以應對大量的請求。

Token 的缺點

那有人就問了,既然 token 這么好,那為什么各個大公司幾乎都采用共享 session 的方式呢,可能很多人是第一次聽到 token,token 不香嗎? token 有以下兩點劣勢:

  • token 太長了:token 是 header, payload 編碼后的樣式,所以一般要比 sessionId 長很多,很有可能超出 cookie 的大小限制(cookie 一般有大小限制的,如 4kb),如果你在 token 中存儲的信息越長,那么 token 本身也會越長,這樣的話由于你每次請求都會帶上 token,對請求來是個不小的負擔

  • 不太安全:網上很多文章說 token 更安全,其實不然,細心的你可能發現了,我們說 token 是存在瀏覽器的,再細問,存在瀏覽器的哪里?既然它太長放在 cookie 里可能導致 cookie 超限,那就只好放在 local storage 里,這樣會造成安全隱患,因為 local storage 這類的本地存儲是可以被 JS 直接讀取的,另外由上文也提到,token 一旦生成無法讓其失效,必須等到其過期才行,這樣的話如果服務端檢測到了一個安全威脅,也無法使相關的 token 失效。

所以 token 更適合一次性的命令認證,設置一個比較短的有效期!!!

拓展:不管是 cookie 還是 token,從存儲角度來看其實都不安全(實際上防護 CSRF 攻擊的正確方式是用 CSRF token),都有暴露的風險,我們所說的安全更多的是強調傳輸中的安全,可以用 HTTPS 協議來傳輸, 這樣的話請求頭都能被加密,也就保證了傳輸中的安全。

其實我們把 cookie 和 token 比較本身就不合理,一個是存儲方式,一個是驗證方式,正確的比較應該是 session vs token。

Token 和 Session 的區別

token和session其實都是為了身份驗證,session一般翻譯為會話,而token更多的時候是翻譯為令牌;session和token都是有過期時間一說,都需要去管理過期時間;

  • Session 是一種記錄服務器和客戶端會話狀態的機制,使服務端有狀態化,可以記錄會話信息(可能保存在緩存、文件或數據庫)。而 Token 是令牌訪問資源接口(API)時所需要的資源憑證。Token 使服務端無狀態化,不會存儲會話信息。

  • 其實token與session的問題是一種時間與空間的博弈問題,session是空間換時間,而token是時間換空間。兩者的選擇要看具體情況而定。雖然確實都是“客戶端記錄,每次訪問攜帶”,但 token 很容易設計為自包含的,也就是說,后端不需要記錄什么東西,每次一個無狀態請求,每次解密驗證,每次當場得出合法 /非法的結論。這一切判斷依據,除了固化在 CS 兩端的一些邏輯之外,整個信息是自包含的。這才是真正的無狀態。 而 sessionid ,一般都是一段隨機字符串,需要到后端去檢索 id 的有效性。萬一服務器重啟導致內存里的 session 沒了呢?萬一 redis 服務器掛了呢?

  • 所謂 Session 認證只是簡單的把 User 信息存儲到 Session 里,因為 SessionID 的不可預測性,暫且認為是安全的。而 Token ,如果指的是 OAuth Token 或類似的機制的話,提供的是 認證 和 授權 ,認證是針對用戶,授權是針對 App 。其目的是讓某 App 有權利訪問某用戶的信息。這里的 Token 是唯一的。不可以轉移到其它 App上,也不可以轉到其它用戶上。Session 只提供一種簡單的認證,即只要有此 SessionID ,即認為有此 User 的全部權利。是需要嚴格保密的,這個數據應該只保存在站方,不應該共享給其它網站或者第三方 App。所以簡單來說:如果你的用戶數據可能需要和第三方共享,或者允許第三方調用 API 接口,用 Token 。如果永遠只是自己的網站,自己的 App,用什么就無所謂了。

JWT概述

  • JSON Web Token(簡稱 JWT)是目前最流行的跨域認證解決方案。是一種認證授權機制

  • JWT 是為了在網絡應用環境間傳遞聲明而執行的一種基于 JSON 的開放標準(RFC 7519)。JWT 的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便于從資源服務器獲取資源。比如用在用戶登錄上。

  • 可以使用 HMAC 算法或者是 RSA 的公/私秘鑰對 JWT 進行簽名。因為數字簽名的存在,這些傳遞的信息是可信的。

  • 阮一峰老師的 JSON Web Token 入門教程 講的非常通俗易懂,這里就不再班門弄斧了

生成 JWT

JWT 的原理

JWT 認證流程:

  • 用戶輸入用戶名/密碼登錄,服務端認證成功后,會返回給客戶端一個 JWT

  • 客戶端將 token 保存到本地(通常使用 localstorage,也可以使用 cookie)

  • 當用戶希望訪問一個受保護的路由或者資源的時候,需要請求頭的 Authorization 字段中使用Bearer 模式添加 JWT,其內容看起來是下面這樣

Authorization: Bearer <token>
  • 服務端的保護路由將會檢查請求頭 Authorization 中的 JWT 信息,如果合法,則允許用戶的行為

  • 因為 JWT 是自包含的(內部包含了一些會話信息),因此減少了需要查詢數據庫的需要

  • 因為 JWT 并不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服務而不需要擔心跨域資源共享問題(CORS)

  • 因為用戶的狀態不再存儲在服務端的內存中,所以這是一種無狀態的認證機制

JWT 的使用方式

方式一

  • 當用戶希望訪問一個受保護的路由或者資源的時候,可以把它放在 Cookie 里面自動發送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請求頭信息的 Authorization 字段里,使用 Bearer 模式添加 JWT。
GET /calendar/v1/events
  • 用戶的狀態不會存儲在服務端的內存中,這是一種 無狀態的認證機制

  • 服務端的保護路由將會檢查請求頭 Authorization 中的 JWT 信息,如果合法,則允許用戶的行為。

  • 由于 JWT 是自包含的,因此減少了需要查詢數據庫的需要

  • JWT 的這些特性使得我們可以完全依賴其無狀態的特性提供數據 API 服務,甚至是創建一個下載流服務。

  • 因為 JWT 并不使用 Cookie ,所以你可以使用任何域名提供你的 API 服務而不需要擔心跨域資源共享問題(CORS)

方式二

  • 跨域的時候,可以把 JWT 放在 POST 請求的數據體里。

方式三

  • 通過 URL 傳輸
http://www.example.com/user?token=xxx

項目中使用 JWT

項目地址:https://github.com/yjdjiayou/jwt-demo

Token 和 JWT 的區別

相同:

  • 都是訪問資源的令牌

  • 都可以記錄用戶的信息

  • 都是使服務端無狀態化

  • 都是只有驗證成功后,客戶端才能訪問服務端上受保護的資源

區別:

  • Token:服務端驗證客戶端發送過來的 Token 時,還需要查詢數據庫獲取用戶信息,然后驗證 Token 是否有效。

  • JWT:將 Token 和 Payload 加密后存儲于客戶端,服務端只需要使用密鑰解密進行校驗(校驗也是 JWT 自己實現的)即可,不需要查詢或者減少查詢數據庫,因為 JWT 自包含了用戶信息和加密的數據。

常見的前后端鑒權方式

  1. Session-Cookie

  2. Token 驗證(包括 JWT,SSO)

  3. OAuth2.0(開放授權)

常見的加密算法

不可逆加密:【Hash加密算法/散列算法/摘要算法】

  • 一旦加密就不能反向解密得到密碼原文,一般用來加密用戶密碼,app的服務器端數據庫里一般存儲的也都是加密后的用戶密碼。

  • 在數據傳輸的過程中,首先把密碼類數據經過MD5加密算法加密,然后再在外面使用可逆的加密方式加密一次,這樣在數據傳輸的過程中,即便數據被截獲了,但是想要完全破解,還是很難的。

  • Hash算法特別的地方在于它是一種單向算法,用戶可以通過Hash算法對目標信息生成一段特定長度的唯一的Hash值,卻不能通過這個Hash值重新獲得目標信息。因此Hash算法常用在不可還原的密碼存儲、信息完整性校驗等。

  • 用途:一般用于效驗下載文件正確性,一般在網站上下載文件都能見到;存儲用戶敏感信息,如密碼、 卡號等不可解密的信息。

  • 常見的不可逆加密算法有:MD5、SHA、HMAC

MD5:Message Digest Algorithm MD5(中文名為消息摘要算法第五版)為計算機安全領域廣泛使用的一種散列函數,用以提供消息的完整性保護。該算法的文件號為RFC 1321(R.Rivest,MIT Laboratory for Computer Science and RSA Data Security Inc. April 1992)。 MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于確保信息傳輸完整一致。是計算機廣泛使用的雜湊算法之一(又譯摘要算法、哈希算法),主流編程語言普遍已有MD5實現。將數據(如漢字)運算為另一固定長度值,是雜湊算法的基礎原理,MD5的前身有MD2、MD3和MD4。

MD5算法具有以下特點: 1、壓縮性:任意長度的數據,算出的MD5值長度都是固定的。 2、容易計算:從原數據計算出MD5值很容易。 3、抗修改性:對原數據進行任何改動,哪怕只修改1個字節,所得到的MD5值都有很大區別。 4、強抗碰撞:已知原數據和其MD5值,想找到一個具有相同MD5值的數據(即偽造數據)是非常困難的。 MD5的作用是讓大容量信息在用數字簽名軟件簽署私人密鑰前被"壓縮"成一種保密的格式(就是把一個任意長度的字節串變換成一定長的十六進制數字串)。除了MD5以外,其中比較有名的還有sha-1、RIPEMD以及Haval等。

SHA1 :安全哈希算法(Secure Hash Algorithm)主要適用于數字簽名標準 (Digital Signature Standard DSS)里面定義的數字簽名算法(Digital Signature Algorithm DSA)。對于長度小于2^64位的消息,SHA1會產生一個160位的消息摘要。當接收到消息的時候,這個消息摘要可以用來驗證數據的完整性。在傳輸的過程中,數據很可能會發生變化,那么這時候就會產生不同的消息摘要。 SHA1有如下特點: 1.不可以從消息摘要中復原信息; 2.兩個不同的消息不會產生同樣的消息摘要,(但會有1x10 ^ 48分之一的機率出現相同的消息摘要,一般使用時忽略)。

可逆加密:可逆加密有對稱加密和非對稱加密。

對稱加密:【文件加密和解密使用相同的密鑰,即加密密鑰也可以用作解密密鑰】

  • 在對稱加密算法中,數據發信方將明文和加密密鑰一起經過特殊的加密算法處理后,使其變成復雜的加密密文發送出去,收信方收到密文后,若想解讀出原文,則需要使用加密時用的密鑰以及相同加密算法的逆算法對密文進行解密,才能使其回復成可讀明文。在對稱加密算法中,使用的密鑰只有一個,收發雙方都使用這個密鑰,這就需要解密方事先知道加密密鑰。

  • 對稱加密算法的優點是算法公開、計算量小、加密速度快、加密效率高。

  • 用途:一般用于保存用戶手機號、身份證等敏感但能解密的信息。

  • 常見的對稱加密算法有AES(高級加密標準)、DES(數據加密算法)、3DES、Blowfish、IDEA、RC4、RC5、RC6

非對稱加密:【兩個密鑰:公開密鑰(publickey)和私有密鑰,公有密鑰加密,私有密鑰解密】

  • 非對稱加密算法是一種密鑰的保密方法。非對稱加密算法需要兩個密鑰:公開密鑰(publickey)和私有密鑰(privatekey)。公開密鑰與私有密鑰是一對,如果用公開密鑰對數據進行加密,只有用對應的私有密鑰才能解密;如果用私有密鑰對數據進行加密,那么只有用對應的公開密鑰才能解密。因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。

  • 非對稱加密算法實現機密信息交換的基本過程是:甲方生成一對密鑰并將其中的一把作為公用密鑰向其它方公開;得到該公用密鑰的乙方使用該密鑰對機密信息進行加密后再發送給甲方;甲方再用自己保存的另一把專用密鑰對加密后的信息進行解密。

  • 服務器存私鑰,客戶端拿公鑰,客戶端加解密算法可以做成so庫。

  • 非對稱加密與對稱加密相比,其安全性更好;非對稱加密的缺點是加密和解密花費時間長、速度慢,只適合對少量數據進行加密。

  • 用途:一般用于簽名和認證。

  • 常見的非對稱加密算法有:RSA(公鑰加密算法)、DSA(數字簽名用)、ECC(移動設備用)、Diffie-Hellman、El Gamal

常見問題

使用 cookie 時需要考慮的問題

  • 因為存儲在客戶端,容易被客戶端篡改,使用前需要驗證合法性,所以,不要存儲敏感數據,比如用戶密碼,賬戶余額;使用 httpOnly 在一定程度上提高安全性

  • 盡量減少 cookie 的體積,能存儲的數據量不能超過 4kb;一個瀏覽器針對一個網站最多存 20 個Cookie,瀏覽器一般只允許存放 300 個Cookie

  • 設置正確的 domain 和 path,減少數據傳輸

  • cookie 無法跨域(每個 cookie 都會綁定單一的域名,無法在別的域名下獲取使用)

  • 移動端對 cookie 的支持不是很好,而 session 需要基于 cookie 實現,所以移動端常用的是 token

使用 session 時需要考慮的問題

  • 將 session 存儲在服務器里面,當用戶同時在線量比較多時,這些 session 會占據較多的內存,需要在服務端定期的去清理過期的 session

  • 當網站采用集群部署的時候,會遇到多臺 web 服務器之間如何做 session 共享的問題。因為 session 是由單個服務器創建的,但是處理用戶請求的服務器不一定是那個創建 session 的服務器,那么該服務器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。解決方案:寫客戶端cookie的方式、服務器之間session數據同步、用mysql數據庫共享session數據。

  • 當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因為不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理

  • sessionId 是存儲在 cookie 中的,假如瀏覽器禁止 cookie 或不支持 cookie 怎么辦? 一般會把 sessionId 跟在 url 參數后面即重寫 url,所以 session 不一定非得需要靠 cookie 實現

  • 移動端對 cookie 的支持不是很好,而 session 需要基于 cookie 實現,所以移動端常用的是 token

使用 token 時需要考慮的問題

  • 如果你認為用數據庫來存儲 token 會導致查詢時間太長,可以選擇放在內存當中。比如 redis 很適合你對 token 查詢的需求。

  • token 完全由應用管理,所以它可以避開同源策略

  • token 可以避免 CSRF 攻擊(因為不需要 cookie 了)

  • 移動端對 cookie 的支持不是很好,而 session 需要基于 cookie 實現,所以移動端常用的是 token

使用 JWT 時需要考慮的問題

  • 因為 JWT 并不依賴 Cookie 的,所以你可以使用任何域名提供你的 API 服務而不需要擔心跨域資源共享問題(CORS)

  • JWT 默認是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。

  • JWT 不加密的情況下,不能將秘密數據寫入 JWT。

  • JWT 不僅可以用于認證,也可以用于交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。

  • JWT 最大的優勢是服務器不再需要存儲 Session,使得服務器認證鑒權業務可以方便擴展。但這也是 JWT 最大的缺點:由于服務器不需要存儲 Session 狀態,因此使用過程中無法廢棄某個 Token 或者更改 Token 的權限。也就是說一旦 JWT 簽發了,到期之前就會始終有效,除非服務器部署額外的邏輯。

  • JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT的有效期應該設置得比較短。對于一些比較重要的權限,使用時應該再次對用戶進行認證。

  • JWT 適合一次性的命令認證,頒發一個有效期極短的 JWT,即使暴露了危險也很小,由于每次操作都會生成新的 JWT,因此也沒必要保存 JWT,真正實現無狀態。

  • 為了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。

使用加密算法時需要考慮的問題

  • 絕不要以明文存儲密碼

  • 永遠使用 哈希算法 來處理密碼,絕不要使用 Base64 或其他編碼方式來存儲密碼,這和以明文存儲密碼是一樣的,使用哈希,而不要使用編碼。編碼以及加密,都是雙向的過程,而密碼是保密的,應該只被它的所有者知道, 這個過程必須是單向的。哈希正是用于做這個的,從來沒有解哈希這種說法, 但是編碼就存在解碼,加密就存在解密。

  • 絕不要使用弱哈希或已被破解的哈希算法,像 MD5 或 SHA1 ,只使用強密碼哈希算法。

  • 絕不要以明文形式顯示或發送密碼,即使是對密碼的所有者也應該這樣。如果你需要 “忘記密碼” 的功能,可以隨機生成一個新的 一次性的(這點很重要)密碼,然后把這個密碼發送給用戶。

分布式架構下 session 共享方案

1. session 復制

  • 任何一個服務器上的 session 發生改變(增刪改),該節點會把這個 session 的所有內容序列化,然后廣播給所有其它節點,不管其他服務器需不需要 session ,以此來保證 session 同步

優點: 可容錯,各個服務器間 session 能夠實時響應。

缺點: 會對網絡負荷造成一定壓力,如果 session 量大的話可能會造成網絡堵塞,拖慢服務器性能。

2. 粘性 session /IP 綁定策略

  • 采用 Ngnix 中的 ip_hash 機制,將某個 ip的所有請求都定向到同一臺服務器上,即將用戶與服務器綁定。 用戶第一次請求時,負載均衡器將用戶的請求轉發到了 A 服務器上,如果負載均衡器設置了粘性 session 的話,那么用戶以后的每次請求都會轉發到 A 服務器上,相當于把用戶和 A 服務器粘到了一塊,這就是粘性 session 機制。

優點: 簡單,不需要對 session 做任何處理。

缺點: 缺乏容錯性,如果當前訪問的服務器發生故障,用戶被轉移到第二個服務器上時,他的 session 信息都將失效。

適用場景: 發生故障對客戶產生的影響較小;服務器發生故障是低概率事件 。

實現方式: 以 Nginx 為例,在 upstream 模塊配置 ip_hash 屬性即可實現粘性 session。

3. session 共享(常用)

  • 使用分布式緩存方案比如 Memcached 、Redis 來緩存 session,但是要求 Memcached 或 Redis 必須是集群

  • 把 session 放到 Redis 中存儲,雖然架構上變得復雜,并且需要多訪問一次 Redis ,但是這種方案帶來的好處也是很大的:

    • 實現了 session 共享;

    • 可以水平擴展(增加 Redis 服務器);

    • 服務器重啟 session 不丟失(不過也要注意 session 在 Redis 中的刷新/失效機制);

    • 不僅可以跨服務器 session 共享,甚至可以跨平臺(例如網頁端和 APP 端)

4. session 持久化

  • 將 session 存儲到數據庫中,保證 session 的持久化

優點: 服務器出現問題,session 不會丟失

缺點: 如果網站的訪問量很大,把 session 存儲到數據庫中,會對數據庫造成很大壓力,還需要增加額外的開銷維護數據庫。

只要關閉瀏覽器 ,session 真的就消失了?

不對。對 session 來說,除非程序通知服務器刪除一個 session,否則服務器會一直保留,程序一般都是在用戶做 log off 的時候發個指令去刪除 session。

然而瀏覽器從來不會主動在關閉之前通知服務器它將要關閉,因此服務器根本不會有機會知道瀏覽器已經關閉,之所以會有這種錯覺,是大部分 session 機制都使用會話 cookie 來保存 session id,而關閉瀏覽器后這個 session id 就消失了,再次連接服務器時也就無法找到原來的 session

如果服務器設置的 cookie 被保存在硬盤上,或者使用某種手段改寫瀏覽器發出的 HTTP 請求頭,把原來的 session id 發送給服務器,則再次打開瀏覽器仍然能夠打開原來的 session。

恰恰是由于關閉瀏覽器不會導致 session 被刪除,迫使服務器為 session 設置了一個失效時間,當距離客戶端上一次使用 session 的時間超過這個失效時間時,服務器就認為客戶端已經停止了活動,才會把 session 刪除以節省存儲空間。

項目地址

https://github.com/yjdjiayou/jwt-demo

巨人的肩膀:

https://juejin.im/post/6844904034181070861
http://www.lxweimin.com/p/b2e23939ad46
https://blog.51cto.com/jamesfancy/2065665
https://blog.csdn.net/kaixuansui/article/details/94433920
https://www.cnblogs.com/yuki-nana/p/14001600.html
https://www.cnblogs.com/xiaozhang2014/p/7750200.html
https://mp.weixin.qq.com/s/j-6ngGKRJvB3gfY4VJaU4A

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容