我們真的需要JWT嗎?
JWT(JSON Web Token)是目前最流行的認證方案之一。部落格園、各種技術公眾號隔三差五就會推一篇JWT相關的文章,真的多如牛毛。但我對JWT有點困惑,今天寫出來跟大家探討探討,不要噴哈。
JWT原理
本文默認讀者已經對JWT有所了解,下面不再詳細介紹JWT,只簡單提一下。
JWT全稱JSON Web Token。當伺服器認證成功後會生成一個Token,這個token包含了header、payload、signature三部分資訊。其中payload的內容有過期時間、簽發時間、還有自定義的欄位。自定義欄位往往用來存放用戶資訊,比如UserId,UserName等等資訊。當客戶端收到這個token後存儲在Cookie,localstorage或者別的什麼地方並且以後每次請求都帶上token。服務端對請求所攜帶的token進行解析,判斷是否過期是否合法。
以上簡單的描述了下JWT的工作原理,因為jwt的payload攜帶了過期時間、用戶資訊等,所以JWT有別於傳統Session方案的一個最大不同就是JWT是無狀態的,JWT不用在記憶體或DB里維持session的狀態,直接拿到token解析就可以了。
JWT的優點
無狀態?
這個優點真的爽,因為沒有了session,不用考慮session伺服器的壓力所以可以毫無顧忌的水平擴展,個人認為這是JWT最大的一個優點,也是JWT的核心內容。但是這也帶來了一個致命的問題:無法讓單獨某個用戶(token)過期或者失效,恰恰這又是一個非常非常常用的功能。
為了解決這個問題,網上提出一些方案:比如服務端設置一個blacklist或者配合redis來存儲token跟過期時間,每次請求到服務端解析JWT之後再次去blacklist或者redis里查詢一次看看是否已經註銷或者已經過期。
但是。。。這樣不就又把session請回來了嗎?這樣的方案跟我用sessionId去取session又有啥區別呢?所謂session不一定非要是asp.net mvc又或者springmvc自帶的session管理叫做session,任何帶有中心存儲功能能維持狀態的東西都是session,比如上面方案里的redis就是一個確確實實的session。
跨域?
因為傳統基於cookie的session機制sessionid存在cookie里,但是cookie不能跨域。但是JWT把token放在http的一個Authorization header上傳輸所以就可以輕鬆跨域。
但是sessionId就一定要存在cookie下嗎,sessionId同樣也可以存儲在localstorage里,然後請求的時候攜帶在http的某個header上,事實上cookie本身也是通過http的一個header傳輸的。這樣不就同樣可以跨域了嗎?sessionId跟token有區別嗎?個人認為沒有區別,都只是一個字元串而已。jwt怎麼在客戶端存儲放在哪個header上那麼sessionId就同樣可以。
數據更安全?
JWT的簽名也僅僅是仿篡改,把數據直接存儲在客戶端,儘管可以加密(JWT加密不是必須的),但是顯然談不上安全。如果是一串無意義的sessionId,她不存儲數據,又不能篡改,是不是更安全呢?
預防CSRF?
這個通跨域那個解釋一樣,sessionId不一定非要存儲在cookie中。
總結
為了預防被噴,再次強調下。今天寫下這不是為了噴JWT。JWT本身設計沒有什麼問題。真正無狀態的JWT確實可以帶來實實在在的好處,服務端水平擴容變的異常容易,再也不用擔心session複製的效率問題,也不用擔心session掛掉後整個集群全部無法正常工作的問題,確實是一個實實在在的好東西。 但是,好東西就一定大家都需要嗎?個人認為如果您所要開發的系統並發量不是那麼高,對水平擴展沒那麼高的需求,並且對用戶註銷是剛需,那麼請好好考慮下是否真的需要JWT。或許簡單的sessionId配合一個存儲工具比如redis,更能符合你的要求。如果你的程式並發高,用戶量大,實時在線人多,那麼使用真無狀態JWT是一個非常好的選擇。它能夠讓你從容的水平擴容,它能夠讓你省下不少session伺服器的費用,session服務不再是您系統的瓶頸。但是這樣的系統又有多少?