普通token登錄驗證和OAuth2.0應用場景(簡述,之後有空再詳寫)

之前2月底特地看了很多關於token機制和Oauth2.0的文章。由於3月初到現在每天都很忙,所以一直沒時間整理。今天其實也還是沒時間。不過現大概寫一些東西。後面有空在補上吧。之前忙着發現杯的作品提交材料,最近則是忙着Netty開發。

在正式介紹Token之前,先大致回顧一下Cookie和Session。(下面介紹跨度比較大,涉及內容不止Cookie和Session)

1. Cookie

​ Cookie一般是指客戶端瀏覽器的Cookie,別的地方有沒有Cookie我就不清楚了。Cookie可以用於保存一些非隱私的公用狀態數據。
​ 比如早期的前端頁面開發中,由於刷新頁面,JS代碼的變量就會失效,必須通過別的形式使一些變量能夠持久化。這時候可以考慮把變量存在Cookie中,使其持久化。當然,這也不是什麼最優的方式。

類似的形式,把參數保存在URL中,保存在公用的JS文件中(題外話,如果是Vue的話,在一些路由操作,多頁面需要公用的狀態參數,可以考慮使用VUEX)
​ 總之,Cookie就是一種保存非私密狀態數據的方案,作用於客戶端,往往只瀏覽器。由於Cookie不安全,不建議存儲私密數據。現在很多網站開發,也儘量避免使用Cookie了。

2. Session

​ Session同樣也是保存狀態的一種方式。不過Session存在於服務器。由於管理狀態的是服務器,那麼相對Cookie就安全許多了。比如你把你的賬號密碼都存在服務器中,反正服務器也不會隨便把你的賬號密碼發給其他人。但是Cookie是在你的瀏覽器的,假如存了賬號密碼,指不定就被人盜走了。

Session在單機環境比較實用,在集羣or分佈式環境就比較麻煩了。這裏先扯一會Cookie吧,你用不同瀏覽器訪問你的QQ郵箱啥的,每次都需要登錄,因爲是不同瀏覽器,Cookie在瀏覽器中存儲,那麼不同瀏覽器當然Cookie不共享了(不同瀏覽器對HTML、JS、CSS的實現和兼容也是有區別,IE出來捱打,哈哈)。

​ 爲什麼說Session在集羣or分佈式環境比較不方便呢?這裏再扯一會別的東西。啥是集羣,啥是分佈式?說的土一點,集羣就是同一個應用/服務,複製粘貼多個相同的去運行。分佈式就是不同服務放到不同服務器上。那爲什麼閒着慌有人要搞集羣or分佈式??集羣的話,你可以假設你其中一個掛了,那麼只要集羣中其他的相同服務還在,用戶就可以正常訪問了。分佈式的話,如果是提供相同服務的分佈式,一般是爲了容災處理,集羣能夠保證程序級別的容災(比如一個java程序掛了,同一臺服務器集羣的java程序還或者就ok了),而分佈式你一臺服務器掛了,我這臺服務器還在就ok了。不過一般分佈式不只是爲了容災,分佈式比較靈活,通常把不同服務包裝成一箇中臺放到不同服務器上運行,這些服務器加起來形成一個大服務,這樣你可以把這幾臺服務器叫作分佈式服務了。

​ 回到正題,Session在集羣中,雖然集羣提供相同的服務,但是畢竟是不同的程序,不同程序的類、方法、變量怎麼可能是同一個?要是你使用Session保存用戶的狀態(賬號、密碼啥的隱私數據),你第一次瀏覽器訪問 “張三.com”,你登錄了一次,服務器A中的服務1-java程序記錄了你的登錄信息,你刷新了一下,訪問到了服務器A的服務2-java程序,又要求你重新登錄一遍,因爲它沒有你的信息,只有服務1-java程序有。作爲用戶,你崩潰了嗎?趕緊投訴電話打起來。這裏你可能會困惑,爲什麼你訪問“張三.com”會訪問到不同的應用,不是一個域名對應一個服務器嗎?域名是標識了一個服務器沒錯,但是一個服務內的多個應用不都是這個服務器的,只要後臺使用Ribbon、Nginx等負載均衡、代理機制,就能使得用戶請求被分發到不同的應用程序上。

​ 集羣中,Session保存狀態可能出現以下情況:

(1)用戶 -> 瀏覽器 -> 用戶登錄請求 -> 服務器A -> 應用1 -> Session1記錄登錄信息 -> 用戶登錄成功,不用再登錄啦,恭喜你。

