XXL-SSO 是一個分佈式單點登陸框架。只須要登陸一次就能夠訪問全部相互信任的應用系統。 擁有"輕量級、分佈式、跨域、Cookie+Token均支持、Web+APP均支持"等特性。現已開放源代碼,開箱即用。web
- 一、簡潔:API直觀簡潔,可快速上手
- 二、輕量級:環境依賴小,部署與接入成本較低
- 三、單點登陸:只須要登陸一次就能夠訪問全部相互信任的應用系統
- 四、分佈式:接入SSO認證中心的應用,支持分佈式部署
- 五、HA:Server端與Client端,均支持集羣部署,提升系統可用性
- 六、跨域:支持跨域應用接入SSO認證中心
- 七、Cookie+Token均支持:支持基於Cookie和基於Token兩種接入方式,並均提供Sample項目
- 八、Web+APP均支持:支持Web和APP接入
- 九、實時性:系統登錄、註銷狀態,所有Server與Client端實時共享
- 十、CS結構:基於CS結構,包括Server"認證中心"與Client"受保護應用"
- 十一、記住密碼:未記住密碼時,關閉瀏覽器則登陸態失效;記住密碼時,支持登陸態自動延期,在自定義延期時間的基礎上,原則上能夠無限延期
- 十二、路徑排除:支持自定義多個排除路徑,支持Ant表達式,用於排除SSO客戶端不須要過濾的路徑
- xxl-sso-server:中央認證服務,支持集羣redis
- xxl-sso-core:Client端依賴spring
- xxl-sso-samples:單點登錄Client端接入示例項目數據庫
- xxl-sso-web-sample-springboot:基於Cookie接入方式,供用戶瀏覽器訪問,springboot版本跨域
- xxl-sso-token-sample-springboot:基於Token接入方式,經常使用於沒法使用Cookie的場景使用,如APP、Cookie被禁用等, springboot版本瀏覽器
導入ideaspringboot
先啓動xxl-sso-servercookie
啓動以前先看配置文件有redis鏈接信息session
因此先啓動本地redis服務,但沒發現redis的密碼配置,密碼配置寫在哪裏呢,咱們先啓動項目看看架構
若是你本地沒有配置redis密碼,則正常啓動,配置了則啓動報錯,我本地redis沒有配置密碼因此能夠正常啓動
咱們先從config開始斷點調試
F8進入
咱們就能夠發現password在這裏配置
接下來:修改Host文件:域名方式訪問認證中心,模擬跨域與線上真實環境
### 在host文件中添加如下內容
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"
一、SSO認證中心地址: http://xxlssoserver.com:8080/xxl-sso-server
二、Client01應用地址: http://xxlssoclient1.com:8084/xxl-sso-token-sample-springboot/
三、Client02應用地址: http://xxlssoclient2.com:8085/xxl-sso-token-sample-springboot/
啓動:xxl-sso-web-sample-springboot
配置文件信息
### xxl-sso xxl.sso.server=http://xxlssoserver.com:8080/xxl-sso-server xxl.sso.logout.path=/logout xxl-sso.excluded.paths= xxl.sso.redis.address=redis://127.0.0.1:6379
說明客戶端會重定向到認證受權中心進行登陸
redis也是鏈接同一個redis
爲何客戶端也要集成redis呢?後面源碼分析就知道了
啓動客戶端兩次,分別改成不一樣端口,模擬,發現修改配置文件,項目自動重啓,因此注意要刪除熱部署的jar包
<!-- devtools --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>provided</scope> <optional>true</optional> </dependency>
1.訪問這個客戶端,會自動重定向到server端進行登陸
2.點擊登陸,server端又跳轉到client後面帶sessionId參數
3.再訪問第二個客戶端,發現能夠免登陸
思考問題:
訪問客戶端的時候,如何自動重定向到認證受權中心server端實現登陸的?
過濾器,過濾請求,若是當前沒有獲取到用戶的會話信息,會自動重定向跳轉到認證受權中心進行登陸。
因此斷點調試 核心依賴jar包中的XxlSsoWebFilter
因此找到這個類在doFilter中斷點調試
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // make url String servletPath = req.getServletPath(); // 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; } } }
訪問:http://xxlssoclient1.com:8084/xxl-sso-web-sample-springboot/
1.先從Cookie中獲取當前的CooikeId
2.若是用戶沒有登陸的狀況下,重定向到認證受權中心進行登陸
3.在認證受權中心進行登陸成功以後返回原來地址(重定向地址)
在WebController中打斷點
@RequestMapping(Conf.SSO_LOGIN) public String login(Model model, HttpServletRequest request, HttpServletResponse response) { // login check XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response); if (xxlUser != null) { // success redirect String redirectUrl = request.getParameter(Conf.REDIRECT_URL); if (redirectUrl!=null && redirectUrl.trim().length()>0) { String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request); String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;; return "redirect:" + redirectUrlFinal; } else { return "redirect:/"; } }
// login check
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
跳轉到登陸界面
F12,查看提交表單url
因此在這裏打斷點
@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<UserInfo> 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"; } // 一、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.setExpireMinite(SsoLoginStore.getRedisExpireMinite()); xxlUser.setExpireFreshTime(System.currentTimeMillis()); // 二、make session id String sessionId = SsoSessionIdHelper.makeSessionId(xxlUser); // 三、login, store storeKey + cookie sessionId SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem); // 四、return, redirect 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:/"; } }
發現這裏是寫死了的,能夠本身修改成查數據庫
@Override public ReturnT<UserInfo> findUser(String username, String password) { if (username==null || username.trim().length()==0) { return new ReturnT<UserInfo>(ReturnT.FAIL_CODE, "Please input username."); } if (password==null || password.trim().length()==0) { return new ReturnT<UserInfo>(ReturnT.FAIL_CODE, "Please input password."); }
經過用戶信息建立sessionId
public static String makeSessionId(XxlSsoUser xxlSsoUser){ String sessionId = xxlSsoUser.getUserid().concat("_").concat(xxlSsoUser.getVersion()); return sessionId; }
登陸時
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); }
key爲sessionId,value爲用戶信息在redis中存一份
public static void put(String storeKey, XxlSsoUser xxlUser) { String redisKey = redisKey(storeKey); JedisUtil.setObjectValue(redisKey, xxlUser, redisExpireMinite * 60); // minite to second } private static String redisKey(String sessionId){ return Conf.SSO_SESSIONID.concat("#").concat(sessionId); }
認證受權登陸成功以後,在認證受權系統域名下(server端)存放對應的cookie信息
認證受權系統回調到子系統中傳遞xxl-ssso-sessionid,子系統域名下尚未對應的cookie信息
回調到子系統的時候,會被xxlssoFilter攔截
cookie信息會在客戶端域名下存一份,這樣能夠保證認證受權系統和子系統雙方Cookie信息同步
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; }
在redis中查詢對應的sessionId信息,因此前面爲何client端也要集成redis的緣由解決了。
public static XxlSsoUser loginCheck(String sessionId){ String storeKey = SsoSessionIdHelper.parseStoreKey(sessionId); if (storeKey == null) { return null; } XxlSsoUser xxlUser = SsoLoginStore.get(storeKey); if (xxlUser != null) { String version = SsoSessionIdHelper.parseVersion(sessionId); if (xxlUser.getVersion().equals(version)) { // After the expiration time has passed half, Auto refresh if ((System.currentTimeMillis() - xxlUser.getExpireFreshTime()) > xxlUser.getExpireMinite()/2) { xxlUser.setExpireFreshTime(System.currentTimeMillis()); //在redis裏面也存一份 SsoLoginStore.put(storeKey, xxlUser); } return xxlUser; } } return null; }
public static void put(String storeKey, XxlSsoUser xxlUser) { String redisKey = redisKey(storeKey); JedisUtil.setObjectValue(redisKey, xxlUser, redisExpireMinite * 60); // minite to second } private static String redisKey(String sessionId){ return Conf.SSO_SESSIONID.concat("#").concat(sessionId); }
登陸成功
在第一個ssoclient系統在ssoserver端登陸了以後,第二個ssoclient系統登陸的話,會重定向到認證受權系統進行登陸,由於認證受權系統有對應的Cookie信息,會直接把認證受權中心第一個ssoclient登陸的cookie信息回調給第二個ssoclient系統。
訪問:http://xxlssoclient1.com:8085/xxl-sso-web-sample-springboot
也會走filter攔截,把當前會話信息也會保存在本地一份
直接免登陸
- 用戶於Client端應用訪問受限資源時,將會自動 redirect 到 SSO Server 進入統一登陸界面
- 用戶登陸成功以後將會爲用戶分配 SSO SessionId 並 redirect 返回來源Client端應用,同時附帶分配的 SSO SessionId
- 在Client端的SSO Filter裏驗證 SSO SessionId 無誤,將 SSO SessionId 寫入到用戶瀏覽器Client端域名下 cookie 中
- SSO Filter驗證 SSO SessionId 經過,受限資源請求放行
版權@須臾之餘https://my.oschina.net/u/3995125
本文參考螞蟻課堂:http://www.mayikt.com