前言
前幾天,我寫了一篇文章,《我設計的一個安全的 web 系統用戶密碼管理流程》。其中著重點是講的如何利用非對稱加密進行安全的設計,并在講述了原理之后,又寫了 《node 后端和瀏覽器前端,有關 RSA 非對稱加密的完整實踐, 前后端匹配的代碼演示》 這篇文章,著重講解了有關 RSA 非對稱加密的完整實現。
但是,這些文章里并沒有講到用戶登錄管理的核心,也就是用戶會話管理。這里面有很多概念,對于很多前端開發和很多初級后端開發來說,都是很模糊的。所以,今天,我想通過這一篇文章,把這里面的核心點全部講明白。
基礎概念
我們會聽到一些詞,大概是 Session
, Session ID
, Cookies
, Token
等等,現在又有了新詞兒,新的解決方案,如 JWT
,長短 Token 等等。
我們一個一個來講。這些詞分別有對應的英文意思,但是每個詞都有多個含義,并且在不同的環境下,還會有衍生不同的含義。因此,我這邊只能從前后端開發的角度去解釋這些名詞,不能按照字典的含義去解釋。
而且,由于網上言論眾多,其中謬誤的也好,佶屈聱牙的也罷,我都不去管它,我只按照我的理解來講解,并且,我相信,我的理解是非常深入到位的。
Session
翻譯——會話,引申為會話管理。那么,什么是會話呢?
首先,我們要理解一個基礎概念——http 服務,是一種無狀態服務。怎么理解無狀態呢?一句話講,就是——在默認情況下,后端程序根本不知道前端發過來的請求,誰是誰!
為了解決這個不知道誰是誰的問題,就引申出來解決方案——讓后端程序知道,每一個請求,都是誰發出來的,好相應的把對應的資源發回去。
比如,張三請求 profile
接口,返回的內容應該 {name:"張三"}
,而不能是 {name:“李四”}。
那么,我們就可以理解,張三發出來的一系列的請求,應該歸屬于一個會話,李四發出來的一系列請求,應該歸屬于另一個會話。
所以,我們就需要把這一系列的會話,進行管理,這就是會話管理。
換句話想,Session,并不是一個單純的詞,而是一個相對很大的概念,為了解決這個問題,不同的語言有不同的解決方案,這些各種不同的解決方案,都可以統稱為 Session。
Session ID
理解了上面的 Session 的概念,這個概念就相對比較好理解了。每一個用戶,都有一堆的資源,我們不能把這些資源都隨時在程序里放著。因此,我們會把這些資源打包存著,然后整出一個 ID 來,方便我們來查詢。這樣,前端只要給我們這個 ID,我們就可以根據這個ID查找到對應的資源。
這里說的用戶資源,是指 用戶名,用戶昵稱,用戶角色,過期時間等簡短信息,只要方便后端程序快速的分清楚這貨是誰,能干些啥事兒即可。不是說,要把所有的用戶資源信息,全部存著。如果是那樣的話,還要數據庫干啥呢對吧。
Token
字面意思,憑證,令牌。是后端下發給前端的一段字符串(可長可短,你要愿意,給數字也不是不行,看你咋設計了。)
前端拿到這個 Token 以后,它每次發起請求時,都需要把這個 Token 給帶上,后端根據這個 Token ,就可以去查找對應的資源了。
OK,注意到了嗎?上面的 Session ID 是方便我們后端去查找資源的,這個 Token,也是后端去查找對應資源的。
所以—— Token = Session ID ?
答案是不一定。要看你咋設計。
一般而言,我是傾向于設計為 Token,就是 Session ID。當然,也可以不一樣,前端給后端 Token,后端先根據 Token 去查找 SessionID,然后根據 ID 再查找對應資源,也是可以的對吧?
但我感覺這樣設計有點缺心眼兒。
Cookies
字面意思是一種餅干,我不喜歡吃餅干,所以我不知道它具體是哪一種餅干。
Cookies 是用戶瀏覽器的一種機制,它可以由我們的后端程序去設定。而且,在每次用戶發起請求的時候,瀏覽器都會把 Cookies 自動帶上。
換句話講,如果我們使用 Cookies,可以大幅的減少前端開發的工作量,因為具體是咋實現的,不用管了,瀏覽器自動實現。
但問題是,現在的前端開發,往往不僅僅是運行在瀏覽器里面的網頁,還有 APP、 小程序或者其他客戶端程序,而這些里面,大多是沒有 Cookies 的機制的。
因此,為了我們的后端服務可以同時服務多端,我建議就不要管這個玩意兒了。
不是不能用,順手的時候,也可以兼容。
JWT
JSONWebToken
的縮寫。在傳統的會話管理里,都涉及一個存儲的問題,就是,要把用戶資源存著,然后去查詢。你可以存在內存里,文件里,數據庫里,等等,總之,你要存數據,并且方便查詢。
假設,我們存在內存里,而我們的后端服務運行在多臺負載均衡的服務器上,那么,當用戶的 A 請求訪問在甲服務器上,甲服務器在內存里存儲了數據,B請求訪問在乙服務器上,而乙服務器的內存中并沒有用戶數據,則會出現讀取失敗的問題。
怎么解決呢?聰明的你一定想到了解決方案——集中存儲,比如單獨搞一臺 redis 服務器,大家都往這里存。挺好的。
那么假設你的這些服務器不在一個機房呢?也不是不能讀是吧,就是慢點兒。
總之,傳統的會話管理,有一個問題,就是 IO 問題,也就是存儲和讀取的問題。
JWT 方案是換了一個思路來解決,其核心原理是,直接將用戶的資源信息加密,然后返回給客戶端,客戶端拿這個加密的長字符串直接作為 Token 來請求,后端程序只要解密字符串,就直接獲得了用戶的資源信息了。
也就是說, JWT 是用計算復雜度取代了存儲復雜度,來解決上面說的問題。
總之,會話管理是一定要消耗資源的,要么是存儲復雜度,要么是計算復雜度。
在很多文章和概念里,把 JWT 方案,和 Session 方案并列對比。可以這么比,但是我感覺概念不對。其實在我看來,JWT 方案,只是一種 Session 解決方案而已,兩者是上下級的關系,而不是并列的關系。
我這邊把傳統的通過存儲(你別管存哪兒,咋存)解決方案,稱之為傳統存儲方案吧。
和傳統存儲方案相對比,優缺點如下
特點 | JWT | 存儲 |
---|---|---|
服務器狀態 | 無狀態 | 有狀態 |
跨域支持 | 天然支持 | 需CORS配置 |
實時吊銷 | 默認不支持 | 支持 |
傳輸效率 | 大點兒,問題不大 | 小,可忽略 |
其實總結下來,JWT最大的問題就是,一旦 Token 簽發,除了超時失效,否則是沒辦法把用戶登出的。
當然,聰明的你,肯定想到了黑名單機制,把登出的 Token 存下來,然后每次讀取驗證一下。那么我的問題是,那你為啥不用傳統的存儲會話管理,要用 JWT ?吃飽撐的?
長短 Token
好,通過上面的這些概念,我們知道,JWT 方案,不方便管理用戶,傳統存儲解決方案,有IO性能問題,那么能不能兩種方案結合結合,咱們取長補短!
可以,于是,有了長短 Token 的解決方案。
每次用戶登錄時,用 JWT 方式,簽發一個長的 Token,這個 Token 的過期時間比較短,比如5分鐘。5分鐘后,它就失效了。
同時,用傳統存儲方案,簽一個短的 Token。這個過期時間可以設置得比較長,比如一個月。
在正常業務時,后端直接讀取用戶傳過來的 JWT 信息,就可以得到用戶資源了。當前端發現 token 過期了,就拿 短 token 過來,再請求一個長 token,然后再繼續使用。
這樣,就解決了存儲問題,又可以管理用戶登出(直接在存儲里刪掉用戶的短Token 即可)
代價是什么呢?代價是提高了各個前端項目的代碼復雜度。你可以說,長短 Token 的解決方案是集合了兩種解決方案的長處,也可以理解為,兩者的短處也全粘上了。
傳輸數據變大,還是得存儲用戶信息,還消耗CPU計算資源等等。
小結
綜上所述,我們應該對用戶會話管理這塊的概念都有了一個清晰的理解了。那么我們在開發項目時應該如何選擇呢?
首先,無論是哪種解決方案,你都得會!沒有什么方案是一招鮮,吃遍天的。
例如,傳統存儲方案中的所謂 IO 問題,對于絕大多數項目來說,這就是一個不存在的問題。直接往內存一存,有啥性能瓶頸?攏共就跑在一臺服務器上,哪里來的什么速度或者進程共享的問題?在我看來,絕大多數項目,連 redis 都是多余。
再說,JWT 方案,有啥登出管理問題?一個小系統,一天登錄攏共就幾個人,你設置為有效期1天,我也不認為有多大的安全隱患,如果真遇到了什么大問題,直接把 JWT 的簽發加密詞一改,所有簽發的TOKEN,全部失效。
最后說說長短 TOKEN,如果你團隊有足夠多的人手,項目也確實有很高的安全性,可以考慮使用。否則,是徒增麻煩。
所以,我推薦你根據你自己的項目選擇合適的解決方案,我只說我的觀點:
- 大多數項目都可以使用 JWT 方案。
- 你有切實的要迫使用戶登出的項目,比如論壇等等,可以用傳統的存儲方案。
- 你團隊實力足夠,甲方是安全潔癖,項目也確實有高等級的要求,或者產品或老板想裝逼忽悠客戶,長短TOKEN你值得擁有!
本來,我是想在這篇文章中,引用一些代碼,方便各位看官理解的,沒想到,光講概念就講了這么多了。沒辦法,周末抽空,我會把每種方案的 NodeJS + Koa 的實戰完整代碼給寫成文章分享給大家。
最近我在參加 CSDN 的一個創作比賽,各位看官在留言區留言可以給我加分。所以希望各位看官幫幫忙,點評一下哦!
當然,我們程序開發都是很忙滴,沒時間點評沒關系哈,點個贊,收個藏,也不是不可以哈!耽誤不了您的事兒,還能讓我繼續保持創作熱情,小可在這邊謝謝各位看官了哈!