(2)用戶,哈哈,不用登錄了 -> 瀏覽器 -> 服務器A -> 負載均衡,轉發請求 -> 應用2,Session2找不到用戶信息 -> 抱歉你誰我不知道,你登錄一下。 -> 用戶,投訴走起。

​ Session要在集羣中同步也不是不行,可以通過第三者同步Session。比如你再開一個服務5,它就專門存Session,其他幾個服務1,2,3,4啥的,每次都從服務5那裏取Session,也可以乾脆把用戶登錄狀態都存在數據庫,每次都從數據庫讀取記錄,讀取到特定數據就表示用戶已經登錄。這裏就提供一點思路,如果真要同步集羣的Session,可以考慮成熟的開源框架Spring Session啥的。

3. Token

  • 前面提到Cookie,一般存儲非隱私數據,且保存在客戶端。

  • Session,可以存儲隱私數據,保存在服務器端(集羣、分佈式煩瑣)。

這裏,講token。首先,token也是用於保存一些數據的,它一般用於用戶的認證/鑑權

啥是認證,啥又是鑑權???

  • 認證,個人理解就是保證用戶是這個用戶。也就是現在操作微博的人是一個叫“張三”的,他的密碼是“隔壁老王”,這個用戶的信息,我後臺數據庫有存儲過,這個人確實存在。那麼他可以訪問“張三”的微博,可以寫說說。
  • 鑑權,個人理解就是保證這個用於有權限。也就是B站直播的普通直播觀看用戶“張三”,和UP主的房管“李四”的區別。張三隻能發發彈幕噴UP主,但是“李四”看了可來氣了,反手就是禁言"李四"一年。這裏李四用戶權限比張三高。(ps:實際中,鑑權不限於用戶之間,也可以是服務之間。比如現在有3臺服務器A、B、C,每個服務器上有不同的服務,服務器A有A-1,A-2,A-3,服務器B有B-1、B-2…,A-1可以訪問B-1,但是沒有權限訪問B-2,這也是一種鑑權。不過這個我覺得主要是OAuth2.0的應用範疇了。)

知道認證/鑑權後,我們回到Token。token憑啥本事做登錄鑑權??我們在設計token時,一般往裏面存用戶狀態信息,過期時間等參數。比如下面的數據結構

Token

{

	String	equipId;//標識設備
    String username;//標識用戶
    .... 其他信息
	TimeStamp TTL;// timeToLive,存活時間
}

使用上面token的方式如下流程:

(1)用戶登錄->輸入賬號密碼->HTTPS(HTTP無狀態,HTTPS多了SSL,較安全)-> 後臺服務器。

(2)後臺服務器-> 數據庫驗證是否存在該用戶->存在,則頒發token->用戶

(3)用戶->收到token,之後每次請求都需要攜帶token。->header添加字段攜帶token

(4)後臺->收到header有token的請求,檢驗token的信息,這個token沒過期,該username的用戶不需要再登錄,可以繼續訪問。

上面只是簡化版的說法,實際可以更復雜,比較忙我就不細講了。比如頒發token的時候,最好對數據進行對稱加密(防止被別人直接看到私密數據),然後進行數字簽名(防止數據被僞造,HTTPS也是主要乾的這個,但是HTTPS不只是防僞造,實際上也算是能加密信息了。)再發給用戶。瞭解HTTPS需要知道對稱加密、非對稱加密、CA(夠寫一篇文章了,有空再說吧)。

​ 使用了token,如果是瀏覽器,可以把token存放在cookie中,然後瀏覽器發出請求的時候,要麼發送cookie給服務器,要麼把token加到header發送給服務器。服務器不需要特定存儲token,只要驗證裏面的時間戳屬性是否過期就行。

​ 這裏,你可能有以下疑問:

  1. token的信息真的真的不用存在服務器中嗎?不怕僞造嗎
  2. 如果token的信息服務器“不存儲”,那麼怎麼驗證token的。
  3. 假如token的部分信息存在服務器的話,那麼用戶惡意用腳本註冊上千次,是不是服務器就要存上千次信息了??
  4. token被盜用怎麼辦?

