登陸驗證發展史有兩條主線。在服務部署方式層面,早期的Web服務系統簡單一般都是單系統,登陸的話就登陸這一個系統就好了,随着系統複雜性越來越高,一個大的系統往往由很多子系統組成,使用者使用這個大系統時不可能一個一個地單獨去登陸每個小系統,理想的是隻要登陸上了這個大系統或者其中的一個小系統,其它所有小系統都不需要再輸密碼什麼的登陸了,直接通路,是以登陸驗證就從單系統登陸演進為多系統的單點登陸。而在驗證方式層面,經曆了cookie認證->session認證->token認證->JWT的發展史。
以單系統登入舉例,先從驗證方式層面去看:
1.cookie驗證
簡單介紹一下cookie,cookie其實可以了解成存儲在浏覽器上的一種資料,裡面的資料格式是key-value鍵值對,資料本質上是儲存在終端某個本地檔案夾内。cookie不能跨域,浏覽器在通路某網站位址時,如果該網址的域有cookie儲存在浏覽器中,可以攜帶上對應cookie。谷歌浏覽器可以在位址欄中輸入chrome://settings/siteData?search=cookie來檢視目前浏覽器儲存的所有cookie,可以看到每個cookie是哪個域下面的。cookie 長度不超過 4KB ,否則會被截掉。每個域名下的cookie數量是有限制的,不同的浏覽器預設的數量限制可能不同,ie7之前是20個,ie7之後和Firefox一樣都是50個,chrome 和 Safari 沒有做硬性限制。
先貼一張cookie認證流程圖:

