client是用域名访问的
1.如果客户端是一台服务器,查看cas-server的debug日志,发现server在客户端登录成功后,会记录下客户端的域名和票据,当用户从客户端退出时,会调server的logout,server取出所有同一票据的客户端域名,循环清除session,但如果server服务器没有配置客户端域名对应的ip host文件,server找不到该域名对应的主机,因此清不掉session,导致无法退出。
解决方案:在server服务器配置host文件,配置客户端域名和ip映射。
2.如果客户端是集群模式
- 单点注销为保证注销成功,需保证配置的域名、IP地址在单点登录服务端可以请求到,这样单点登录在做回调的时候才可以通过存储的st信息把登录的客户端注销成功
- 集群环境下单点登录注销会存在注销不成功情况,原因是单点登录服务在做回调的时候回可能分发在非登录的tomcat服务上
解决方案:
1:Nginx+tomcat+redis session共享,该实现方案,不管注销落在哪个tomcat都可以把redis中session注销成功。(未实践)
2:修改cas-client源码,单点登录客户端集群采用广播的方式,注销时向所有节点发起注销session操作。(实践成功)
基于cas-client-core-3.4.1实现
参考 http://blog.csdn.net/zhurhyme/article/details/74726008?locationNum=5&fps=1
基于cas-client-core-3.2.1实现
参考 https://www.jianshu.com/p/4c6f010c420e
web.xml修改以下配置
<!-- 单点登录退出监听 -->
<listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <!-- 单点注销过滤器。 -->
<filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>com.cas.SingleSignOutFilter</filter-class> <init-param> <description>cas client cluster nodes</description> <param-name>clusterNodeUrls</param-name> <param-value>http://ip:8080/client1/,http://ip:8081/client2/</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
修改cas-client 的SingleSignOutFilter和SingleSignOutHandler
import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.jasig.cas.client.session.SessionMappingStorage; import org.jasig.cas.client.util.AbstractConfigurationFilter; public class SingleSignOutFilter extends AbstractConfigurationFilter { private static final SingleSignOutHandler handler = new SingleSignOutHandler(); public void init(FilterConfig filterConfig) throws ServletException { if (!isIgnoreInitConfiguration()) { handler.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket")); handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", "logoutRequest")); //clusterNodeUrls handler.setClusterNodeUrls(getPropertyFromInitParams(filterConfig, "clusterNodeUrls", "")); } handler.init(); } public void setArtifactParameterName(String name) { handler.setArtifactParameterName(name); } public void setLogoutParameterName(String name) { handler.setLogoutParameterName(name); } public void setSessionMappingStorage(SessionMappingStorage storage) { handler.setSessionMappingStorage(storage); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; if (handler.isTokenRequest(request)) { handler.recordSession(request); } else if(handler.isLogoutRequest(request)){//cas-server logout请求 handler.destroySession(request); return; } else if(handler.isLogoutRequestFromClusterNode(request)){//接收其它节点发送的http logout请求 //清除本节点session handler.destroySessionFromClusterNode(request); return; } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } protected static SingleSignOutHandler getSingleSignOutHandler() { return handler; } }
import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.HttpClientUtils; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.jasig.cas.client.session.HashMapBackedSessionMappingStorage; import org.jasig.cas.client.session.SessionMappingStorage; import org.jasig.cas.client.util.CommonUtils; import org.jasig.cas.client.util.XmlUtils; public final class SingleSignOutHandler { private final Log log = LogFactory.getLog(getClass()); private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage(); private String artifactParameterName = "ticket"; private String logoutParameterName = "logoutRequest"; private String logoutParameterClusterName = "logoutRequestCluster"; ///clusterNodeUrls private String clusterNodeUrls; public void setSessionMappingStorage(SessionMappingStorage storage) { this.sessionMappingStorage = storage; } public SessionMappingStorage getSessionMappingStorage() { return this.sessionMappingStorage; } public void setArtifactParameterName(String name) { this.artifactParameterName = name; } public void setLogoutParameterName(String name) { this.logoutParameterName = name; } public void setClusterNodeUrls(String clusterNodeUrls) { this.clusterNodeUrls = clusterNodeUrls; } public void init() { CommonUtils.assertNotNull(this.artifactParameterName,"artifactParameterName cannot be null."); CommonUtils.assertNotNull(this.logoutParameterName,"logoutParameterName cannot be null."); CommonUtils.assertNotNull(this.sessionMappingStorage,"sessionMappingStorage cannote be null."); } public boolean isTokenRequest(HttpServletRequest request) { return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request,this.artifactParameterName)); } public boolean isLogoutRequest(HttpServletRequest request) { log.info("isLogoutRequest begin----"); log.info(request.getRequestURL()); log.info("request.getMethod()=" + request.getMethod()); log.info("CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,this.safeParameters))=" + CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName))); log.info("isLogoutRequest end----"); return ("POST".equals(request.getMethod())) && (!isMultipartRequest(request)) && (CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName))); } /** * 判断是否是其它节点发送的logout通知 * @param request * @return */ public boolean isLogoutRequestFromClusterNode(HttpServletRequest request) { log.info("isLogoutRequestFromClusterNode begin---"); log.info("clusterNodeUrls=" + this.clusterNodeUrls); log.info("request.getParameter(this.logoutParameterClusterName)=" + request.getParameter(this.logoutParameterClusterName)); log.info("isLogoutRequestFromClusterNode end---"); return (!isMultipartRequest(request)) && ("true".equals(request.getParameter(this.logoutParameterClusterName))); } public void recordSession(HttpServletRequest request) { HttpSession session = request.getSession(true); String token = CommonUtils.safeGetParameter(request,this.artifactParameterName); log.info("--------recordSession-------------token:"+token); if (this.log.isDebugEnabled()) { this.log.debug("Recording session for token " + token); } try { this.sessionMappingStorage.removeBySessionById(session.getId()); } catch (Exception e) { } this.sessionMappingStorage.addSessionById(token, session); } public void destroySession(HttpServletRequest request) { log.info("destroySession begin---"); String logoutMessage = CommonUtils.safeGetParameter(request,this.logoutParameterName); if (this.log.isTraceEnabled()) { this.log.trace("Logout request: " + logoutMessage); } String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex"); if (CommonUtils.isNotBlank(token)) { HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token); if (session != null) {//session在当前节点 log.info("destroySession session在当前节点------"); String sessionID = session.getId(); if (this.log.isDebugEnabled()) { this.log.debug("Invalidating session [" + sessionID + "] for token [" + token + "]"); } try { session.invalidate(); } catch (IllegalStateException e) { this.log.debug("Error invalidating session.", e); } }else {//session不在当前节点 log.info("destroySession session不在当前节点------"); //清除其他节点,采用广播形式发送http请求 destroySessionOfClusterNodes(token); } } log.info("destroySession end---"); } /** * 采用广播形式发送http请求,通知其他节点清除session * @author xubo 2018-3-21 * @param token */ private void destroySessionOfClusterNodes(String token) { //广播到所有节点 log.info("destroySessionOfClusterNodes--begin-----:" + token); if(this.clusterNodeUrls != null && this.clusterNodeUrls.length() > 0){ log.info(clusterNodeUrls); String[] clusters = this.clusterNodeUrls.split(","); for (String url : clusters) { HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPostReq = new HttpPost(url); List<NameValuePair> paramList = new ArrayList<NameValuePair>(); paramList.add(new BasicNameValuePair(this.logoutParameterClusterName,"true")); paramList.add(new BasicNameValuePair(this.artifactParameterName,token)); try { httpPostReq.setEntity(new UrlEncodedFormEntity(paramList)); httpClient.execute(httpPostReq); } catch (Exception e) { log.debug("Error destroySessionOfClusterNodes.",e); }finally{ HttpClientUtils.closeQuietly(httpClient); } } } log.info("destroySessionOfClusterNodes--end-----:" + token); } /** * 接收从其它节点的通知,清除session * @author xubo 2018-3-21 * @param request */ public void destroySessionFromClusterNode(HttpServletRequest request){ String token = request.getParameter(this.artifactParameterName); log.info("destroySessionFromClusterNode----begin---:" + token); if(CommonUtils.isNotBlank(token)){ final HttpSession session = sessionMappingStorage.removeSessionByMappingId(token); if(session != null){ String sessionID = session.getId(); if(log.isDebugEnabled()){ log.debug("Invalidating session[" + sessionID +"] for token [" + token + "]"); } try { session.invalidate(); } catch (final IllegalStateException e) { log.debug("Error invalidating session",e); } } } log.info("destroySessionFromClusterNode----end---:" + token); } private boolean isMultipartRequest(HttpServletRequest request) { return (request.getContentType() != null) && (request.getContentType().toLowerCase().startsWith("multipart")); } }