先問小夥伴們一個問題,登入難嗎?“登入有什麼難得?輸入使用者名和密碼,背景檢索出來,校驗一下不就行了。”凡是這樣回答的小夥伴,你明顯就是産品思維,登入看似簡單,使用者名和密碼,背景校驗一下,完事了。但是,登入這個過程涵蓋的知識點是非常多的,絕不是檢索資料,校驗一下這麼簡單的事。
那麼登入都要哪些實作方式呢?i最傳統的就要是Cookie-Session這種方式了,最早的登入方式都是這樣實作的。但是随着手機端、H5端的興起,前後端分離的模式越來越流行,基于Cookie-Session這種登入方式不是很友善,漸漸的JTW開始流行,現在大部分項目的登入方式都是基于JWT的了。那麼Cookie和JWT都是怎樣實作登入的呢?這兩種方式有什麼差別呢?我們在做登入的x時候該怎麼選擇呢?咱們先看看這兩種方式的原理。
Cookie方式
因為Http協定是無狀态的,我們背景的服務(如Tomcat)在接收到前端發送過來的Http請求時,是區分不出哪個請求是誰發出的,這和我們的登入功能是相違背的,登入的功能就是要區分每一個請求是由哪個使用者發出的,這就變成了有狀态,那怎麼辦呢?Cookie應運而生,Cookie是存儲在浏覽器端的,在Cookie中存儲的内容是鍵值對,也就是name-value。浏覽器在向背景發送請求的時候,會把Cookie放在請求頭中,傳送給背景的服務,背景的服務會從請求頭中取到Cookie,再從Cookie中取出鍵值對中jsessionid對應的值。這個jsessionid的值就是你這次會話的id,對應着服務端的一個session。
好了,到這裡session這個概念出來了,session是什麼呢?session是存儲在服務端的,每一個會話對應服務中的一個session。咱們可以把session了解為一個Map,它的key存儲的session的id,value存儲的東西就随便了,我們在寫程式時想存啥就存啥。它的key存儲的值就是Cookie中存儲的jsessionid的值,這樣,浏覽器發送請求到背景服務,背景才能根據Cookie中的jsessionid取到對應的session,再從session中取到之前存儲的狀态,如存儲在session中的登入狀态、使用者id等。Cookie-Session機制是通用的,所有的浏覽器都支援Cookie,就連最低端的IE都支援,你說他普遍不普遍。Session是後端容器必須支援的,如Tomcat,還有像其他的如Resin、jetty等。這些對開發人員都是透明的,無需過多關注。
Cookie-Session的由來給大家說完了,我們看看基于Cookie這種方式的登入流程,

