閱讀收獲
??1. 了解單點登錄實現原理
??2. 掌握快速使用xxl-sso接入單點登錄功能
一、早期的多系統登錄解決方案
- 單系統登錄解決方案的核心是cookie,cookie攜帶會話id在瀏覽器與服務器之間維護會話狀態。但cookie是有限制的,這個限制就是cookie的域(通常對應網站的域名),瀏覽器發送http請求時會自動攜帶與該域匹配的cookie,而不是所有cookie
- 既然這樣,為什么不將web應用群中所有子系統的域名統一在一個頂級域名下,例如“*.baidu.com”,然后將它們的cookie域設置為“baidu.com”,這種做法理論上是可以的,甚至早期很多多系統登錄就采用這種同域名共享cookie的方式。
- 共享cookie的方式存在眾多局限。
- 應用群域名得統一
- 應用群各系統使用的技術(至少是web服務器)要相同,不然cookie的key值(Tomcat為JSESSIONID)不同,無法維持會話,共享cookie的方式是無法實現跨語言技術平臺登錄的,比如JAVA、php、.net系統之間
- cookie本身不安全。
- 因此,我們需要一種全新的登錄方式來實現多系統應用群的登錄,這就是單點登錄
二、什么是單點登錄
- 單點登錄英文全稱Single Sign On,簡稱SSO。
- 指在多系統應用群中登錄一個系統,便可在其他所有系統中得到授權而無需再次登錄,包括單點登錄與單點注銷兩部分
三、為什么需要單點登錄
- web系統早已從久遠的單系統發展成為如今由多系統組成的應用群,面對如此眾多的系統,用戶難道要一個一個登錄、然后一個一個注銷嗎?
- web系統由單系統發展成多系統組成的應用群,復雜性應該由系統內部承擔,而不是用戶。無論web系統內部多么復雜,對用戶而言,都是一個統一的整體,也就是說,用戶訪問web系統的整個應用群與訪問單個系統一樣,登錄/注銷只要一次就夠了
四、單點登錄原理
4.1 登錄
sso需要一個獨立的認證中心,只有認證中心能接受用戶的用戶名密碼等安全信息,其他系統不提供登錄入口,只接受認證中心的間接授權。
間接授權通過令牌實現,sso認證中心驗證用戶的用戶名密碼沒問題,創建授權令牌,在接下來的跳轉過程中,授權令牌作為參數發送給各個子系統,子系統拿到令牌,即得到了授權,可以借此創建局部會話,局部會話登錄方式與單系統的登錄方式相同。
這個過程,也就是單點登錄的原理,用下圖說明
- 用戶訪問系統1的受保護資源,系統1發現用戶未登錄,跳轉至sso認證中心,并將自己的地址作為參數
- sso認證中心發現用戶未登錄,將用戶引導至登錄頁面(帶系統1地址)
- 用戶輸入用戶名密碼提交登錄申請
- sso認證中心校驗用戶信息,創建用戶與sso認證中心之間的會話,稱為全局會話,同時創建授權令牌
- sso認證中心帶著令牌跳轉到最初的請求地址(系統1)
- 系統1拿到令牌,去sso認證中心校驗令牌是否有效
- sso認證中心校驗令牌,返回有效,注冊系統1
- 系統1使用該令牌創建與用戶的會話,稱為局部會話,返回受保護資源
- 用戶訪問系統2的受保護資源
- 系統2發現用戶未登錄,跳轉至sso認證中心,并將自己的地址作為參數
- sso認證中心發現用戶已登錄,跳轉回系統2的地址,并附上令牌
- 系統2拿到令牌,去sso認證中心校驗令牌是否有效
- sso認證中心校驗令牌,返回有效,注冊系統2
- 系統2使用該令牌創建與用戶的局部會話,返回受保護資源
用戶登錄成功之后,會與sso認證中心及訪問的子系統建立會話,用戶與sso認證中心建立的會話稱為全局會話,用戶與各個子系統建立的會話稱為局部會話,局部會話建立之后,用戶訪問子系統受保護資源將不再通過sso認證中心,全局會話與局部會話有如下約束關系
- 局部會話存在,全局會話一定存在
- 全局會話存在,局部會話不一定存在
- 全局會話銷毀,局部會話必須銷毀
4.2 注銷
在一個子系統中注銷,所有子系統的會話都將被銷毀,用下面的圖來說明
sso認證中心一直監聽全局會話的狀態,一旦全局會話銷毀,監聽器將通知所有注冊系統執行注銷操作
下面對上圖簡要說明
- 用戶向系統1發起注銷請求
- 系統1根據用戶與系統1建立的會話id拿到令牌,向sso認證中心發起注銷請求
- sso認證中心校驗令牌有效,銷毀全局會話,同時取出所有用此令牌注冊的系統地址
- sso認證中心向所有注冊系統發起注銷請求
- 各注冊系統接收sso認證中心的注銷請求,銷毀局部會話
- sso認證中心引導用戶至登錄頁面
五、快速接入SSO
5.1 xxl-sso特性
- 簡潔:API直觀簡潔,可快速上手
- 輕量級:環境依賴小,部署與接入成本較低
- 單點登錄:只需要登錄一次就可以訪問所有相互信任的應用系統
- 分布式:接入SSO認證中心的應用,支持分布式部署
- HA:Server端與Client端,均支持集群部署,提高系統可用性
- 跨域:支持跨域應用接入SSO認證中心
- Cookie+Token均支持:支持基于Cookie和基于Token兩種接入方式,并均提供Sample項目
- Web+App均支持:支持Web和APP接入
- 實時性:系統登陸、注銷狀態,全部Server與Client端實時共享
- CS結構:基于CS結構,包括Server"認證中心"與Client"受保護應用"
- 記住密碼:未記住密碼時,關閉瀏覽器則登錄態失效;記住密碼時,支持登錄態自動延期,在自定義延期時間的基礎上,原則上可以無限延期
- 路徑排除:支持自定義多個排除路徑,支持Ant表達式,用于排除SSO客戶端不需要過濾的路徑
5.2 環境
- JDK:1.7+
- redis:4.0+
5.3 下載xxl-sso源碼
源碼倉庫地址 |
Release Download |
github.com/xuxueli/xxl… |
Download |
gitee.com/xuxueli0323… |
Download |
5.4 文檔地址
- 中文文檔
5.5 項目結構說明
- xxl-sso-server:中央認證服務,支持集群
- xxl-sso-core:Client端依賴
- xxl-sso-samples:單點登陸Client端接入示例項目
- xxl-sso-web-sample-springboot:基于Cookie接入方式,供用戶瀏覽器訪問,springboot版本
- xxl-sso-token-sample-springboot:基于Token接入方式,常用于無法使用Cookie的場景使用,如APP、Cookie被禁用等,springboot版本
復制代碼
5.6 基于Token方式部署
- 由于前后端分離開發的模式較多,這里只介紹基于Token方式部署,在一些無法使用Cookie的場景下,可使用該方式,如需要Cookie查看基于cookie方式部署
5.6.1 認證中心(SSO Server)搭建
- 項目名:xxl-sso-server
- 配置文件位置:application.properties
### redis 地址: 如 "{ip}"、"{ip}:{port}"、"{redis/rediss}://xxl-sso:{password}@{ip}:{port:6379}/{db}";多地址逗號分隔
xxl.sso.redis.address=redis://127.0.0.1:6379
### 登錄態有效期窗口,默認24H,當登錄態有效期窗口過半時,自動順延一個周期
xxl.sso.redis.expire.minute=1440
復制代碼
5.6.2 單點登陸Client端搭建
- 項目名:xxl-sso-token-sample-springboot
- maven依賴
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-sso-core</artifactId>
<version>${最新穩定版}</version>
</dependency>
復制代碼
- 配置文件:application.properties
##### SSO Server認證中心地址(推薦以域名方式配置認證中心)
xxl.sso.server=http://xxlssoserver.com:8080/xxl-sso-server
##### 注銷登陸path,值為Client端應用的相對路徑
xxl.sso.logout.path=/logout
##### 路徑排除Path,允許設置多個,且支持Ant表達式。用于排除SSO客戶端不需要過濾的路徑
xxl-sso.excluded.paths=
### redis // redis address, like "{ip}"、"{ip}:{port}"、"{redis/rediss}://xxl-sso:{password}@{ip}:{port:6379}/{db}";Multiple "," separated
xxl.sso.redis.address=redis://xxl-sso:password@127.0.0.1:6379/0
xxl.sso.redis.address=redis://127.0.0.1:6379
復制代碼
- 配置 XxlSsoTokenFilter
package com.xxl.sso.sample.config;
import com.xxl.sso.core.conf.Conf;
import com.xxl.sso.core.filter.XxlSsoTokenFilter;
import com.xxl.sso.core.util.JedisUtil;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author xuxueli 2018-11-15
*/
@Configuration
public class XxlSsoConfig implements DisposableBean {
@Value("${xxl.sso.server}")
private String xxlSsoServer;
@Value("${xxl.sso.logout.path}")
private String xxlSsoLogoutPath;
@Value("${xxl.sso.redis.address}")
private String xxlSsoRedisAddress;
@Value("${xxl-sso.excluded.paths}")
private String xxlSsoExcludedPaths;
@Bean
public FilterRegistrationBean xxlSsoFilterRegistration() {
// xxl-sso, redis init
JedisUtil.init(xxlSsoRedisAddress);
// xxl-sso, filter init
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setName("XxlSsoWebFilter");
registration.setOrder(1);
registration.addUrlPatterns("/*");
//token使用的是XxlSsoTokenFilter
registration.setFilter(new XxlSsoTokenFilter());
registration.addInitParameter(Conf.SSO_SERVER, xxlSsoServer);
registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoLogoutPath);
registration.addInitParameter(Conf.SSO_EXCLUDED_PATHS, xxlSsoExcludedPaths);
return registration;
}
@Override
public void destroy() throws Exception {
// xxl-sso, redis close
JedisUtil.close();
}
}
復制代碼
5.6.3 驗證 (模擬請求 Token 方式接入SSO的接口)
- 修改Host文件:域名方式訪問認證中心,模擬跨域與線上真實環境
### 在host文件中添加以下內容0
127.0.0.1 xxlssoserver.com
127.0.0.1 xxlssoclient1.com
127.0.0.1 xxlssoclient2.com
復制代碼
- 分別運行 "xxl-sso-server" 與 "xxl-sso-token-sample-springboot"
- 認證中心搭建成功后,默認為Token方式登陸提供API接口:
- 1、登陸接口:/app/login參數:POST參數username:賬號password:賬號響應:JSON格式code:200 表示成功、其他失敗msg:錯誤提示data: 登陸用戶的 sso sessionid
- 2、注銷接口:/app/logout參數:POST參數sessionId:登陸用戶的 sso sessionid響應:JSON格式code:200 表示成功、其他失敗msg:錯誤提示
- 3、登陸狀態校驗接口:/app/logincheck參數:POST參數sessionId:登陸用戶的 sso sessionid響應:JSON格式code:200 表示成功、其他失敗msg:錯誤提示data:登陸用戶信息userid:用戶IDusername:用戶名
- idea的http client測試如下:
##client1
POST http://xxlssoclient1.com:8082/xxl-sso-token-sample-springboot/
Content-Type: application/x-www-form-urlencoded
xxl_sso_sessionid: 1000_bac4555c627e4233b6da7d3f9b91aff5
###
##client2
http://xxlssoclient2.com:8082/xxl-sso-token-sample-springboot/
Content-Type: application/x-www-form-urlencoded
xxl_sso_sessionid: 1000_bac4555c627e4233b6da7d3f9b91aff5
###
##校驗
POST http://xxlssoserver.com:8080/xxl-sso-server/app/logincheck
Content-Type: application/x-www-form-urlencoded
sessionId=1000_9df8ee645d7f435fb5f76c9223dfe6e8
###
##注銷
POST http://xxlssoserver.com:8080/xxl-sso-server/app/logout
Content-Type: application/x-www-form-urlencoded
sessionId=1000_0ec8e28158da4241926314e0b3b9b834
###
#登錄sso
POST http://xxlssoserver.com:8080/xxl-sso-server/app/login
Content-Type: application/x-www-form-urlencoded
username=user&password=123456
復制代碼
- SSO登錄/注銷流程驗證:
- 可參考測試用例 :com.xxl.app.sample.test.TokenClientTest
- 正常情況下,登錄流程如下:
- 1、獲取用戶輸入的賬號密碼后,請求SSO Server的登錄接口,獲取用戶 sso sessionid ;(參考代碼:TokenClientTest.loginTest)
- 2、登陸成功后,獲取到 sso sessionid ,需要主動存儲,后續請求時需要設置在 Header參數 中
- 3、此時,使用 sso sessionid 訪問受保護的 "Client01應用" 和 "Client02應用" 提供的接口,接口均正常返回(參考代碼:TokenClientTest.clientApiRequestTest)
- 正常情況下,注銷流程如下:
- 1、請求SSO Server的注銷接口,注銷登陸憑證 sso sessionid ;(參考代碼:TokenClientTest.logoutTest)
- 2、注銷成功后,sso sessionid 將會全局失效
- 3、此時,使用 sso sessionid 訪問受保護的 "Client01應用" 和 "Client02應用" 提供的接口,接口請求將會被攔截,提示未登錄并返回狀態碼 501(參考代碼:TokenClientTest.clientApiRequestTest)
六、總體設計
6.1 架構圖
6.2 功能定位
XXL-SSO 是一個分布式單點登錄框架。只需要登錄一次就可以訪問所有相互信任的應用系統。
借助 XXL-SSO,可以快速實現分布式系統單點登錄。
6.3 核心概念
概念 |
說明 |
SSO Server |
中央認證服務,支持集群 |
SSO Client |
接入SSO認證中心的Client應用 |
SSO SessionId |
登錄用戶會話ID,SSO 登錄成功為用戶自動分配 |
SSO User |
登錄用戶信息,與 SSO SessionId 相對應 |
6.4 登錄流程剖析
- 用戶于Client端應用訪問受限資源時,將會自動 redirect 到 SSO Server 進入統一登錄界面
- 用戶登錄成功之后將會為用戶分配 SSO SessionId 并 redirect 返回來源Client端應用,同時附帶分配的 SSO SessionId
- 在Client端的SSO Filter里驗證 SSO SessionId 無誤,將 SSO SessionId 寫入到用戶瀏覽器Client端域名下 cookie 中
- SSO Filter驗證 SSO SessionId 通過,受限資源請求放行
6.5 注銷流程剖析
- 用戶與Client端應用請求注銷Path時,將會 redirect 到 SSO Server 自動銷毀全局 SSO SessionId,實現全局銷毀
- 然后,訪問接入SSO保護的任意Client端應用時,SSO Filter 均會攔截請求并 redirect 到 SSO Server 的統一登錄界面
6.6 基于Cookie,相關概念
- 登錄憑證存儲:登錄成功后,用戶登錄憑證被自動存儲在瀏覽器Cookie中
- Client端校驗登錄狀態:通過校驗請求Cookie中的是否包含用戶登錄憑證判斷
- 系統角色模型:
- SSO Server:認證中心,提供用戶登錄、注銷以及登錄狀態校驗等功能
- Client應用:受SSO保護的Client端Web應用,為用戶瀏覽器訪問提供服務
- 用戶:發起請求的用戶,使用瀏覽器訪問
6.7 基于Token,相關概念
- 登錄憑證存儲:登錄成功后,獲取到登錄憑證(xxl_sso_sessionid=xxx),需要主動存儲,如存儲在 localStorage、Sqlite 中
- Client端校驗登錄狀態:通過校驗請求 Header參數 中的是否包含用戶登錄憑證(xxl_sso_sessionid=xxx)判斷;因此,發送請求時需要在 Header參數 中設置登陸憑證
- 系統角色模型:
- SSO Server:認證中心,提供用戶登錄、注銷以及登錄狀態校驗等功能
- Client應用:受SSO保護的Client端Web應用,為用戶請求提供接口服務
- 用戶:發起請求的用戶,如使用Android、IOS、桌面客戶端等請求訪問
6.8 未登錄狀態請求處理
基于Cookie,未登錄狀態請求:
- 頁面請求:redirect 到SSO Server登錄界面
- JSON請求:返回未登錄的JSON格式響應數據
- 數據格式:
- code:501 錯誤碼
- msg:sso not login.
- 數據格式:
基于Token,未登錄狀態請求:
- 返回未登錄的JSON格式響應數據
- 數據格式:
- code:501 錯誤碼
- msg:sso not login.
- 數據格式:
6.9 登錄態自動延期
支持自定義登錄態有效期窗口,默認24H,當登錄態有效期窗口過半時,自動順延一個周期。
6.10 記住密碼
未記住密碼時,關閉瀏覽器則登錄態失效;記住密碼時,登錄態自動延期,在自定義延期時間的基礎上,原則上可以無限延期。
6.11 路徑排除
自定義路徑排除Path,允許設置多個,且支持Ant表達式。用于排除SSO客戶端不需要過濾的路徑。