手把手教你學會 基於Spring-Boot,JWT的單點登入

FleyX發表於2019-06-26

塞爾達,林克

  最近我們組要給負責的一個管理系統 A 整合另外一個系統 B,為了讓使用者使用更加便捷,避免多個系統重複登入,希望能夠達到這樣的效果——使用者只需登入一次就能夠在這兩個系統中進行操作。很明顯這就是**單點登入(Single Sign-On)**達到的效果,正好可以明目張膽的學一波單點登入知識。

本篇主要內容如下:

  • SSO 介紹
  • SSO 的幾種實現方式對比
  • 基於 JWT 的 spring boot 單點登入實戰

注意:   SSO 這個概念已經出現很久很久了,目前各種平臺都有非常成熟的實現,比如OpenSSOOpenAMKerberosCAS等,當然很多時候成熟意味著複雜。本文不討論那些成熟方案的使用,也不考慮 SSO 在 CS 應用中的使用。

什麼是 SSO

  單點點說就是:一次登入後可免登陸訪問其他的可信平臺。比如我們登入淘寶網後,再開啟天貓首頁可以發現已經是登入狀態了。SSO 是一種比較流行的服務於企業業務整合的一種解決方案。

如何實現 SSO

  我們都知道目前的 http 協議是無狀態的,也就是第一次請求和第二次請求是完全獨立,不相關的,但現實中我們的業務邏輯都是有狀態的,這樣就引入了 cookie-session 的機制來維護狀態,瀏覽器端儲存一個 sessionId,後臺儲存跟該 sessionId 相關的資料。每次向後臺發起請求時都攜帶此 sessionId 就能維持狀態了。然後就有了 cookie,瀏覽器在傳送請求時自動將 cookie 中的資料放到請求中,發給服務端,無需手動設定。

  然後我們可以考慮考慮實現 SSO 的核心是什麼?答案就是如何讓一個平臺 A 登入後,其他的平臺也能獲取到平臺 A 的登入資訊(在 cookie-session 機制中就是 sessionId)。

方案一 共享 cookie

  基於 cookie-session 機制的系統中,登入系統後會返回一個 sessionId 儲存在 cookie 中,如果我們能夠讓另外一個系統也能獲取到這個 cookie,不就獲取到憑證資訊了,無需再次登入。剛好瀏覽器的 cookie 可以實現這樣的效果(詳見web 跨域及 cookie 學習)。

  cookie 允許同域名(或者父子域名)的不同埠中共享 cookie,這點和 http 的同域策略不一樣(http 請求只要協議、域名、埠不完全相同便認為跨域)。因此只需將多個應用前臺頁面部署到相同的域名(或者父子域名),然後共享 session 便能夠實現單點登入。架構如下:

共享session架構

  上面方案顯而易見的限制就是不僅前臺頁面需要共享 cookie,後臺也需要共享 session(可以用jwt來幹掉 session,但是又會引入新的問題,這裡不展開).這個方案太簡單了,不作進一步說明。

方案二 基於回撥實現

  通過上文可以知道,要實現單點登入只需將使用者的身份憑證共享給各個系統,讓後臺知道現在是在訪問。就能實現一次登入,到處訪問的效果,實在是非常方便的。在 session 機制中是共享 sessionId,然後多個後臺使用同一個 session 源即可。這裡我們用一種新的基於 JWT 的 token 方式來實現,不瞭解 JWT 的可以看這篇:java-jwt 生成與校驗,簡單來說 jwt 可以攜帶無法篡改的資訊(一段篡改就會校驗失敗),所以我們可以將使用者 id 等非敏感資訊直接放到 jwt 中,幹掉了後臺的 session。然後我們要做的就是將 jwt 共享給各個平臺頁面即可。系統架構如下:

基於jwt的回撥

  此架構中,業務系統 A 和業務系統 B 之間不需要有任何聯絡,他們都只和 SSO 認證平臺打交道,因此可以任意部署,沒有同域的限制。你可能就要問了這樣要怎麼共享身份憑證(也就是 jwt 字串)?這裡就要通過 url 引數來進行騷操作了。文字總結來說是這樣的:jwt 存到認證平臺前端的 localStore(不一定是 localStore,cookie,sessionStore 都可以),然後業務平臺攜帶自己的回撥地址跳轉到認證中心的前臺,認證中心的前臺再將 ujwt 作為 url 引數,跳回到那個回撥地址上,這樣就完成了 jwt 的共享。

  文字很可能看不懂,下面是整個過程的路程圖:

流程圖

相信通過上面的流程圖你應該能大概看明白,jwt 是如何共享了的吧,還看不懂的繼續看下來,下面上一個 spring boot 實現的簡易 SSO 認證。主要有兩個系統:SSO 認證中心,系統 A(系統 A 換不同埠執行就是系統 A、B、C、D 了).