​ 這裏一個一個解答:

  1. 前面提到服務器不需要特定存token,指的是不需要像Session那樣針對某個和自己連接的客戶端創建一個專門存儲用戶信息的對象(對象指的就是Session)。

    至於僞造問題,如果你token傳輸用的HTTPS,而且你爲了雙重保險,token還是經過了非對稱加密(數字簽名,HTTPS的話多一步CA認證->包裝成數字證書),那麼你的數據還能被僞造,只能說黑客技高一籌。

  2. 如果是簡單的服務,各種安全性啥的沒什麼考慮,確實token不需要特地存儲,只要用戶登錄操作需要後臺頒發token時,從數據庫驗證用戶賬號密碼正確就可以頒發token給用戶了。況且如果用了非對稱加密,本身這個token的解密密鑰只有服務器有,token也難以被僞造。

  3. 這個問題的話,我個人覺得光是靠token就不大行了。解決方案可以是使用Redis數據庫(之所以用Redis,一方面快,還有就是Redis單線程,任務處理就好像阻塞隊列一樣,不會出現多線程同步問題。)。

    具體可以這樣設計。使用username作爲key,value 爲Set類型,Set只存放3-5個值,每個值爲username.equipId,每次頒發Token的時候,先查看這個Set的大小,要是>=3,則直接使用裏面的equipId爲用戶簽發token,而不另外新建。

    另外,可以使用username.equipId爲key,val爲其他私密數據(比如token的密鑰等信息)的String類型存放 用戶的過期時間。一般網站不都有7天內免登錄嗎?可以設置這個String7天過期,前面提到的Set也是7天過期。這樣就可以做到7天內用戶免登錄+用戶最多3-5個設備能夠頒發token(具體別的細節不說)。token爲了安全性,最好失效時間短一點,比如1小時or半小時失效,那麼只要這個7天的String類型變量還在,就允許token刷新重新頒發(或者延用上次的equipId,看你實際業務)。

  4. 被盜用,那你做出標準的祈禱姿勢,希望黑客無法解密對稱加密,哈哈。不過如果token的存活時間短,黑客可能只能擁有你的token一個小時。被盜用,黑客可以直接用你的身份免登錄(當然後臺其實還是可以有對策的,這裏不細講了。)

​ 總之,使用了token,雖然需要每次都傳輸token,但是相對cookie安全,相對session不存在分佈式/集羣同步問題(因爲驗證都是通過數據庫,不同服務可能連接的同一個數據庫)。

OAuth2.0

​ OAuth2.0的概念我這裏不說了,文章不錯的有很多,這裏不細貼了。要是感興趣又懶得找文章的,可以去我github的筆記看看。其實這篇文章我本地MD筆記也有,以後再上傳到github的筆記上。

​ 這裏不細講OAuth2.0的使用啥的。我就說說個人理解的OAuth2.0的使用場景和Token應用場景。

  • Token:前面也能感覺到token被盜用很蛋疼。比如黑客拿到後,要是他也能續簽token,那你豈不是7天內(假如後臺免密登錄7天)賬號都被黑客控制了?
  • OAuth2.0在認證/授權中,有個很厲害的東西,叫作授權碼模式,這個要說也是一篇文章,不細講。你就只要知道里面有個core授權碼,它就厲害在可消費,也就是一個token只能對應一個core。假如黑客拿你的token去續簽,他獲得了一個新core。後面你要續簽了,你獲得的core和黑客獲取的不一樣,這個token失效。這時候你可能就發現異常了,於是修改了密碼(一般修改密碼,後臺需要讓token都失效,實現形式很多,也可以是一篇文章,不細講了。)

​ OAuth2.0如果要以之前簡單token的方式用於登錄認證,那麼OAuth2.0可以比簡單token更適合分佈式。前面簡單token機制,需要每個服務器的服務都編寫相同的token驗證,非常低效(當然不是真的所有實現都這樣,只不過簡單實現往往就是如此)。而OAuth2.0往往用於獨立的認證/鑑權服務,就好像前面說的Session同步需要多個服務去第三方獲取Session。這裏OAuth2.0把認證/鑑權獨立出來,其他服務要認證/鑑權全找它就好了。

​ 還有,OAuth2.0使用其實很頻繁,比如你網站用微信登錄,github登錄等第三方登錄,走的就是OAuth2.0.

簡單Token和OAuth2.0使用場景對比

​ 如果是多臺服務器分佈式服務,且服務多樣化,比如有3個以上的服務,那麼建議使用OAuth2.0,把認證/鑑權的工作獨立出來(這個認證/鑑權也可以是服務和服務之間的,而且個人覺得,OAuth2.0一般也就是用於服務和服務之間)。

​ 如果是隻有單種服務,或者只有2種服務,可以不考慮抽出OAuth2.0,用簡單的token機制就ok了。不過,個人覺得,簡單token機制往往用於客戶端和服務之間

​ 總結:

  • 分佈式多服務(3個以上),考慮OAuth2.0
  • 第三方登錄(第三方也可以是自己的服務),不用想了,OAuth2.0
  • 單個或2個服務,不需要服務器之間認證/鑑權,只要客戶端與服務器,簡單Token

最後說句題外話,token實現可以考慮使用JWT,一個成熟的token實現方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章