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