单点登录框架XXL-SSO源码解析
项目介绍
XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 拥有"轻量级、分布式、跨域、Cookie+Token均支持、Web+APP均支持"等特性。现已开放源代码,开箱即用。
项目地址
https://gitee.com/xuxueli0323/xxl-sso
源码解析
- 访问SSO-Client,
http://localhost:8081//xxl-sso-web-sample-springboot
,会重定向到SSO-Server的登录页面。访问客户端需要经过XxlSsoWebFilter
,第一次访问获取到的xxlUser信息为空,会跳转到SSO-Server的登录页面。
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
//...
// valid login user, cookie + redirect
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;
res.sendRedirect(loginPageUrl);
return;
}
}
//...
}
SsoWebLoginHelper.loginCheck(req, res);
,从cookie中获取数据,获取不到用户信息;再根据参数获取paramSessionId,也是获取不到的,所以第一次获取到的XxlSsoUser为空。
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;
}
- 重定向到授权服务器,
http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://localhost:8081/xxl-sso-web-sample-springboot/
,进行服务端登录,WebController#doLogin
。
@RequestMapping(doLogin)
public String doLogin(HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes,
String username,
String password,
String ifRemember) {
boolean ifRem = (ifRemember!=null&&on.equals(ifRemember))truefalse;
valid login
ReturnTUserInfo 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 redirectlogin;
}
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
SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);
4、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;
}
}
主要生成了sessionId,String sessionId = xxlSsoUser.getUserid().concat("_").concat(xxlSsoUser.getVersion());
。
SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);
主要做了保存用户数据到redis中,存放cookie。Cookie保存在private static final String COOKIE_PATH = "/";
,保证了所有域名都可以获取到cookie。
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);
}
最后跳转到SSO-Client,且带有参数sessionId。
-
当再次访问SSO-Client,cookie和参数中都有sessionId。根据sessionId从Redis中获取对象XxlSsoUser。
-
用户退出。会清空cookie,跳转到SSO-Server的退出页面。
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;
}
SSO-Server的退出接口,核心逻辑就是清除cookie和redis缓存数据。
@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";
}
项目总结
- 客户端的校验是根据
XxlSsoWebFilter
,如果cookie和参数中都没有,则需要重定向到SSO-Server。当SSO-Server登录成功之后,cookie和参数都有sessionId,且redis缓存中也存在xxlSsoUser,则校验通过。 - 其他的客户端的登录是因为也有
XxlSsoWebFilter
拦截,会通过cookie来获取用户id,然后从redis中获取用户信息,验证通过。