一、前言
單點登入自然也要單點登出,在一個子系統中登出,所有子系統的會話都将被銷毀,用下面的圖來說明。單點登出難點在于在其中一個系統登出之後,需要把其他的子系統的會話銷毀.是以肯定需要子系統在令牌校驗通過之後,統一認證中心要把該子系統的位址和會話記錄起來.才能在登出的時候找到這些子系統通,依次調用子系統通的登出方法,銷毀局部會話.
二、單點登出流程圖

單點登出流程
cookie和session存儲結構
三、代碼實作
用戶端(注意:兩個用戶端項目都得改):
步驟:
1.在兩個用戶端項目中修改
main.jsp
,讓退出的位址映射到統一認證中心的登出方法.
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
不熟悉git指令的同學
用戶端單點登出Demo
用戶端2單點登出Demo
服務端單點登出Demo
原文連結:https://www.jianshu.com/p/667c8f0b514f