- 使用者在浏覽器輸入使用者名、密碼,點選登入,發送請求到背景服務;
- 背景服務校驗使用者名、密碼,将登入狀态狀态和使用者id存儲在session中;
- 将session的id存儲在Cookie中,通過響應頭傳回到浏覽器;
- 當使用者點選其他功能時,向背景發送的請求中會自動帶上Cookie;
- 背景通過Cookie中的jsessionid找到對應的session,開發人員可從session中取出目前會話的登入狀态和使用者id。
基于Cookie-Session機制的登入實作方式的整體流程就是這個樣子。看上去很完美,但還是存在不少問題的,我們來看看這些問題。
分布式會話
上面的示例,我們的背景服務隻有一個,一個服務往往很難支撐服務,為了保障可靠性,最少都是部署兩個背景服務。但是當部署多個背景服務時,我們的session就會出現問題,看看下面的圖,
- 假如使用者登入的請求,配置設定到了背景服務1,背景服務1的session存了使用者的登入狀态和使用者id。
- 使用者在點選其他功能時,請求配置設定到了背景服務2,可是背景服務2的session并沒有存儲登入狀态和使用者id。
我們怎麼解決這個問題呢?其實也很簡單,第一,session集中管理,比如使用Redis;第二,所有的背景服務在擷取session時,統一從Redis中擷取。如下所示,
我們可以使用中間件Spring-Session和Redis就可以解決這個問題。
CORS
使用Cookie實作登入的另外一個問題就是跨域,現在往往都采用前後端分離的方式進行開發,在開發的過程中,前端和後端通常不在一個域下,由于浏覽器的同源政策,Cookie不能傳入到後端。至于同源政策,不明白的小夥伴可以問一下度娘,這裡不過多介紹了。要解決這個問題,在前端、後端都要進行設定,在我的另一篇文章《前後端分離|關于登入狀态那些事》中有詳細的介紹。總體歸納為:
- 後端設定CORS允許跨域的域名,并且
設定為true;withCredentials
- 前端在向後端發送請求時,也需要設定
;withCredentials = true
這樣,我們的Cookie就可以實作跨域了。不進行這些設定,Cookie跨域是不可能的,同源政策保證了我們Cookie的安全。
CSRF
CSRF,這個CORS是不一樣的,長的比較像,也比較容易混。CSRF往往和系統的安全扯上聯系,也是等保測試中比較重要的測試内容,它也是和Cookie有關的,大體的流程是這樣的,
- 使用者登入了A網站,并沒有退出;
- 此時,使用者又通路了B網站;
- 在B網站有個隐藏的請求,請求了A網站的一個重要的接口,比如:轉賬、支付等。
- 在請求A網站的同時,帶上了A網站的Cookie,是以一些危險的操作就成功了。
關于CSRF的攻防,在我前面的文章《CSRF的原理與防禦 | 你想不想來一次CSRF攻擊?》中有詳細的介紹。總之,使用Cookie實作登入是需要重點防範一下CSRF攻擊的。
JWT方式
近年來,由于手機端的興起,前後端分離開發方式的流行,JWT這種登入的實作方式悄然興起,那麼什麼是JWT呢?JWT是英文JSON Web Token的縮寫,它由3部分組成,
- header,一般情況下存儲兩個資訊,1類型,一般都是JWT;2加密算法,比如:HMAC、RSA等;
- payload,這裡就存儲登入的相關資訊了,比如:登入狀态、使用者id、過期時間等。
- signature,簽名,這個是将header、payload和密鑰的資訊做一次加密,背景在接收到JWT的時候,一定要驗簽,謹防JWT的僞造。
下面咱們看看JWT的登入實作,
我們看到整體的流程和Cookie的實作方式是一樣的,隻不過是沒有用到Cookie、Session。那麼它與Cookie-Session的差別是什麼呢?
- 登入狀态、使用者id并沒有存儲到session,而是存在JWT的payload裡,傳回給了前端。
- 在前端JWT不會自動存儲到Cookie中,前端開發人員要處理JWT的存儲問題,比如LocalStorage
- 再次發起請求,JWT不會自動放到請求頭中,需前端同學手動設定
- 後端從請求頭中取出JWT,驗簽通過後,拿到登入狀态、使用者id,不是從session中取
相比Cookie的方式,JWT的方式需要更多的開發工作量。那麼其他的問題存在嗎?我們一個一個看。
我們背景部署多個服務,會有分布式會話的問題嗎?
無論請求被配置設定到哪一個背景服務中,登入狀态和使用者id都是從JWT中取出來的,不會出現分布式會話的問題。我們在背景部署叢集的時候,根本不用care這個問題。
Cookie的跨域受到同源政策的保護,不經過特殊的設定,是不能夠跨域的。那麼JWT呢?JWT是前端同學手動在請求頭中設定的,如果向其他的域發送請求要注意,稍不注意,在請求的時候,調用了封裝的公共方法,就會把JWT發送給其他域的背景,前端的小夥伴要打起精神啊。
Cookie的方式,B通路A網站,會把A的Cookie帶上,進而造成了安全隐患。那麼JWT呢?JWT在前端存儲在A網站的域下,B在通路A網站時,是拿不到A網站的JWT的,那麼也不可能在請求頭中設定JWT,A網站的背景拿不到JWT,也不會做其他操作。是以,JWT可以很好的防止CSRF攻擊。
總結
通過前面我們對Cookie和JWT的分析,可以總結成如下的表格,
Cookie-Session | JWT | |
---|---|---|
工作量 | 浏覽器和容器天然支援 | 需要額外開發,有一定工作量 |
需要借助中間件 | 無需關心,登入資訊從JWT解出 | |
不支援跨域、需特殊設定 | 開發人員設定請求頭,可以跨域 | |
需特殊防範 | 無需防範,第三方拿不到JWT |
好了,Cookie和JWT的特點都總結出來了,大家在實作登入的時候,就各取所需吧。結合自己的項目,選擇适合自己項目的實作方式吧。