XXL-SSO源码解读
一. 概述
XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。拥有”轻量级、分布式、跨域、Cookie+Token均支持、Web+APP均支持”等特性。现已开放源代码,开箱即用。
二.特性
- 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客户端不需要过滤的路径
三. sso-server 登录
3.1 sso-server 登录流程
sso 登录的 sessionId 是存在 redis 中,解决了分布式登录的问题。
1.首页请求 http://xxlssoserver.com:8080/xxl-sso-server 后台会请求到 WebController的 index方法
2.校验是否已登录,已登录跳转到首页,未登录,跳转到 login 页面,即http://xxlssoserver.com:8080/xxl-sso-server/login
3.填写登录名,密码登录,请求WebController的 doLogin方法,首先会判断是否是记住密码,若是记住密码,则 cookie 有效期是int 的最大值,单位为秒
登录成功,则跳转到首页页面
4. sessionId 是 userId 和一个随机生成的 uuid,每次登录都会重新生成一个 sessionId;
那么之前的 sessionId 怎么失效呢,有两种情况:
第一种是主动点击退出按钮,退出接口logout,会清除 redis 中的对应的 sessionId, 并设置 cookie 中的maxAge 为0,即让当前 cookie 立即失效
第二种是 sessionId 设置在 redis 中是有过期时间的,默认为24h, 登录后,长时间,不操作,redis 中的 sessionId过期后也需要重新登录的
loginCheck 里有一段代码也很重要,就是主动把 redis 中 sessionId 的过期时间续期的代码,这段逻辑是 xxlUser 中有个expireFreshTime 属性,每次 loginCheck 时,会校续期时间是否少于定义的一半时间,是的话,就主动续期
/** * login check * * @param sessionId * @return */ 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.getExpireMinute()/2) { xxlUser.setExpireFreshTime(System.currentTimeMillis()); SsoLoginStore.put(storeKey, xxlUser); } return xxlUser; } } return null; }
5. cookie 的保存,maxAge 是 cookie 的生命的周期
/** * 保存 * * @param response * @param key * @param value * @param ifRemember */ public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) { //如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化, // 即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。 //如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效, // 关闭窗口后该Cookie即失效。maxAge为负数的Cookie,为临时性Cookie,不会被持久化,不会被写到Cookie文件中。 // Cookie信息保存在浏览器内存中,因此关闭浏览器该 Cookie就消失了。Cookie默认的maxAge值为-1 int age = ifRemember?COOKIE_MAX_AGE:-1; set(response, key, value, null, COOKIE_PATH, age, true); }
/**
* 保存
*
* @param response
* @param key
* @param value
* @param maxAge
*/
private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
Cookie cookie = new Cookie(key, value);
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setPath(path);
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(isHttpOnly);
response.addCookie(cookie);
}
7. 退出登录接口
删除 redis 中的 sessionId, 并设置 cookie 失效
/** * Logout * * @param request * @param redirectAttributes * @return */ @RequestMapping(Conf.SSO_LOGOUT) public String logout(HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) { // logout SsoWebLoginHelper.logout(request, response);
redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL)); return "redirect:/login"; } /** * client logout * * @param request * @param response */ public static void logout(HttpServletRequest request, HttpServletResponse response) { String cookieSessionId = CookieUtil.getValue(request, Conf.SSO_SESSIONID); if (cookieSessionId==null) { return; } String storeKey = SsoSessionIdHelper.parseStoreKey(cookieSessionId); if (storeKey != null) {
//删除 redis 中的 sessionId SsoLoginStore.remove(storeKey); }
//cookie 设置失效,即 maxAge 设置为0 CookieUtil.remove(request, response, Conf.SSO_SESSIONID); }
四. sso-client 登录/退出
4.1登录流程图
clinet 登录主要是
XxlSsoConfig 主要是初始化redis 示例,即注册一个过滤器XxlSsoWebFilter
XxlSsoWebFilter是client 客户登录校验的逻辑
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; } } } // logout path check if (logoutPath!=null && logoutPath.trim().length()>0 && logoutPath.equals(servletPath)) { // remove cookie SsoWebLoginHelper.removeSessionIdByCookie(req, res); // redirect logout String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT); res.sendRedirect(logoutPageUrl); return; } // valid login user, cookie + redirect
//校验 sessionId 是否存在于 redis 中 XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res); // 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 sessionId
//重定向到 sso-server 的登录页,登录成功后,跳转到当前请求的路径地址
//loginPageUrl= http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN) + "?" + Conf.REDIRECT_URL + "=" + link; res.sendRedirect(loginPageUrl); return; } } // ser sso user request.setAttribute(Conf.SSO_USER, xxlUser); // already login, allow chain.doFilter(request, response); return; }
client1登录成功后,直接访问 client2无需再次登录,直接访问成功,原因就是client1登录成功后,sessionId 已经存在于 redis 中了,此时client2拿到当前的 cookie 请求即可校验通过
4.2 退出流程图
XxlSsoWebFilter中的退出逻辑比较简单,重定向到 logout 接口 , logout 接口删除 redis 中的 sessionId 后,重定向到 sso-server 的登录页面
xxl-sso 已支持 token ,但还未支持 jwt, 介绍一篇 jwt 的文章也很好,https://blog.csdn.net/weixin_45070175/article/details/118559272
作者:guanbin —— 纵码万里千山
出处:https://www.cnblogs.com/guanbin-529/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。