實戰

實現 SSO 認證中心

  spring boot 框架先搭起來,由於是簡易專案,除 spring boot web 基本依賴,只需要如下的額外依賴:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.4</version>
</dependency>
<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.7.0</version>
</dependency>
複製程式碼

完整的 POM 檔案,請到 github 上檢視.

後臺實現

  後臺做的事情並不多,只有以下 5 個方法:

  • /login : 登入成功後簽發一個 jwt token
    在 demo 中只是簡單對比使用者名稱密碼如果是一樣的認為登入成功,返回 token
  • /checkJwt : 檢查 jwt 的有效性
    檢查傳來的 jwt-token 是否有效,返回失效的 jwt 列表
  • /refreshjwt : 重新整理 jwt
    判斷該 jwt 是否快要過期,如果快要過期,生成一個新的 jwt 返回
  • /inValid : 讓某個 jwt 失效
    jwt 如何失效一直是一個比較麻煩的問題,各有利弊。本例中採用的是為每個 jwt 生成一個隨機的祕鑰 secret,將 jwt--secret 儲存到 redis 中,想要讓某個 jwt 失效,只需將該記錄在 redis 中刪除即可(這樣在解密時便無法獲取到 secret)。但是這樣讓無狀態的認證機制變成有狀態了(記錄了 jwt 和 secret 的對應關係)。

  總結來說 SSO 後臺主要只做了兩件事:驗證使用者名稱密碼返回 jwt;驗證 jwt 是否合法。具體程式碼檢視 github 上 sso 目錄下的程式碼。

前臺實現

  前臺的邏輯較為複雜,不是那麼容易理解,不明白的多看幾遍上面的流程圖。

  再次回到 SSO 的重點:分享登入狀態。要如何在前臺將登入狀態(在這裡就是 jwt 字串)分享出去呢?由於瀏覽器的限制,除了 cookie 外沒有直接共享資料的辦法。既然沒有直接共享,那肯定是有間接的辦法的!

  這個辦法就是回撥。系統 A 的前臺在跳轉到 SSO 的前臺時,將當前路徑作為 url 引數傳遞給 sso 前臺,sso 前臺在獲取到 jwt 後,再跳轉到系統 A 傳過來的 url 路徑上,並帶上 jwt 作為 url 引數。這就完成了 jwt 的一次共享,從 sso 共享到系統 A。

打個比方:你點了個外賣,別人要怎麼把外賣給你呢?顯然你會留下的地址,讓別人帶上飯送到這個地址,然後你就能享用美食了。這和 jwt 的傳遞非常相識了。

系統 A 說:我要 jwt,快把它送到http://localhost:8081/test1/這個地址上。

SSO 說:好嘞,這個地址是合法的可以送 jwt 過去,這就跳轉過去:http://localhost:8081/test1/?jwt=abcdefj.asdf.asdfasf

系統 A 說:不錯不錯,真香。

  要注意這裡有個坑就是:如果另外一個惡意系統 C 安裝相同的格式跳轉到 SSO,想要獲取 jwt,這顯然是不應該給它的。所以在回跳回去的時候要判斷一下這個回撥地址是不是合法的,能不能給 jwt 給它,可以向後臺請求判斷也可以在 sso 前臺直接寫死合法的地址。在 demo 是沒有這個判斷過程的。

實現業務系統

  業務系統程式碼非常簡單,主要是用了一個攔截器,攔截 http 請求,提取出 token 向 sso 認證中心驗證 token 是否有效,有效放行,否則返回錯誤給前端。太簡單也不貼程式碼了,到 github 上看看就明白了。

效果

  上面說了一大串都是原理了,其實這個難也就難在原理部分,程式碼實現並沒有那麼複雜。這裡就不貼程式碼了,有需要直接到 github 上看。

  這裡上幾個效果圖:

  • 系統 A 首次登陸系統
    系統A登陸gif

可以看到首次登陸是需要跳到 sso 認證中心輸入使用者名稱密碼進行登陸驗證的。登陸成功回跳後介面請求成功。

  • 將 A 的啟動埠改為 8082 後再次啟動,當作系統 B
    系統B登陸gif

可以看到這次是無需登陸的,跳到認證中心後就馬上跳回了,如果去掉 alert 一般是看不出跳轉過程的。

最後在任意一個系統登出,都會讓所有的系統推出登陸。

可以看到,在系統 A 登入系統後,系統 B,系統 C 都不再需要輸入使用者名稱密碼進行登入。如果速度足夠快甚至都注意不到調到 SSO 再跳回來的過程。

原始碼:github

本篇原創釋出於:www.tapme.top/blog/detail…

掃碼關注微信公眾號:FleyX 學習筆記,獲取更多幹貨

手把手教你學會 基於Spring-Boot,JWT的單點登入

相關文章