天天看點

ticket+token+redis 實作sso單點登入防重放、防盜用、防篡改

作者:Java聯盟盟主

本篇速覽腦圖

ticket+token+redis 實作sso單點登入防重放、防盜用、防篡改

名詞約定

全局會話

在SSO登入頁面登入後,我們就認為建立起了全局會話

判定标志

SSO頁面的session存在且未過期

局部會話

在各個子系統,是否已經登入過,這個我們稱為局部會話

判定标志

子系統存在可行的token【未過期且有效】

ticket

SSO系統頒發給子系統的憑證,有此憑證且有效的話,表明SSO系統允許子系統去建立局部會話【生成token】

token

子系統的通路憑證,各個子系統的token是不一樣的,具體視業務而定,子系統也需要配置相應的攔截器來檢測token

SSO登入

當SSO登入頁面登入成功後,會存儲一份session,建立起會話,表示全局會話已存在 session我們這裡就不再過多贅述,想了解更多的話可以參考本專欄往期内容

使用者通路流程

直接通路子系統A分成了兩種情況:SSO已登入或未登入【全局會話是否存在】

我們先列出時序圖,後續分析起來對照時序圖會更清晰一點

ps:時序圖裡邊涉及到了一些代碼細節,不清晰的地方可以先跳過,後續講解代碼的時候再着重解讀

SSO已登入

ticket+token+redis 實作sso單點登入防重放、防盜用、防篡改
注意:這裡我們分析的是SSO已登入,也就是全局會話已存在的情況
  1. melo直接通路子系統A,此時會優先判斷局部會話是否存在且有效【token】
    1. 若有效則直接放行,沒有SSO什麼事情了,業務正常執行
    2. 若無效,說明局部會話不存在,此時去判斷全局會話
  2. 接下來會跳轉到SSO頁面,SSO頁面調用SSO服務接口,判斷全局會話是否存在,發現session存在且有效
現在全局會話校驗成功了,接下來的問題就是如何建立局部會話?
  1. 局部會話依賴于全局會話,是以需要全局會話去頒發ticket給子系統A【相當于一個授權的過程,允許子系統去登入】
  2. 子系統拿到ticket後,校驗是否是SSO頒發的且有效,有效則解析出ticket裡邊的憑證【比如學号】,然後根據學号在子系統A生成局部會話token,至此局部會話也建立成功了。

SSO未登入

SSO未登入的流程,跟上邊大體是相同的,隻不過在SSO頁面判斷全局會話不存在時【session為空】,此時需要跳轉到SSO登入頁面,登入成功生成session後 ,再去給子系統頒發ticket

ticket+token+redis 實作sso單點登入防重放、防盜用、防篡改

細節

SSO如何跳轉回子系統A

子系統A跳轉到SSO的時候,需要傳遞參數redirect_url,後續根據這個url就能跳轉回去

ticket如何傳遞給子系統A

一般是從SSO跳轉到子系統A的時候,拼接在位址欄後邊,比如melo.com?ticket=xxx

不同系統需要共用redis嗎?

不需要的,我們用遠端調用的方式,去調用各個系統的接口,各個系統接口内部,就能通路各自主機redis,而不需跨系統去通路其他主機的redis

安全優化

ticket如何防範被篡改?

ticket裡邊是有使用者憑證的,黑客如果篡改了ticket裡邊的使用者憑證,比如改成黑客自己的,那到子系統A登入的時候,登入的就是黑客的身份了。

此處melo的解決方法,其實是類似jwt,頒發ticket的時候,是用jwt的加密方式【結合數字簽名】

此處不清晰的同學,可以參考本專欄往期内容

隻要加密數字簽名的私鑰不洩露,黑客就沒辦法自行篡改憑證後,捏造出對應的數字簽名。

ticket如何防盜用?

ticket拼接在位址欄,安全風險還是蠻高的,如果黑客拿到我們的ticket,豈不是能直接去子系統A登入了?

此處melo的解決方法是:SSO頒發ticket的時候,擷取此時使用者的ip,采用JWT機制,并在payload裡邊綁定使用者的ip,到子系統A的時候,校驗ticket有效性的時候,再次擷取使用者ip,并解析出payload裡邊的ip,對照兩個ip是否一緻,一緻說明是同一個使用者。

此方法的缺點是:黑客能夠看到payload裡邊的ip,如果黑客僞造自己ip為payload裡邊的,這樣就能通過我們的校驗了。

ticket如何設定隻允許使用一次?

ticket如果可以被多次使用,也會帶來一定的風險。

此處melo的解決方法是:SSO頒發ticket的時候,就把ticket存儲在redis中,并設定1min過期

