cas的客戶端應用是負載均衡,單點退出怎麼辦?
之前的專案一直是單節點,這次在生產系統中使用了負載均衡,一個應用部署了兩個節點,負載均衡策略未知。這樣在使用時發現了這麼一個問題:在單點退出後,應用有時候可以退出,但有時還在登陸狀態,這就很鬱悶了。
我的cas版本是5.1.2。一點點排查,把每個節點的日誌都開啟,把日誌級別設定成最小trace,功夫不負有心人,發現了問題:使用者在瀏覽器訪問的都是節點一,在cas單點退出後,cas服務端傳送logoutRequest的post請求,結果負載均衡把請求傳送到了節點二,節點二壓根未訪問過,logoutRequest請求當然不起作用。
問題找到了,看官可能覺得樓主有點low,首先,負載均衡具體什麼策略,為什麼會發錯節點;第二,負載均衡多節點為什麼不用session共享。下面我一一解答。
第一個,負載均衡不是我負責的,我只知道通過瀏覽器訪問一個應用能保證每次都訪問同個節點,這就保證了使用者每次都攜帶登入資訊。我以為採用的是nginx的ip的hash值,結果被告知不是,是通過硬體控制負載均衡的。
第二個,之前公司專案差不多都是單節點,幾乎所有的應用都未使用session共享,況且現在是上線的關鍵節點,現改也來不及,最好是修改cas客戶端的東西,大家都更新一下jar包就可以了。
logger.trace("Logout request:\n{}", logoutMessage); final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex"); if (CommonUtils.isNotBlank(token)) { final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token); if (session != null) { final String sessionID = session.getId(); logger.debug("Invalidating session [{}] for token [{}]", sessionID, token); try { session.invalidate(); } catch (final IllegalStateException e) { logger.debug("Error invalidating session.", e); } this.logoutStrategy.logout(request); }
瞭解cas的同學知道,cas服務端傳送logoutRequest的post請求,裡面攜帶token資訊,客戶端就憑藉這個token找到session。圖中標紅部分,當未找到時不起作用,解決之道就在此,我們拿到了token資訊,然後向攜帶著token嚮應用的各個節點發送一次退出請求,這樣就能找到session,然後失效退出了。
就這樣,問題找到了,就著手解決吧。
第一步:修改cas客戶端,當session為空時,傳送post請求,上程式碼。
logger.trace("Logout request:\n{}", logoutMessage); final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex"); if (CommonUtils.isNotBlank(token)) { final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token); if (session != null) { final String sessionID = session.getId(); logger.debug("Invalidating session [{}] for token [{}]", sessionID, token); try { session.invalidate(); } catch (final IllegalStateException e) { logger.debug("Error invalidating session.", e); } this.logoutStrategy.logout(request); }else{ if(CommonUtils.isNotBlank(this.balanceUrls)) { String[] urls = balanceUrls.split(","); for(String url : urls) { Map<String, Object> map = new HashMap<String, Object>(); map.put("token",token); try {
//傳送post請求,不要在意我的工具類名字,複製來的 HttpClient.GET_JCMH_QX(map, url + "/logoutRequestOther"); }catch(Exception e) { logger.debug("Error HttpClient ", e); } } } }
比之前的程式碼多了else語句,這裡的balanceUrls(在filter的initParam維護),是負載均衡的轉發地址,多個用,隔開,然後遍歷傳送post請求,請求路徑統一為“/logoutRequestOther“。
第二步在我們第一步的/logoutRequestOthe請求肯定是請求不到的,我們怎麼處理呢?為了最小的改動,在cas客戶端增加filter進行處理,增加LogoutForBalanceFilter類。
一定要跟SingleSignOutFilter同目錄,原因是我們需要用到 private SessionMappingStorage sessionMappingStorage ,session資訊就儲存在這個類中。
package org.jasig.cas.client.session; import org.jasig.cas.client.util.CommonUtils; import org.jasig.cas.client.util.JsonUtil; import org.jasig.cas.client.util.XmlUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.InputStream; import java.util.Map; public class LogoutForBalanceFilter implements Filter { private final Logger logger = LoggerFactory.getLogger(LogoutForBalanceFilter.class); private SessionMappingStorage sessionMappingStorage; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { if (servletRequest instanceof HttpServletRequest) { HttpServletRequest req = (HttpServletRequest) servletRequest; String uri = req.getRequestURI(); logger.debug("================== uri [{}]", uri); if (uri.contains("/logoutRequestOther")) { if (sessionMappingStorage == null) { sessionMappingStorage = getSessionMappingStorage(); }
//獲取post請求的引數 Map<?,?> map = JsonUtil.jsonStrToMap(getParams(req)); if(map != null) { String token = (String)map.get("token"); logger.debug("================== token [{}]", token); if (CommonUtils.isNotBlank(token)) {
//通過token查詢session final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token); if (session != null) { final String sessionID = session.getId(); logger.debug("Invalidating session [{}] for token [{}]", sessionID, token); try { session.invalidate(); } catch (final IllegalStateException e) { logger.debug("Error invalidating session.", e); } //this.logoutStrategy.logout(request); } } } return; } chain.doFilter(servletRequest, servletResponse); }else { chain.doFilter(servletRequest, servletResponse); } } @Override public void destroy() { } protected static SessionMappingStorage getSessionMappingStorage() { return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage(); } protected String getParams(HttpServletRequest request){ String body = ""; StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { ex.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); return body; } }
至此客戶端更改完畢。
這樣,同事們只需要更改下web.xml,增加一個filter,新增負載均衡轉發的地址引數列表。
<filter> <filter-name>Cas_Balance_Logout</filter-name> <filter-class>org.jasig.cas.client.session.LogoutForBalanceFilter</filter-class> </filter> <filter-mapping> <filter-name>Cas_Balance_Logout</filter-name> <url-pattern>/logoutRequestOther</url-pattern> </filter-mapping> <filter>
<filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>http://zsptsso.jlsw.tax.cn/cas</param-value> </init-param> <init-param> <param-name>balanceUrls</param-name> <param-value>http://123.14.62.125:8002/zntx,http://123.14.62.126:8002/zntx</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener>
圖中紅色部分就是同事們需要增加的,是不是很簡單呢,完