cookie認證的登入逾時一般是服務端設定cookie的有效時間。cookie認證特點最大的就是使用者資訊儲存在浏覽器,早期網際網路環境還比較安全,後來大家發現請求一旦被攔截,被拿到cookie後使用者資訊就洩露了,然後開始給cookie中的使用者資訊加密,但這也是治标不治本的方法,隻要使用者資訊是在浏覽器,終究是不安全的,最大的問題還是使用者資訊儲存在浏覽器的話,服務端不好管理受限太多。而且雖然大部分浏覽器都是預設開啟cookie存儲的,可是萬一誰設定了一下不存儲cookie呢,那豈不是登入不了系統了。還有就是浏覽器一次請求會把對應域中的所有cookie全部帶上,可以如果我隻需要帶有使用者資訊的cookie用來做驗證呢,其他cookie豈不是白白增加了請求包大小,也是帶寬的浪費。
可見cookie認證的缺點還是很多的,是以現在的Web應用一般都不用cookie認證了,後來出現了session認證。
2.session認證
session是存儲在服務端web容器中,也就是記憶體裡的東西,除非特意删除,生命周期和整個容器一緻。裡面存儲的資料格式也是鍵值對。當通路伺服器否個網頁的時候,會在伺服器端的記憶體裡開辟一塊記憶體,這塊記憶體就叫session,而這個記憶體是跟浏覽器關聯在一起的。這個浏覽器指的是浏覽器視窗,或者是浏覽器的子視窗,意思就是,隻允許目前這個session對應的浏覽器通路,就算是在同一個機器上新啟的浏覽器也是無法通路的。而另外一個浏覽器也需要記錄session的話,就會再啟一個屬于自己的session。
貼一張session認證的流程圖:
session認證和cookie認證最大的差別就是使用者資訊是儲存在服務端的,這就大大減少了安全風險,浏覽器有的隻是去服務端找對應session的sessionId。可以看出session認證還是依賴于cookie機制的,是以除了比cookie安全點外,cookie認證的其他缺點session幾乎也都有,比如浏覽器設定了不存儲cookie就登入不了了,還有一次請求攜帶多餘cookie等問題。session認證還有一個緻命的缺點,由于session是占用了伺服器記憶體,随着系統登入使用者越來越多,寶貴的記憶體資源就被會占用很多,這是最大的缺陷,但這一點一般在使用者不多的中小Web應用中不用理會。
session認證在叢集環境中也是有問題的,比如第一次通路是路由到了節點A然後登陸了,session建立在了節點A上,第二次通路路由到了節點B,雖然通過nginx的反向代理可以解決跨域問題,cookie是可以帶過去,但是節點B上面沒有session啊,難不成又要登入一遍?解決辦法就是利用Spring Session或者自己用Redis做session共享,或者直接在nginx上配置ip_hash,使得同一個ip始終路由到同一個節點。
雖然session仍然有缺陷,但是現在大部分的Web應用還在使用session認證。後來出現了token認證。
3.token認證
token又叫令牌,本質上就是一串無意義的字元串,一般放在請求頭裡,請求頭key一般是"Authorization",當然也可以和服務端約定好自定義成其他的,隻要服務端能夠從請求頭中拿到token就好了。token認證的出現最大的特點就是讓登陸認證不再依賴于cookie機制了,将token放在了請求頭裡,那些由于cookie機制導緻的弊端自然就沒有了。
貼一張token認證的流程圖:
token認證最大的好處就是支援移動裝置,因為移動端是不支援cookie的,自然cookie認證和session認證就用不了了。又不依賴cookie機制,使用者資訊又是服務端管理的,session認證看上去貌似沒什麼毛病了。但是慢慢的大家發現,怎麼每次請求都要用token去資料庫中查詢使用者資訊,這樣是不是資料庫的壓力太大了。
由于token是一串無意識的字元串,那能不能讓token變得有意義呢,如果token攜帶了使用者資訊,不就不需要每次請求都通路資料庫查了嘛,從token中直接解析出使用者資訊以及使用者登入狀态進行校驗,這就是後來的JWT。給浏覽器傳回的token是一串帶着使用者資訊的字元串,這一點就和最初的cookie認證比較像了。
4.JWT認證
JWT全稱是Java Web Token。其實就是特殊的token,了解起來就是攜帶着使用者資訊的token。是以JWT認證和token認證本質上是一樣的,隻不過token認證的使用者資訊是從資料庫裡查出來的,而JWT認證的使用者資訊是直接從這個特殊的token中解析出來的。
雖然差不多,還是貼一張JWT認證的流程圖:
上面都是單系統登入中的流程圖,随着系統複雜性越來越高,在多系統之間的單點登入也必不可少。單點登入都要有一個專門的認證中心伺服器,這和之前登入驗證都在應用伺服器上做不同。
cookie認證已經逐漸被淘汰了就不說了,基于token認證也是不适合應用于單點登入的,可以想象一下,單系統登入的token校驗工作是在服務端進行的,而如果應用于單點登入,那必然是要在認證中心進行。如果每次請求中攜帶的token,都要由應用伺服器去請求一遍認證中心去校驗token,那也太麻煩了,認證中心的壓力就太大了。是以一般說基于token的單點登入都是指基于JWT的單點登入,因為JWT的校驗是可以在各個應用伺服器上進行的,不需要每次都去請求認證中心。而剩下的基于session認證方式也對應的單點登入解決方案,因為基于session認證的單點登入有一個局部會話的概念,也不需要每次去認證中心校驗。
1.基于session認證的單點登入
基于session認證的單點登入中還有局部會話和全局會話兩個概念,即浏覽器和應用伺服器之間建立的是局部會話,和認證中心伺服器之間建立的是全局會話,也就是使用者登入成功之後,在認證中心伺服器和應用伺服器上都儲存着包含使用者資訊和登入狀态的session,浏覽器中的表現就是應用服務和認證中心服務的域下面都會有對應的存着sessionId的cookie。
比如我在feedbackuat單點登入成功之後,feedbackuat和ssouat都會有cookie。
先貼一下基于session認證的單點登入流程圖,這圖把我畫懵了:
需要注意的是,基于session認證的單點登入在叢集環境中是不需要做session共享或者nginx配置ip_hash的。叢集中各個節點就相當于各個子系統了,沒有局部會話session那就重新走一遍自動單點認證呗,反正不需要使用者手動再輸一遍密碼就好了。
為了驗證這一點我用nginx配置了一個項目的叢集環境,這個項目是內建了單點登入的,把ip_hash注釋掉了,并且增加了兩個響應頭辨別一下路由到了哪台伺服器:
然後重新整理頁面看看是不是即使路由到了不同的伺服器,也不需要單點登入:
就重新整理了一次,不同的請求路由到了不同的節點,但是不需要重新登入。
java實作基于session認證的單點登入需要兩個jar包,cas-client-core和cas-server-core,即cas用戶端和服務端,cas用戶端就是圖中的伺服器A和B,cas服務端就是認證中心。這裡主要看一下cas-client-core這個jar包,從中捋出部分源碼對應一下圖中的流程。
在AuthenticationFilter的doFilter()方法中,服務端校驗如果局部會話和ticket都沒有,則将浏覽器重定向到單點登入位址:
需要注意的是,上圖中拿ticket隻是嘗試拿,方法字首有safe,因為ticket是認證中心的登入接口傳回的,而認證中心的登入接口是需要開發的,自然各種各樣的方式傳回ticket給用戶端,是以cas-client-core包中是不會有拿ticket然後去校驗然後建立本地局部會話這個過程封裝的,這個過程需要我們自己寫,自定義一個過濾器實作它的AbstractCasFilter,然後在自定義的doFilter()方法中實作這段邏輯:
這是自定義的過濾器,這裡面拿ticket和AuthenticationFilter中拿ticket的方法是一樣的,這是因為在認證中心使用的也是cas預設的放ticket的方式,也就是作為url參數,參數key就是"ticket":
2.基于JWT認證的單點登入
其實單點登入的過程都差不多,就是認證方式的差別,了解了基于session認證的單點登入後,也很好了解基于JWT認證的單點登入。
jwt的token校驗都是在應用伺服器上。