最强SSO单点登录教程(五)自己动手写SSO单点注销服务端和客户
作者:蓝雄威,叩丁狼教育高级讲师。原创文章,转载请注明出处。
一、前言
单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明。单点注销难点在于在其中一个系统注销之后,需要把其他的子系统的会话销毁.所以肯定需要子系统在令牌校验通过之后,统一认证中心要把该子系统的地址和会话记录起来.才能在注销的时候找到这些子系统通,依次调用子系统通的注销方法,销毁局部会话.
二、单点注销流程图
单点注销流程cookie和session存储结构
三、代码实现
客户端(注意:两个客户端项目都得改):
步骤:
1.在两个客户端项目中修改main.jsp
,让退出的地址映射到统一认证中心的登出方法.
<a href="http://www.sso.com:8443/logOut" >退出</a>
2.在SSOClientFilter.java
校验令牌信息token的时候,还需要把该系统的登出地址和该系统的会话id一并的发送到统一认证中心.修改SSOClientFilter.java
的doFilter
方法
@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