一 前言
这个是续上一篇基于Springboot、Java的SSO单点登陆系统的简单实现后续的篇章,因为长度有点长,为了提高阅读体验,就拆分成两篇了。如果还没有看过上篇的朋友建议先看一下,文末会给出GitHub地址。
(1)使用环境:
SpringBoot2.X
MyBatis
基于redis存储的springSession
(2)基础学习:
关于SSO的基础学习可以参考该文章:单点登录(SSO),从原理到实现
代码风格使用的是晓风轻的代码规范,对于其中的AOP实现此处不会给出代码,具体可以在文章尾部的gitHub上查看:我的编码习惯 - Controller规范
进阶可以参考:单点登录(一)-----理论-----单点登录SSO的介绍和CAS+选型
(3)目标
- 使用Header认证替换Cookie,避免用户禁用cookie导致登陆失效的情况
- 实现可以运行操作的SSO单点登录系统
(4)注意:
- 此处使用了一个项目来模拟一个Client与一个Server,因为Server依靠存储token来判断用户是否登陆,而Client依靠Session判断用户是否登陆,因此两者能在同个项目共存。
- 由于项目的依赖很多,所以不会事无巨细地讲,只会挑重点的看,具体的可以在文章尾部的GitHub上查看
看完以上文章之后总结一下,在这次简单实现中我们需要做到的有以下几点:
- 用户从Client服务器发起注销请求。
- Client服务器需要将用户的注销请求发往SSO认证中心
- SSO认证中心注销掉所有Client服务器中的局部会话并且注销SSO认证中心中存放的token
二 正文
一些基础的工具类已经在上一篇文章中提出过了,这次直接进入正题:
用户的注销主要需要实现:
- 在所有子系统中注销用户的局部会话
- 在SSO认证中心中注销用户的token信息
在controller层:
// SSO认证中心
/**
* 注销用户在所有子系统的登陆状态
* @param requestBean token
* @return 操作结果
*/
@PostMapping("/logout")
public ResultBean<Data> logout(@RequestBody RequestBean requestBean) {
return new ResultBean<>(userService.logout(requestBean));
}
// Client服务器
/**
* 注销局部会话,若请求方不为SSO认证中心,则请求认证中心注销所有子系统的登陆状态
* @param requestBean token
* @param request 请求
* @return 操作结果
*/
@PostMapping("sublogout")
public ResultBean<Data> subLogout(@RequestBody RequestBean requestBean, HttpServletRequest request) {
if (!request.getRemoteAddr().startsWith("127.0.0.1")) {
try {
return new HttpClientUtil().postAction("http://localhost:8889/user/logout", new RequestBean());
} catch (IOException e) {
e.printStackTrace();
}
} else {
return new ResultBean<>(userService.subLogout(requestBean));
}
return new ResultBean<>();
}
可以注意到在客户端中,对接收到的请求进行了判断,若不是来自SSO认证中心的注销登录状态请求,则转发请求到SSO认证中心,由SSO认证中心来验证并决定是否注销所有子系统的登陆状态。
既然如此,那我们就先从SSO认证中心的注销功能开始看起:
在UserService接口中:
/**
* 注销用户在所有子系统的登陆状态
* @param requestBean token
* @return 操作结果
*/
Data logout(RequestBean requestBean);
/**
* 注销局部会话,若请求方不为SSO认证中心,则请求认证中心注销所有子系统的登陆状态
* @param requestBean token
* @return 操作结果
*/
Data subLogout(RequestBean requestBean);
下面从SSO认证中心logout()的实现方法进入:
/**
* 验证token是否存在,若存在则请求注销所有子系统的局部变量并且销毁token
* @param requestBean token 令牌凭证
* @return 操作结果
*/
@Override
public Data logout(RequestBean requestBean) {
String token = requestBean.getToken();
log.info("logout() : token = {}", token);
if (tokenAndUrlMap.containsKey(token)) {
List<String> urls = tokenAndUrlMap.get(token);
// 注销所有子系统的登陆状态
for (String clientUrl : urls) {
logoutSubSystem(token, clientUrl);
}
// 移除用户登陆状态
tokenAndUrlMap.remove(token);
tokenAndUserMap.remove(token);
tokenAndSessionId.remove(token);
// 默认成功
return null;
}
throw new CheckException("令牌错误");
}
先对令牌的合法性进行验证,验证通过后通过HttpClient发送请求注销所有子系统的局部变量。
发送销毁子系统局部变量请求:
private void logoutSubSystem(String token, String clientUrl) {
try {
log.info("logoutSubSystem(): sessionId = {}", tokenAndSessionId.get(token));
httpClientUtil.postAction(clientUrl + "/user/sublogout", new RequestBean().setAuthToken(tokenAndSessionId.get(token)));
} catch (IOException e) {
e.printStackTrace();
}
}
此时就像子系统发送了销毁局部变量的请求。那么下面让我们看下子系统的操作。
后面的操作中我采用了一个销毁Session的其他方法,就是使用redis来单独销毁该session的user属性。(也可以通过将x-auth-token放置于header中使得服务器能够找到相应的session,这样就直接在controller层操作session的销毁就行了,不过这次我采用的不是这种方法)
从上述的controller层我们可以发现子系统并没有销毁局部变量,那么是怎么销毁的呢。看到子系统的serviceImpl中:
/**
* 验证来自服务器的token与clientUrl,
* @param requestBean token、clientUrl
* @return 操作结果,成功data为带token与clientUrl
*/
@Override
public Data subLogout(RequestBean requestBean) {
String xAuthToken = requestBean.getAuthToken();
log.info("subLogout() : xAuthToken = {}", xAuthToken);
if (isNotEmpty(xAuthToken)) {
return subLogoutImpl(xAuthToken);
}
throw new CheckException(UserStatusEnum.PARAMETER_ERROR.getMsg());
}
/**
* 通过redis直接删除局部变量在redis数据库中的user属性
* @param xAuthToken x-auth-token 相当于SESSIONID
* @return 操作成功
*/
private Data subLogoutImpl(String xAuthToken) {
redisTemplate.opsForList().getOperations().delete("spring:session:sessions:" + xAuthToken);
return new Data();
}
这样就能操作成功了。
下面给出logout()与sublogout()的json格式:
{
"user":{
"account":"1",
"password":"1"
},
"token":"ce263a4d-23a8-4b5a-9dab-0690b4f6aaf5"
}
三 总结
在用户注销一个子系统的登陆状态时,为了不出现用户注销后在其他子系统中也能够登陆的情况,于是需要SSO认证中心将所有已注册的子系统中的用户登陆状态都进行注销。
于是在上述中,我们将用户token的验证与判断移交到SSO认证中心,当子系统接收到注销请求的时候,都会直接把该请求移交到SSO认证中心中,再由SSO认证中心统一进行子系统的注销。
项目GitHub地址:https://github.com/attendent/distrubuted