最近咱們組要給負責的一個管理系統 A 集成另一個系統 B,爲了讓用戶使用更加便捷,避免多個系統重複登陸,但願可以達到這樣的效果——用戶只需登陸一次就可以在這兩個系統中進行操做。很明顯這就是**單點登陸(Single Sign-On)**達到的效果,正好能夠明目張膽的學一波單點登陸知識。前端
本篇主要內容以下:java
注意: SSO 這個概念已經出現好久好久了,目前各類平臺都有很是成熟的實現,好比OpenSSO
,OpenAM
,Kerberos
,CAS
等,固然不少時候成熟意味着複雜。本文不討論那些成熟方案的使用,也不考慮 SSO 在 CS 應用中的使用。git
單點點說就是:一次登陸後可免登錄訪問其餘的可信平臺。好比咱們登陸淘寶網後,再打開天貓首頁能夠發現已是登陸狀態了。SSO 是一種比較流行的服務於企業業務整合的一種解決方案。github
咱們都知道目前的 http 協議是無狀態的,也就是第一次請求和第二次請求是徹底獨立,不相關的,但現實中咱們的業務邏輯都是有狀態的,這樣就引入了 cookie-session 的機制來維護狀態,瀏覽器端存儲一個 sessionId,後臺存儲跟該 sessionId 相關的數據。每次向後臺發起請求時都攜帶此 sessionId 就能維持狀態了。而後就有了 cookie,瀏覽器在發送請求時自動將 cookie 中的數據放到請求中,發給服務端,無需手動設置。web
而後咱們能夠考慮考慮實現 SSO 的核心是什麼?答案就是如何讓一個平臺 A 登陸後,其餘的平臺也能獲取到平臺 A 的登陸信息(在 cookie-session 機制中就是 sessionId)。redis
基於 cookie-session 機制的系統中,登陸系統後會返回一個 sessionId 存儲在 cookie 中,若是咱們可以讓另一個系統也能獲取到這個 cookie,不就獲取到憑證信息了,無需再次登陸。恰好瀏覽器的 cookie 能夠實現這樣的效果(詳見web 跨域及 cookie 學習)。spring
cookie 容許同域名(或者父子域名)的不一樣端口中共享 cookie,這點和 http 的同域策略不同(http 請求只要協議、域名、端口不徹底相同便認爲跨域)。所以只需將多個應用前臺頁面部署到相同的域名(或者父子域名),而後共享 session 便可以實現單點登陸。架構以下:json
上面方案顯而易見的限制就是不只前臺頁面須要共享 cookie,後臺也須要共享 session(能夠用jwt
來幹掉 session,可是又會引入新的問題,這裏不展開).這個方案太簡單了,不做進一步說明。跨域
經過上文能夠知道,要實現單點登陸只需將用戶的身份憑證共享給各個系統,讓後臺知道如今是誰
在訪問。就能實現一次登陸,處處訪問的效果,實在是很是方便的。在 session 機制中是共享 sessionId,而後多個後臺使用同一個 session 源便可。這裏咱們用一種新的基於 JWT 的 token 方式來實現,不瞭解 JWT 的能夠看這篇:java-jwt 生成與校驗,簡單來講 jwt 能夠攜帶沒法篡改的信息(一段篡改就會校驗失敗),因此咱們能夠將用戶 id 等非敏感信息直接放到 jwt 中,幹掉了後臺的 session。而後咱們要作的就是將 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 了).
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/checkJwt
: 檢查 jwt 的有效性/refreshjwt
: 刷新 jwt/inValid
: 讓某個 jwt 失效總結來講 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 上看。
這裏上幾個效果圖:
能夠看到首次登錄是須要跳到 sso 認證中心輸入用戶名密碼進行登錄驗證的。登錄成功回跳後接口請求成功。
能夠看到此次是無需登錄的,跳到認證中心後就立刻跳回了,若是去掉 alert 通常是看不出跳轉過程的。
最後在任意一個系統註銷,都會讓全部的系統推出登錄。
能夠看到,在系統 A 登陸系統後,系統 B,系統 C 都再也不須要輸入用戶名密碼進行登陸。若是速度足夠快甚至都注意不到調到 SSO 再跳回來的過程。
源碼:github
本篇原創發佈於:www.tapme.top/blog/detail…
掃碼關注微信公衆號:FleyX 學習筆記,獲取更多幹貨