子系統A驗證ticket有效性的時候,遠端調用SSO的verify接口【接口的功能是:判斷SSO的redis中ticket是否已過期】,未過期則說明有效,并删除掉該ticket

  • 若已過期,則說明已失效了,需要重新頒發
  • 細心的同學也許會發現,遠端調用其實就解決了,不同系統需要共用redis的問題,如果是直接在子系統A去驗證的話,需要去通路SSO的redis【但由于防火牆問題,redis一般都是設定僅本機可通路的】
  • 而如果用遠端調用的話,是去調用sso的verify接口,此接口隻需要判斷本機上的redis是否存在ticket即可

防重放

比如我們有這樣一個接口,查詢支付訂單的接口,調用該接口時出于安全性,需要綁定一個簽名,簽名由訂單的場地id+時間段等資訊,末尾再加上通訊雙方約定好的私鑰 secret ,用md5加密生成sign,作為接口的簽名傳遞到服務端去校驗

服務端用同樣的資訊和規則,結合 secret ,用md5加密後,判斷兩個簽名是否一緻

雖然這樣解決了接口資訊篡改的問題,黑客無法自行僞造簽名來發請求,但如果黑客抓取到我們的一個合法請求後,其實是可以一直用這個合法請求,去瘋狂調用我們接口的~

什麼是重播攻擊

是以,重播攻擊就是:黑客攔截了我們的請求,獲得我們發給服務端的一個合法請求,然後重複地發送該合法請求,若該請求耗時過長,極端調用可能會搞崩我們的系統

注意 -- 跟幂等的差別

幂等是允許執行多次,隻不過執行多次後的結果依然是一樣的 而防重放,是不允許執行多次,從根頭上就遏制住了

如何防止重播攻擊?

原理其實很簡單,就是讓合法的請求,隻能被執行一次,保證每次請求的唯一性

時間戳 timestamp

我們認為一次HTTP請求從發出到到達伺服器的時間是不會超過60s的,當你發送一個請求時必須攜帶一個時間戳timestamp,假設值為20 。

當請求發送到伺服器之後,伺服器會取出伺服器的目前時間, 假設為 now =100, 很明顯 now -timestamp>60s,那麼伺服器就認為請求是不合法的。

防止時間戳被更改

一般情況下,黑客從抓包重放請求耗時遠遠超過了60s,但如果黑客修改了時間戳為目前時間,使得 now-timestamp < 60s 的話,似乎就能繞過我們的校驗了诶?

是以此時我們會結合上文的 防篡改 機制 , 具體流程如下:

  1. 用戶端對 傳輸的參數資訊【時間戳等】+約定好的secret 進行 md5 加密,得到sign,一并傳遞給服務端
  2. 服務端重複該過程,對 收到的參數資訊【時間戳等】+約定好的secret 進行 md5 加密,得到sign
  3. 判斷是否跟用戶端發送過來的sign一緻,若一緻則說明接口參數資訊沒有被篡改過

存在問題

如果黑客在60s再次發起請求的話,那還是防範不了的

随機數nonce

nonce的意思是僅一次有效的随機字元串,是以需要做到每次請求的 nonce 都不同,可以由時間戳來生成,結合 ip 位址等,進一步確定唯一性。

用戶端請求接口的時候,生成随機數,發送到服務端,服務端吧 nonce 存儲在redis裡邊,當重放請求到達時,驗證發現 nonce 已經存在,則認為請求是非法的

存在問題

要保證曆史全局唯一,有點麻煩,并且redis存儲那麼多,開銷有點大

時間戳+随機數

綜合來看,時間戳的問題是沒法防止60s内的攻擊,而随機數的問題在于要做到全局唯一,而且要存儲很多 nonce ,耗費空間大

是以我們其實可以結合兩者,具體流程如下:

  1. 用戶端生成時間戳和随機數,并且作為sign的加密參數,傳遞給服務端
  2. 服務端先判斷時間戳是否合法,若不合法,則直接傳回【省去存儲随機數的開銷】
  3. 若時間戳合法,再将 nonce 存儲到redis裡邊,并且設定 1min過期
注意這裡設定 1min過期 的好處在于,隻需要保證在這 1min内 ,nonce不會重複即可,這樣用戶端生成nonce也不用太過複雜

後續那些在60s内的攻擊,雖然能繞過時間戳校驗,但卻因為 nonce 的存在【1min過期,是以能防1min内的,且占用記憶體不會過多】,會被認為是非法的。

而60s以外的攻擊,在時間戳校驗就直接GG了。

  • 是以此方案,綜合解決了兩者的缺點:無法防止60s的攻擊,以及存儲開銷大
原文連結:https://juejin.cn/post/7160224863964102669