基于Springboot的SSO单点登陆系统的注销操作实战

一 前言

这个是续上一篇基于Springboot、Java的SSO单点登陆系统的简单实现后续的篇章,因为长度有点长,为了提高阅读体验,就拆分成两篇了。如果还没有看过上篇的朋友建议先看一下,文末会给出GitHub地址。

(1)使用环境:

SpringBoot2.X

MyBatis

基于redis存储的springSession

(2)基础学习:

关于SSO的基础学习可以参考该文章:单点登录(SSO),从原理到实现

代码风格使用的是晓风轻的代码规范,对于其中的AOP实现此处不会给出代码,具体可以在文章尾部的gitHub上查看:我的编码习惯 - Controller规范

进阶可以参考:单点登录(一)-----理论-----单点登录SSO的介绍和CAS+选型

(3)目标

  1. 使用Header认证替换Cookie,避免用户禁用cookie导致登陆失效的情况
  2. 实现可以运行操作的SSO单点登录系统

(4)注意:

  1. 此处使用了一个项目来模拟一个Client与一个Server,因为Server依靠存储token来判断用户是否登陆,而Client依靠Session判断用户是否登陆,因此两者能在同个项目共存。
  2. 由于项目的依赖很多,所以不会事无巨细地讲,只会挑重点的看,具体的可以在文章尾部的GitHub上查看

看完以上文章之后总结一下,在这次简单实现中我们需要做到的有以下几点:

  1. 用户从Client服务器发起注销请求。
  2. Client服务器需要将用户的注销请求发往SSO认证中心
  3. SSO认证中心注销掉所有Client服务器中的局部会话并且注销SSO认证中心中存放的token

二 正文

一些基础的工具类已经在上一篇文章中提出过了,这次直接进入正题:

用户的注销主要需要实现:

  1. 在所有子系统中注销用户的局部会话
  2. 在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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值