目录
- 1 项目启动
- 修改Host文件
- 运行路径
- SSO登录/注销流程验证
- 2 分析
- 登录流程
单点登录原理及简单实现:https://www.cnblogs.com/ywlaker/p/6113927.html
xxl-sso是一款基于redis轻量级分布式高可用的SSO实现组件,支持web端(Cookie实现)和app端(Token实现)两种方式,两种方式的验证都是用Filter实现的
项目地址 gitee :https://gitee.com/xuxueli0323/xxl-sso
xxl-sso/ xxl-sso-samples 里面是基于 cookie 和 token 的调用示例,单点登陆Client端接入示例项目
1 项目启动
修改Host文件
修改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-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登录/注销流程验证
正常情况下,登录流程如下:
- 访问 “Client01应用地址” ,将会自动 redirect 到 “SSO认证中心地址” 登录界面
- 成功登录后,将会自动 redirect 返回到 “Client01应用地址”,并切换为已登录状态
- 此时,访问 “Client02应用地址”,不需登陆将会自动切换为已登录状态
正常情况下,注销流程如下:
- 访问 “Client01应用地址” 配置的 “注销登陆path”,将会自动 redirect 到 “SSO认证中心地址” 并自动注销登陆状态
- 此时,访问 “Client02应用地址”,也将会自动注销登陆状态
2 分析
登录流程
首次访问 client1
的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
,进入过滤器
① 在client项目中配置了core的过滤器XxlSsoWebFilter(判断cookie或者请求携带参数中的id是否与redis中一致),首次访问client1时,过滤器判断cookie、请求携带参数、redis均为null,请求重定向到认证中心server携带参数(login?redirect_url=)
在client项目中配置了core的过滤器XxlSsoWebFilter
@Bean public FilterRegistrationBean xxlSsoFilterRegistration() { JedisUtil.init(xxlSsoRedisAddress); FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setName("XxlSsoWebFilter"); registration.setOrder(1); registration.addUrlPatterns("/*"); registration.setFilter(new XxlSsoWebFilter()); //配置了 core 里的过滤器 registration.addInitParameter(Conf.SSO_SERVER, xxlSsoServer); registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoLogoutPath); registration.addInitParameter(Conf.SSO_EXCLUDED_PATHS, xxlSsoExcludedPaths); return registration; }
core 里的过滤器如下
此处是配置文件中配置的参数### xxl-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= xxl.sso.redis.address=redis://127.0.0.1:6379
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// 当前访问的路径为 servletPath="/"
String servletPath = req.getServletPath();
// excluded path check ———— excludedPaths="" 跳过
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 ———— logoutPath="/logout" 跳过
if (logoutPath!=null
&& logoutPath.trim().length()>0
&& logoutPath.equals(servletPath)) {
// remove cookie
SsoWebLoginHelper.removeSessionIdByCookie(req, res);
// redirect logout
//注销时,请求重定向 http://xxlssoserver.com:8080/xxl-sso-server/logout
String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
res.sendRedirect(logoutPageUrl);
return;
}
// valid login user, cookie + redirect 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断,此时两种key都是null ———— 跳过
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
String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
+ "?" + Conf.REDIRECT_URL + "=" + link;
//首次登录 loginPageUrl = http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
res.sendRedirect(loginPageUrl);
return;
}
}
// ser sso user
request.setAttribute(Conf.SSO_USER, xxlUser);
chain.doFilter(request, response);
return;
}
判断redis中是否有user
public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){ String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid"); // cookie user 此处是通过cookieId获取 redis 中的user //首次访问client、server,cookieSessionId为null xxlUser为null //二次认证中心登录后跳回访问client1,所以cookieSessionId为null xxlUser为null //三次client2访问发现携带cookie XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId); if (xxlUser != null) { return xxlUser; } // remove old cookie //版本不一致,移除session,设置新的 SsoWebLoginHelper.removeSessionIdByCookie(request, response); // set new cookie 此处是通过请求参数的 Id 获取 redis 中的user //首次访问client、server,paramSessionId cookieSessionId xxlUser 都为 null //二次认证中心登录后跳回访问client1,携带参数 ?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19 String paramSessionId = request.getParameter("xxl_sso_sessionid"); //从redis获取user与sessionid进行匹配。匹配成功则设置 cookie 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; }
② 进入server的WebController的login方法,发现cookie、请求携带参数、redis均为null 。跳转到登录页,输入账号密码,提交server的dologin登录方法,将user存放cookie 存放redis,请求重定向到client1携带参数(?xxl_sso_sessionid=)
③ 再次来到过滤器,此时cookie为null,判断请求携带参数与redis一致,将user存入cookie,放行到IndexController登录成功页面
④ 此时再访问client2,来到过滤器,过滤器判断cookie、请求携带参数、redis均为null,请求重定向到认证中心server携带参数(login?redirect_url=)
⑤ 进入server的WebController的login方法,发现cookie中有user且与redis一致。请求重定向到client2携带参数(?xxl_sso_sessionid=)
⑥ 再次来到过滤器,此时cookie为null,判断请求携带参数与redis一致,将user存入cookie,放行到IndexController登录成功页面