什么是XXL-SSO
XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 拥有"轻量级、分布式、跨域、Cookie+Token均支持、Web+APP均支持"等特性。现已开放源代码,开箱即用。
- xxl-sso-server:中央认证服务,支持集群 - xxl-sso-core:Client端依赖 - xxl-sso-samples:单点登陆Client端接入示例项目 - xxl-sso-web-sample-springboot:基于Cookie接入方式,供用户浏览器访问,springboot版本 - xxl-sso-token-sample-springboot:基于Token接入方式,常用于无法使用Cookie的场景使用,如APP、Cookie被禁用等, springboot版本
导入idea
先启动xxl-sso-server
启动之前先看配置文件有redis连接信息
所以先启动本地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"
1、SSO认证中心地址: http://xxlssoserver.com:8080/xxl-sso-server 2、Client01应用地址: http://xxlssoclient1.com:8084/xxl-sso-token-sample-springboot/ 3、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";
}
// 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.setExpireMinite(SsoLoginStore.getRedisExpireMinite());
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:/";
}
}
发现这里是写死了的,可以自己修改为查数据库
@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拦截,把当前会话信息也会保存在本地一份
直接免登录