問題表現:兩個站點使用同一個cas單點登陸,登陸後,在A系統登出,B系統也同時登;而在B系統登出A系統未登出;java
問題緣由:A系統沒有實現cas的logout邏輯。須要實現logoutFilter邏輯;在請求cas.example.com/cas/logout時,Cas Server會將客戶端攜帶的TGT刪除,同時回調該TGT對應的全部service;因此cas client須要實現登出邏輯,使會話失效;web
參考:http://elim.iteye.com/blog/2144265apache
-----------------------------------------------我來分隔-----------------------------------------------------tomcat
一開始並不知道這套邏輯;只從表現出來的差別去查找。服務器
表現:在A系統登出,再去刷新B系統頁面;會獲得一個新的JSESSIONID, 也就是服務端把當前鏈接當成新的會話;cookie
查找JSESSIONID的生成代碼。一開始想到應該是tomcat生成的,因此根據http://stackoverflow.com/questions/595872/under-what-conditions-is-a-jsessionid-created;提供的說法,在調用request.getSession()或request.getSession(true)時觸發是否生成JSESSIONID(根據請求頭是否帶有JSESSIONID的cookie);其中找到:session
JSESSIONID這個字符串值定義在: tomcat-embed-core.jar下的org.apache.catalina.util.SessionConfig類下的一個常量屬性;dom
把JSESSIONID植入cookie的是 org.apache.catalina.connector.Request類的doGetSession方法;ide
若是使用了Shiro,shiro會攔截以上步驟,覆蓋爲本身的實現;url
shiro中也默認是JSESSIONID,定義在org.apache.shiro.web.servlet.ShiroHttpSession類下的屬性;
session生成是org.apache.shiro.web.session.mgt.DefaultSessionManager類的create方法;
cookie的生成是在org.apache.shiro.web.session.mgt.DefaultSessionManager類的storeSessionId方法;
shiro的seesionId默認使用的是UUID.randomUUID()方法;
shiro會在認證失敗(會先獲取當前session,並判斷是否已認證)的攔截器的onAccessDenied()方法中調用saveRequestAndRedirectToLogin(...).保存當前請求到新session.而且跳轉到cas登陸頁
-------------------------------------------下面是logoutFilter的實現邏輯----------------------------------
/** * 攔截全部請求 * --若是請求中包含了ticket參數(成功登錄cas後,cas server會自動跳轉到cas client並在url中帶上ticket), * 記錄ticket和sessionID的映射 * * --若是請求中包含logoutRequest參數(說明是cas logout, cas server回調ticket相同的全部cas client), * 標記session爲無效 若是session不爲空,且被標記爲無效,則登出 * * @param request the incoming ServletRequest * @param response the outgoing ServletResponse * @return 是logoutRequest請求返回false,不然返回true * @throws Exception if there is any error. */ @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest req = (HttpServletRequest) request; if (handler.isTokenRequest(req)) { // 是不是登錄成功的回調。請求連接中含有ticket參數,記錄ticket和sessionID handler.recordSession(req); return true; } else if (handler.isLogoutRequest(req)) { // cas服務器發送的請求,連接中含有logoutRequest參數,在以前記錄的session中設置logoutRequest屬性爲true // 由於Subject是和線程是綁定的,因此沒法獲取登陸的Subject直接logout handler.invalidateSession(req); // Do not continue up filter chain return false; } else { logger.trace("Ignoring URI " + req.getRequestURI()); } Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(false); if (session != null && session.getAttribute(handler.getLogoutParameterName()) != null) { //若是session存在,且屬性中有logoutRequest值,則登出 try { subject.logout(); } catch (SessionException ise) { logger.debug("Encountered session exception during logout. This can generally safely be ignored.", ise); } } return true; }