目录
- 1 项目启动
- 2 分析
- 登录流程
- ① 首次访问 `client1` 的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot`/` ,进入过滤器
- ② 请求重定向至 `server`的 http://xxlssoserver.com:8080/xxl-sso-server`/login`?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
- ③ 请求重定向至 `client1`的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot`/`?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19
- ① 再访问 `client2` 的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot`/`
- ② 请求重定向至 `server`的 http://xxlssoserver.com:8080/xxl-sso-server`/login`?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
- ③ 请求重定向至 `client2`的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot`/`?xxl_sso_sessionid=1000_bfada28b41f5492ea442ce730282043f
- 注销流程
- ① 访问 `client1` 的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot`/logout`,进入过滤器
- ② 请求重定向至 `server`的 http://xxlssoserver.com:8080/xxl-sso-server`/logout`
- ③
- ① 刷新请求 `client2`的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot`/`?xxl_sso_sessionid=1000_62816da65ce24d069776a7d25c510c4b
- ② 请求重定向至 `server`的 http://xxlssoserver.com:8080/xxl-sso-server`/login`?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
- ③
单点登录原理及简单实现: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
使用FilterRegistrationBean 详解
DisposableBean 与InitializingBean 详解@Configuration public class XxlSsoConfig implements DisposableBean { @Value("${xxl.sso.server}") private String xxlSsoServer; @Value("${xxl.sso.logout.path}") private String xxlSsoLogoutPath; @Value("${xxl-sso.excluded.paths}") private String xxlSsoExcludedPaths; @Value("${xxl.sso.redis.address}") private String xxlSsoRedisAddress; @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; } @Override public void destroy() throws Exception { JedisUtil.close(); // xxl-sso, redis close } }
core 里的过滤器如下(判断cookie或者请求携带参数中的id是否与redis中一致)
发现 redis 无数据,请求重定向到 server 的登录页面 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
此处是配置文件中配置的参数### 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,分别以cookieId和请求参数id作为key判断,此时两种key都是null ———— 跳过
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 XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId); if (xxlUser != null) { return xxlUser; } //版本不一致,移除session,设置新的 SsoWebLoginHelper.removeSessionIdByCookie(request, response); // 此处是通过请求参数的 Id 获取 redis 中的user //首次访问client、server,paramSessionId cookieSessionId xxlUser 都为 null String paramSessionId = request.getParameter("xxl_sso_sessionid"); xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId); if (xxlUser != null) { // set new cookie CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false); // expire when browser close (client cookie) return xxlUser; } return null; }
② 请求重定向至 server
的 http://xxlssoserver.com:8080/xxl-sso-server/login
?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
② 进入server的WebController的login方法,发现cookie、请求携带参数、redis均为null 。跳转到登录页,输入账号密码,提交server的dologin登录方法,将user存放cookie 存放redis,请求重定向回到client1携带参数(?xxl_sso_sessionid=)
查询之前未登录,就进入登陆页面,输入登陆账号密码
校验账号密码 —— 根据user对象生成 xxl_sso_sessionid 存放 redis、cookie
—— 根据请求路径携带的参数 ?redirect_url= ,将请求重定向回去 client1,并携带 xxl_sso_sessionid 作为请求参数
@RequestMapping("/login")
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {
// 查验 cookie、请求参数 是否携带 id (id用于查询redis是否有user)
//xxlUser = null ———— 跳过
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
if (xxlUser != null) {
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:/";
}
}
model.addAttribute("errorMsg", request.getParameter("errorMsg"));
model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "login";
}
跳转到登录也买你,数据账号密码后进入 doLogin 方法
@RequestMapping("/doLogin") //首次提交登录 /xxl-sso-server/doLogin
public String doLogin(HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes,
String username,
String password,
String ifRemember) {
//首次提交登录ifRemember=null ifRem =false
boolean ifRem = (ifRemember!=null&&"on".equals(ifRemember))?true:false;
// 校验账号密码是否正确
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";
}
// 1、创建 XxlSsoUser 对象
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
String sessionId = SsoSessionIdHelper.makeSessionId(xxlUser);
// 3、存放session,存放redis
SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);
// 4、首次提交登录,转回client1. redirectUrlFinal = http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19
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:/";
}
}
③ 请求重定向至 client1
的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19
进入过滤器
③ 再次来到过滤器,此时cookie为null,判断请求携带参数与redis一致,将user存入cookie,放行到IndexController登录成功页面
@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判断
// 此时cookieId为null,根据 请求参数id 查询redis得到user,再将 user 存入cookie
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
// 此处user有数据 ———— 跳过
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;
}
}
// ser sso user
request.setAttribute(Conf.SSO_USER, xxlUser);
chain.doFilter(request, response);
return;
}
public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){ String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid"); // cookie user 此处是通过cookieId获取 redis 中的user //二次认证中心登录后跳回访问client1,所以cookieSessionId为null xxlUser为null XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId); if (xxlUser != null) { return xxlUser; } // remove old cookie 版本不一致,移除session,设置新的 SsoWebLoginHelper.removeSessionIdByCookie(request, response); // 此处是通过请求参数的 Id 获取 redis 中的user //二次认证中心登录后跳回访问client1,携带参数 ?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19 String paramSessionId = request.getParameter("xxl_sso_sessionid"); //从redis获取user与sessionid进行匹配。匹配成功则设置 cookie xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId); if (xxlUser != null) { // set new cookie CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false); // expire when browser close (client cookie) return xxlUser; } return null; }
登录成功,进入 client1 首页
@RequestMapping("/")
public String index(Model model, HttpServletRequest request) {
XxlSsoUser xxlUser = (XxlSsoUser) request.getAttribute(Conf.SSO_USER);
model.addAttribute("xxlUser", xxlUser);
return "index"; //登录成功页面
}
① 再访问 client2
的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
④ 此时再访问client2,来到过滤器,过滤器判断cookie、请求携带参数、redis均为null,请求重定向到认证中心server携带参数(login?redirect_url=)
@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;
}
// 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断,此时两种key都是null ———— 跳过
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
// xxlUser = null 首次登录
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,分别以cookieId和请求参数id作为key判断,此时两种key都是null ———— 跳过
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 XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId); if (xxlUser != null) { return xxlUser; } //版本不一致,移除session,设置新的 SsoWebLoginHelper.removeSessionIdByCookie(request, response); // 此处是通过请求参数的 Id 获取 redis 中的user //首次访问client、server,paramSessionId cookieSessionId xxlUser 都为 null String paramSessionId = request.getParameter("xxl_sso_sessionid"); xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId); if (xxlUser != null) { // set new cookie CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false); // expire when browser close (client cookie) return xxlUser; } return null; }
② 请求重定向至 server
的 http://xxlssoserver.com:8080/xxl-sso-server/login
?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
⑤ 进入server的WebController的login方法,发现cookie中有user且与redis一致。请求重定向到client2携带参数(?xxl_sso_sessionid=)
@RequestMapping("/login")
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {
// 因为server已有cookieId,在redis中查询得到 user
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
if (xxlUser != null) {
String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
if (redirectUrl!=null && redirectUrl.trim().length()>0) {
//携带参数重定向回到client2. http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_bfada28b41f5492ea442ce730282043f
String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request);
String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;;
return "redirect:" + redirectUrlFinal;
} else {
return "redirect:/";
}
}
model.addAttribute("errorMsg", request.getParameter("errorMsg"));
model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "login";
}
此时server已有cookieId,在redis中查询得到 user
public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){ String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid"); // 此处是通过cookieId获取 redis 中的user //此时是client2重定向进 server,此时的server有cookie //根据 cookieId 查询 redis 得到 user 信息,直接返回 XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId); if (xxlUser != null) { return xxlUser; } SsoWebLoginHelper.removeSessionIdByCookie(request, response); String paramSessionId = request.getParameter("xxl_sso_sessionid"); xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId); if (xxlUser != null) { // set new cookie CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false); // expire when browser close (client cookie) return xxlUser; } return null; }
③ 请求重定向至 client2
的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
?xxl_sso_sessionid=1000_bfada28b41f5492ea442ce730282043f
⑥ 再次来到过滤器,此时cookie为null,判断请求携带参数与redis一致,将user存入cookie,放行到IndexController登录成功页面
@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)) {
SsoWebLoginHelper.removeSessionIdByCookie(req, res);
String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
res.sendRedirect(logoutPageUrl);
return;
}
// 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断
// 此时cookieId为null,根据 请求参数id 查询redis得到user,再将 user 存入cookie
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
// 此处user有数据 ———— 跳过
if (xxlUser == null) {
String header = req.getHeader("content-type");
boolean isJson= header!=null && header.contains("json");
if (isJson) {
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 {
String link = req.getRequestURL().toString();
String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
+ "?" + Conf.REDIRECT_URL + "=" + link;
res.sendRedirect(loginPageUrl);
return;
}
}
// ser sso user
request.setAttribute(Conf.SSO_USER, xxlUser);
chain.doFilter(request, response);
return;
}
public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){ String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid"); // cookie user 此处是通过cookieId获取 redis 中的user //二次认证中心登录后跳回访问client1,所以cookieSessionId为null xxlUser为null XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId); if (xxlUser != null) { return xxlUser; } // remove old cookie 版本不一致,移除session,设置新的 SsoWebLoginHelper.removeSessionIdByCookie(request, response); // 此处是通过请求参数的 Id 获取 redis 中的user //二次认证中心登录后跳回访问client1,携带参数 ?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19 String paramSessionId = request.getParameter("xxl_sso_sessionid"); //从redis获取user与sessionid进行匹配。匹配成功则设置 cookie xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId); if (xxlUser != null) { // set new cookie CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false); // expire when browser close (client cookie) return xxlUser; } return null; }
登录成功,进入 client2 首页
@RequestMapping("/")
public String index(Model model, HttpServletRequest request) {
XxlSsoUser xxlUser = (XxlSsoUser) request.getAttribute(Conf.SSO_USER);
model.addAttribute("xxlUser", xxlUser);
return "index"; //登录成功页面
}
注销流程
① 访问 client1
的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/logout
,进入过滤器
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// 当前访问的路径为 servletPath="/logout"
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 移除 client1 的cookie
SsoWebLoginHelper.removeSessionIdByCookie(req, res);
//注销时,请求重定向 http://xxlssoserver.com:8080/xxl-sso-server/logout
String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
res.sendRedirect(logoutPageUrl);
return;
}
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
if (xxlUser == null) {
String header = req.getHeader("content-type");
boolean isJson= header!=null && header.contains("json");
if (isJson) {
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 {
String link = req.getRequestURL().toString();
String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
+ "?" + Conf.REDIRECT_URL + "=" + link;
res.sendRedirect(loginPageUrl);
return;
}
}
request.setAttribute(Conf.SSO_USER, xxlUser);
chain.doFilter(request, response);
return;
}
② 请求重定向至 server
的 http://xxlssoserver.com:8080/xxl-sso-server/logout
@RequestMapping("/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";
}
服务端注销逻辑
public static void logout(HttpServletRequest request, HttpServletResponse response) { //查询是否有 cookie String cookieSessionId = CookieUtil.getValue(request, Conf.SSO_SESSIONID); if (cookieSessionId==null) { return; } // 移除 redis String storeKey = SsoSessionIdHelper.parseStoreKey(cookieSessionId); if (storeKey != null) { SsoLoginStore.remove(storeKey); } //移除 cookie CookieUtil.remove(request, response, Conf.SSO_SESSIONID); }
跳转到 /login
@RequestMapping(Conf.SSO_LOGIN)
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {
// 检查 cookie 和 请求参数 是否携带 id(id用于查询redis中是否有user)
//结果 xxlUser = null ———— 跳过
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
if (xxlUser != null) {
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:/";
}
}
model.addAttribute("errorMsg", request.getParameter("errorMsg"));
model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "login";
}
跳转到登陆页面
③
此时页面地址是 http://xxlssoserver.com:8080/xxl-sso-server/login
@RequestMapping("/doLogin") //首次提交登录 /xxl-sso-server/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";
}
// 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
String sessionId = SsoSessionIdHelper.makeSessionId(xxlUser);
// 3、login, store storeKey + cookie sessionId
// 存放session,存放redis
SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);
// 4、return, redirect sessionId
// 首次提交登录,转回client1. redirectUrlFinal = http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19
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:/";
}
}
@RequestMapping("/")
public String index(Model model, HttpServletRequest request, HttpServletResponse response) {
// login check
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
if (xxlUser == null) {
return "redirect:/login";
} else {
model.addAttribute("xxlUser", xxlUser);
return "index";
}
}
进入首页
① 刷新请求 client2
的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
?xxl_sso_sessionid=1000_62816da65ce24d069776a7d25c510c4b
进入过滤器
@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)) {
SsoWebLoginHelper.removeSessionIdByCookie(req, res);
String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
res.sendRedirect(logoutPageUrl);
return;
}
// 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断
// 由于 redis 已被删除,所以 xxlUser = null
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
if (xxlUser == null) {
String header = req.getHeader("content-type");
boolean isJson= header!=null && header.contains("json");
if (isJson) {
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 {
String link = req.getRequestURL().toString();
String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
+ "?" + Conf.REDIRECT_URL + "=" + link;
//请求重定向至 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient2.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;
}
public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){ String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid"); // cookie user 此处是通过cookieId获取 redis 中的user //此处有 cookie ,但是 redis 已被删除,所以 xxlUser = null XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId); if (xxlUser != null) { return xxlUser; } SsoWebLoginHelper.removeSessionIdByCookie(request, response); // 此处是通过请求参数的 Id 获取 redis 中的user //此处有 请求参数 ,但是 redis 已被删除,所以 xxlUser = null ———— 跳过 String paramSessionId = request.getParameter("xxl_sso_sessionid"); xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId); if (xxlUser != null) { // set new cookie CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false); // expire when browser close (client cookie) return xxlUser; } return null; }
② 请求重定向至 server
的 http://xxlssoserver.com:8080/xxl-sso-server/login
?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
跳转到 /login
@RequestMapping("/login")
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {
// 检查 cookie 和 请求参数 是否携带 id(id用于查询redis中是否有user)
//结果 xxlUser = null ———— 跳过
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
if (xxlUser != null) {
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:/";
}
}
model.addAttribute("errorMsg", request.getParameter("errorMsg"));
model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "login";
}
跳转到登陆页面
③
此时页面地址是 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/