1. 程式人生 > >深度剖析分散式單點登入框架XXL-SSO

深度剖析分散式單點登入框架XXL-SSO

於2018年初,在github上建立XXL-SSO專案倉庫並提交第一個commit,隨之進行系統結構設計,UI選型,互動設計…… 於2018年初,在github上建立XXL-SSO專案倉庫並提交第一個commit,隨之進行系統結構設計,UI選型,互動設計…… 於2018-12-05,XXL-SSO參與"[2018年度最受歡迎中國開源軟體](https://www.oschina.net/project/top_cn_2018?sort=1)"評比,在當時已錄入的一萬多個國產開源專案中角逐,最終排名第55名。 於2019-01-23,XXL-SSO被評選上榜"[2018年度新增開源軟體排行榜之國產 TOP 50](https://www.oschina.net/news/103857/2018-osc-new-opensource-software-cn-top50)"評比,排名第8名。 至今,XXL-SSO已接入多家公司的線上產品線,接入場景如電商業務,O2O業務和核心中介軟體配置動態化等,截止2018-03-15為止,XXL-SSO已接入的公司包括不限於: 1. 湖南創發科技 2. 深圳龍華科技有限公司 3. 摩根國際 4. 印記雲 ## 一、簡介 ### 1.1 概述 XXL-SSO 是一個分散式單點登入框架。只需要登入一次就可以訪問所有相互信任的應用系統。 擁有"輕量級、分散式、跨域、Cookie+Token均支援、Web+APP均支援"等特性。現已開放原始碼,可以做到開箱即用。 ### 1.2 特性 - 1、簡潔:API直觀簡潔,可快速上手; - 2、輕量級:環境依賴小,部署與接入成本較低; - 3、單點登入:只需要登入一次就可以訪問所有相互信任的應用系統。 - 4、分散式:接入SSO認證中心的應用,支援分散式部署; - 5、HA:Server端與Client端,均支援叢集部署,提高系統可用性; - 6、跨域:支援跨域應用接入SSO認證中心; - 7、Cookie+Token均支援:支援基於Cookie和基於Token兩種接入方式,並均提供Sample專案; - 8、Web+APP均支援:支援Web和APP接入; - 9、實時性:系統登陸、登出狀態,全部Server與Client端實時共享; - 10、CS結構:基於CS結構,包括Server"認證中心"與Client"受保護應用"; - 11、記住密碼:未記住密碼時,關閉瀏覽器則登入態失效;記住密碼時,支援登入態自動延期,在自定義延期時間的基礎上,原則上可以無限延期; - 12、路徑排除:支援自定義多個排除路徑,支援Ant表示式。用於排除SSO客戶端不需要過濾的路徑; ### 1.3 下載 #### 文件地址 - [中文文件](http://www.xuxueli.com/xxl-sso/) #### 原始碼倉庫地址 | 原始碼倉庫地址 | Release Download | | ------------------------------------------------------------ | ---------------------------------------------------------- | | [https://github.com/xuxueli/xxl-sso](https://github.com/xuxueli/xxl-sso) | [Download](https://github.com/xuxueli/xxl-sso/releases) | | [https://gitee.com/xuxueli0323/xxl-sso](https://gitee.com/xuxueli0323/xxl-sso) | [Download](https://gitee.com/xuxueli0323/xxl-sso/releases) | #### 技術交流 - [社群交流](http://www.xuxueli.com/page/community.html) ### 1.4 環境 - JDK:1.7+******** - Redis:4.0+ - Mysql:5.6+ ## 二、快速入門(基於Cookie) ### 2.1 系統資料庫初始化 ### 2.2 原始碼編譯 ``` - 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版本 ``` ### 2.3 部署 "認證中心(SSO Server)" ```properties 專案名:xxl-sso-server ``` **配置說明** 配置檔案位置:application.properties ```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.minite=1440 ``` **Redis 密碼配置** 在`xxl-sso-core`專案中`com.xxl.sso.core.util`包下的`JedisUtil`類中大約`85行`設定密碼,如若redis沒有設定密碼,則不需要配置此處。 ```java for (int i = 0; i < addressArr.length; i++) { JedisShardInfo jedisShardInfo = new JedisShardInfo(addressArr[i]); jedisShardInfo.setPassword("******");#新增密碼 jedisShardInfos.add(jedisShardInfo); } ``` ### 2.4 部署 "單點登陸Client端接入示例專案" ```properties 專案名:xxl-sso-web-sample-springboot ``` **maven依賴** ```xml com.xuxueli xxl-sso-core ${最新穩定版} ``` **配置 XxlSsoFilter** 參考程式碼:com.xxl.sso.sample.config.XxlSsoConfig ```java @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("/*"); registration.setFilter(new XxlSsoWebFilter()); registration.addInitParameter(Conf.SSO_SERVER, xxlSsoServer); registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoLogoutPath); return registration; } ``` **配置說明** 配置檔案位置:application.properties ```properties …… ### xxl-sso (CLient端SSO配置) ##### SSO Server認證中心地址(推薦以域名方式配置認證中心,本機可參考章節"2.5"修改host檔案配置域名指向) 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:[email protected]:6379/0 ``` 路徑過濾:在`xxl-sso-core`專案中`com.xxl.sso.core.filer`包下的`XxlSsoWebFilter`類中大約`54行` ```java // excluded path check if (excludedPaths!=null && excludedPaths.trim().length()>
0) { for (String excludedPath:excludedPaths.split(",")) { String uriPattern = excludedPath.trim(); // 支援ANT表示式 if (antPathMatcher.match(uriPattern, servletPath)) { // excluded path, allow chain.doFilter(request, response); return; } } } ``` 判斷登入重定向:在`xxl-sso-core`專案中`com.xxl.sso.core.filer`包下的`XxlSsoWebFilter`類中大約`82行` ```java // valid login fail if (xxlUser == null) { String header = req.getHeader("content-type"); boolean isJson= header!=null && header.contains("json"); if (isJson) { // json msg res.setContentType("application/json;charset=utf-8"); res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}"); return; } else { // total link String link = req.getRequestURL().toString(); // redirect logout String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN) + "?" + Conf.REDIRECT_URL + "=" + link; res.sendRedirect(loginPageUrl); return; } } ``` ### 2.5 驗證 - 環境準備:啟動Redis、初始化Mysql表資料; - 修改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-web-sample-springboot" ``` 1、SSO認證中心地址: http://xxlssoserver.com:8080/xxl-sso-server 2、Client01應用地址: http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/ 3、Client02應用地址: http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/ ``` - SSO登入/登出流程驗證 ``` 正常情況下,登入流程如下: 1、訪問 "Client01應用地址" ,將會自動 redirect 到 "SSO認證中心地址" 登入介面; 2、成功登入後,將會自動 redirect 返回到 "Client01應用地址",並切換為已登入狀態; 3、此時,訪問 "Client02應用地址",不需登陸將會自動切換為已登入狀態; 正常情況下,登出流程如下: 1、訪問 "Client01應用地址" 配置的 "登出登陸path",將會自動 redirect 到 "SSO認證中心地址" 並自動登出登陸狀態; 2、此時,訪問 "Client02應用地址",也將會自動登出登陸狀態; ``` ![](https://img2020.cnblogs.com/blog/1639299/202007/1639299-20200713192751016-609627217.png) ![](https://img2020.cnblogs.com/blog/1639299/202007/1639299-20200713192807779-643454914.png)```` ![](https://img2020.cnblogs.com/blog/1639299/202007/1639299-20200713192821340-1288016060.png) ### 2.5 登入原始碼分析 ```java @RequestMapping("/doLogin") public String doLogin(HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes, String username, String password, String ifRemember) { boolean ifRem = (ifRemember!=null&&"on".equals(ifRemember))?true:false;//判斷是否記住密碼 // valid login ReturnT result = userService.findUser(username, password);//校驗密碼是否正確 if (result.getCode() != ReturnT.SUCCESS_CODE) { redirectAttributes.addAttribute("errorMsg", result.getMsg()); redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL)); return "redirect:/login"; } // 1、make xxl-sso user 根據不同規則設定使用者資訊 XxlSsoUser xxlUser = new XxlSsoUser(); xxlUser.setUserid(String.valueOf(result.getData().getUserid())); xxlUser.setUsername(result.getData().getUsername()); xxlUser.setVersion(UUID.randomUUID().toString().replaceAll("-", "")); xxlUser.setExpireMinute(SsoLoginStore.getRedisExpireMinute()); xxlUser.setExpireFreshTime(System.currentTimeMillis()); // 2、make session id 根據使用者資訊生成sessionId String sessionId = SsoSessionIdHelper.makeSessionId(xxlUser); // 3、login, store storeKey + cookie sessionId 將sessionId&使用者資訊存入Reids SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem); // 4、return, redirect sessionId 重定向到子系統(並傳遞xxl_sso_sessionid) String redirectUrl = request.getParameter(Conf.REDIRECT_URL); if (redirectUrl!=null && redirectUrl.trim().length()>0) { String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId; return "redirect:" + redirectUrlFinal; } else { return "redirect:/"; } } ``` **校驗密碼是否正確** ```java @Override public ReturnT findUser(String username, String password) { if (username==null || username.trim().length()==0) { return new ReturnT(ReturnT.FAIL_CODE, "Please input username."); } if (password==null || password.trim().length()==0) { return new ReturnT(ReturnT.FAIL_CODE, "Please input password."); } // mock user for (UserInfo mockUser: mockUserList) { if (mockUser.getUsername().equals(username) && mockUser.getPassword().equals(password)) { return new ReturnT(mockUser); } } return new ReturnT(ReturnT.FAIL_CODE, "username or password is invalid."); } ``` **根據使用者資訊生成sessionId** ```java public static String makeSessionId(XxlSsoUser xxlSsoUser){ String sessionId = xxlSsoUser.getUserid().concat("_").concat(xxlSsoUser.getVersion()); return sessionId; } ``` **將sessionId&使用者資訊存入Reids** ```java public static void login(HttpServletResponse response, String sessionId, XxlSsoUser xxlUser, boolean ifRemember) { String storeKey = SsoSessionIdHelper.parseStoreKey(sessionId); if (storeKey == null) { throw new RuntimeException("parseStoreKey Fail, sessionId:" + sessionId); } SsoLoginStore.put(storeKey, xxlUser); CookieUtil.set(response, Conf.SSO_SESSIONID, sessionId, ifRemember);//在認證授權系統下存放對應cookie資訊 } ``` ```java public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){ String cookieSessionId = CookieUtil.getValue(request, Conf.SSO_SESSIONID); // cookie user XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId); if (xxlUser != null) { return xxlUser; } // redirect user // remove old cookie SsoWebLoginHelper.removeSessionIdByCookie(request, response); // set new cookie String paramSessionId = request.getParameter(Conf.SSO_SESSIONID); xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId); if (xxlUser != null) { CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false); // expire when browser close (client cookie) return xxlUser; } return null; } ``` ## 三、快速入門(基於Token) ### 3.1 "認證中心(SSO Server)" 搭建 "認證中心" 搭建成功後,預設為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:使用者ID - username:使用者名稱 ### 2.2 部署 "單點登陸Client端接入示例專案" (Token方式) ```properties 專案名:xxl-sso-token-sample-springboot ``` ### 2.3 驗證 (模擬請求 Token 方式接入SSO的介面) - 環境準備:啟動Redis、初始化Mysql表資料; - 修改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" ``` 1、SSO認證中心地址: http://xxlssoserver.com:8080/xxl-sso-server 2、Client01應用地址: http://xxlssoclient1.com:8082/xxl-sso-token-sample-springboot/ 3、Client02應用地址: http://xxlssoclient2.com:8082/xxl-sso-token-sample-springboot/ ``` - 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) ``` ## 四、總體設計 ### 4.1 架構圖 ![](https://img2020.cnblogs.com/blog/1639299/202007/1639299-20200713192620082-207921965.png) ### 4.2 功能定位 XXL-SSO 是一個分散式單點登入框架。只需要登入一次就可以訪問所有相互信任的應用系統。 藉助 XXL-SSO,可以快速實現分散式系統單點登入。 ### 4.3 核心概念 | 概念 | 說明 | | ------------- | -------------------------------------------- | | SSO Server | 中央認證服務,支援叢集; | | SSO Client | 接入SSO認證中心的Client應用; | | SSO SessionId | 登入使用者會話ID,SSO 登入成功為使用者自動分配; | | SSO User | 登入使用者資訊,與 SSO SessionId 相對應; | ### 4.4 登入流程剖析 - 使用者於Client端應用訪問受限資源時,將會自動 redirect 到 SSO Server 進入統一登入介面。 - 使用者登入成功之後將會為使用者分配 SSO SessionId 並 redirect 返回來源Client端應用,同時附帶分配的 SSO SessionId。 - 在Client端的SSO Filter裡驗證 SSO SessionId 無誤,將 SSO SessionId 寫入到使用者瀏覽器Client端域名下 cookie 中。 - SSO Filter驗證 SSO SessionId 通過,受限資源請求放行; ### 4.5 登出流程剖析 - 使用者與Client端應用請求登出Path時,將會 redirect 到 SSO Server 自動銷燬全域性 SSO SessionId,實現全域性銷燬; - 然後,訪問接入SSO保護的任意Client端應用時,SSO Filter 均會攔截請求並 redirect 到 SSO Server 的統一登入介面。 ### 4.6 基於Cookie,相關概念 - 登陸憑證儲存:登陸成功後,使用者登陸憑證被自動儲存在瀏覽器Cookie中; - Client端校驗登陸狀態:通過校驗請求Cookie中的是否包含使用者登入憑證判斷; - 系統角色模型: - SSO Server:認證中心,提供使用者登陸、登出以及登陸狀態校驗等功能。 - Client應用:受SSO保護的Client端Web應用,為使用者瀏覽器訪問提供服務; - 使用者:發起請求的使用者,使用瀏覽器訪問。 ### 4.7 基於Token,相關概念 - 登陸憑證儲存:登陸成功後,獲取到登入憑證(xxl_sso_sessionid=xxx),需要主動儲存,如儲存在 localStorage、Sqlite 中; - Client端校驗登陸狀態:通過校驗請求 Header引數 中的是否包含使用者登入憑證(xxl_sso_sessionid=xxx)判斷;因此,傳送請求時需要在 Header引數 中設定登陸憑證; - 系統角色模型: - SSO Server:認證中心,提供使用者登陸、登出以及登陸狀態校驗等功能。 - Client應用:受SSO保護的Client端Web應用,為使用者請求提供介面服務; - 使用者:發起請求的使用者,如使用Android、IOS、桌面客戶端等請求訪問。 ### 4.8 未登入狀態請求處理 基於Cookie,未登入狀態請求: - 頁面請求:redirect 到SSO Server登入介面; - JSON請求:返回未登入的JSON格式響應資料 - 資料格式: - code:501 錯誤碼 - msg:sso not login. 基於Token,未登入狀態請求: - 返回未登入的JSON格式響應資料 - 資料格式: - code:501 錯誤碼 - msg:sso not login. ### 4.9 登入態自動延期 支援自定義登入態有效期視窗,預設24H,當登入態有效期視窗過半時,自動順延一個週期; ### 4.10 記住密碼 未記住密碼時,關閉瀏覽器則登入態失效;記住密碼時,登入態自動延期,在自定義延期時間的基礎上,原則上可以無限延期; ### 4.11 路徑排除 自定義路徑排除Path,允許設定多個,且支援Ant表示式。用於排除SSO客戶端不需要過濾的路徑 *** ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzEzLzE3MzQ2ZDMzYTY0ZTBkNzI?x-oss-process=image/form