单点登录SSO

最强SSO单点登录教程(五)自己动手写SSO单点注销服务端和客户

2018-04-21  本文已影响121人  叩丁狼教育

作者:蓝雄威,叩丁狼教育高级讲师。原创文章,转载请注明出处。

一、前言

单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明。单点注销难点在于在其中一个系统注销之后,需要把其他的子系统的会话销毁.所以肯定需要子系统在令牌校验通过之后,统一认证中心要把该子系统的地址和会话记录起来.才能在注销的时候找到这些子系统通,依次调用子系统通的注销方法,销毁局部会话.

二、单点注销流程图

单点注销流程
cookie和session存储结构

三、代码实现

客户端(注意:两个客户端项目都得改):

步骤:
1.在两个客户端项目中修改main.jsp,让退出的地址映射到统一认证中心的登出方法.

<a href="http://www.sso.com:8443/logOut" >退出</a>

2.在SSOClientFilter.java校验令牌信息token的时候,还需要把该系统的登出地址和该系统的会话id一并的发送到统一认证中心.修改SSOClientFilter.javadoFilter方法

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        HttpSession session = req.getSession();
        //1.判断是否有局部的会话
        Boolean isLogin = (Boolean) session.getAttribute("isLogin");
        if(isLogin!=null && isLogin){
            //有局部会话,直接放行.
            chain.doFilter(request, response);
            return;
        }
        //判断地址栏中是否有携带token参数.
        String token = req.getParameter("token");
        if(StringUtils.isNoneBlank(token)){
            //token信息不为null,说明地址中包含了token,拥有令牌.
            //判断token信息是否由认证中心产生的.
            //验证地址为:http://www.sso.com:8443/verify
            String httpURL = SSOClientUtil.SERVER_URL_PREFIX+"/verify";
            Map<String,String> params = new HashMap<String,String>();
            //把客户端地址栏添加到的token信息传递给统一认证中心进行校验
            params.put("token", token);
            /**-------------------------阶段四添加的代码start---------------------------------**/
            //获取客户端的完整登出地址 http://www.crm.com:8088/logOut
            params.put("clientUrl", SSOClientUtil.getClientLogOutUrl());
            params.put("jsessionid", session.getId());
            /**-------------------------阶段四添加的代码end---------------------------------**/
            try {
                String isVerify = HttpUtil.sendHttpRequest(httpURL, params);
                if("true".equals(isVerify)){
                    //如果返回的字符串是true,说明这个token是由统一认证中心产生的.
                    //创建局部的会话.
                    session.setAttribute("isLogin", true);
                    //放行该次的请求
                    chain.doFilter(request, response);
                    return;
                }
            } catch (Exception e) {
                //这里可以完善,比如出现异常在前台显示具体页面
                //我们这个案例就不做这个哈.
                e.printStackTrace();
            }
        }
        //没有局部会话,重定向到统一认证中心,检查是否有其他的系统已经登录过.
        // http://www.sso.com:8443/checkLogin?redirectUrl=http://www.crm.com:8088
        SSOClientUtil.redirectToSSOURL(req, resp);
    }
服务端:

步骤:
1.在MockDatabaseUtil.java中模拟表t_client_info存储已注册的子系统信息,创建ClientInfoVo.java封装客户端信息

package cn.wolfcode.sso.vo;
import lombok.Getter;
import lombok.Setter;
/**
 * Created by wolfcode-lanxw
 */
@Setter@Getter
public class ClientInfoVo {
    private String clientUrl;//客户端登出地址
    private String jsessionid;//客户端会话id
}

MockDatabaseUtil.java中模拟表t_client_info的信息.

public class MockDatabaseUtil {
    //模拟数据库中的t_token表
    public static Set<String> T_TOKEN = new HashSet<String>();
    //模拟数据库中的t_client_info表
    public static Map<String,List<ClientInfoVo>> T_CLIENT_INFO =new HashMap<String,List<ClientInfoVo>>();
}

2.需要在SSOServerController.java修改verifyToken方法的逻辑,获取客户端传过来的令牌信息(token),客户端登出地址(clientUrl),客户端的会话id(jsessionid),并且需要把客户端地址存储起来.

@RequestMapping("/verify")
    @ResponseBody
    public String verifyToken(String token,String clientUrl,String jsessionid){
        //在模拟的数据库表t_token中查找是否有这条记录
        if(MockDatabaseUtil.T_TOKEN.contains(token)){
            //根据token获取客户端的登出地址记录集合
            List<ClientInfoVo> clientInfoList = MockDatabaseUtil.T_CLIENT_INFO.get(token);
            if(clientInfoList==null){
                //第一个系统注册的时候获取出来的集合空的,所以需要创建一个
                clientInfoList = new ArrayList<ClientInfoVo>();
                //创建好之后需要放入到map中,方便后续的获取
                MockDatabaseUtil.T_CLIENT_INFO.put(token,clientInfoList);
            }
            //封装客户端的信息
            ClientInfoVo vo = new ClientInfoVo();
            vo.setClientUrl(clientUrl);
            vo.setJsessionid(jsessionid);
            //把当前的客户端地址注册到集合中.
            clientInfoList.add(vo);
            //说明令牌有效,返回true
            return "true";
        }
        return "false";
    }

3.在SSOServerController.java添加一个统一认证中心登录的方法logOut

@RequestMapping("/logOut")
 public String logOut(HttpSession session){
     //销毁全局会话
     session.invalidate();
     return "logOut";
 }

4.拷贝之前客户端工具类HttpUtil.java到项目中
5.创建MySessionListener.java,监听session的销毁事件,当全局会话销毁的时候,获取全局会话中的令牌信息token,通过token信息查询t_client_info表,获取已注册的子系统集合,遍历子系统集合,依次调用子系统的登出方法.
6.清空t_token表数据,清空t_client_info表数据

package cn.wolfcode.sso.listener;

import cn.wolfcode.sso.util.HttpUtil;
import cn.wolfcode.sso.util.MockDatabaseUtil;
import cn.wolfcode.sso.vo.ClientInfoVo;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.List;

/**
 * Created by wolfcode-lanxw
 */
@WebListener
public class MySessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent se) {
    }
    //监听session的销毁事件
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        //获取会话中的令牌信息
        String token = (String) session.getAttribute("token");
        //删除t_token表中的数据
        MockDatabaseUtil.T_TOKEN.remove(token);
        //删除t_client_info表中的数据
        List<ClientInfoVo> clientInfoVoList = MockDatabaseUtil.T_CLIENT_INFO.remove(token);
        try{
            for(ClientInfoVo vo:clientInfoVoList){
                //获取出注册的子系统,依次调用子系统的登出的方法
                HttpUtil.sendHttpRequest(vo.getClientUrl(),vo.getJsessionid());
            }
        }catch(Exception e){
            e.printStackTrace();;
        }
    }
}

四、单点注销流程梳理

客户端:

1.在登陆的按钮链接写上统一认证中心的登出方法即可.
<a href=”http://www.sso.com/logOut”>退出</a>
2.校验令牌信息的同时把客户端登出地址(clientUrl)和客户端会话id(jsession)一并的传到统一认证中心

服务端:

    1.编写logOut方法,调用session.invalidate()
    2.创建session的监听器,在session的监听器的销毁方法写如下逻辑
        2.1获取session中的token.
        2.2根据token获取所有客户端的登出地址和会话id
        2.3通过HttpUtil选项调用客户端的登出方法.

五、测试

在服务端和两个客户端运行tomcat7:run命令.
在浏览器中按下Ctrl+Shift+Delete按键清楚cookie和缓存,避免干扰.

1.访问http://www.crm.com:8088/main,跳转到统一认证中心登录界面.
2.输入账号密码,放行请求,访问到CRM系统的main.jsp页面.
3.在同一浏览器中打开多个窗口输入http://www.wms.com:8089/main,都是放行请求.
4.点击CRM系统中的退出按钮,显示系统已注销
5.在同一浏览器中打开http://www.wms.com:8089/main,此时就跳转到统一认证中心的登陆页面.

如果测试结果和上述一致,说明单点注销功能也完成了.

六、代码下载

熟悉git命令的同学:

客户端单点注销Demo:

git reset --hard 5631a13ecfd0b73bfc27adaa34ba0fd6fe01b2fb

客户端2单点注销Demo:

git reset --hard 01db6af390ff9f765121d3f9e9b1895b0e671bd5

服务端单点注销Demo:

git reset --hard 5d619c5065cabd83a16d5b4d34e3c89a5950fb5b
不熟悉git命令的同学

客户端单点注销Demo
客户端2单点注销Demo
服务端单点注销Demo

WechatIMG7.jpeg
上一篇下一篇

猜你喜欢

热点阅读