cas-client无法全部退出问题

本文介绍了解决CAS单点登录环境下登出问题的方法。针对单一服务器和集群环境下的问题,提供配置hosts文件和修改CAS客户端源码两种方案,并详细说明了如何通过广播方式确保集群中所有节点成功登出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

client是用域名访问的

1.如果客户端是一台服务器,查看cas-server的debug日志,发现server在客户端登录成功后,会记录下客户端的域名和票据,当用户从客户端退出时,会调server的logout,server取出所有同一票据的客户端域名,循环清除session,但如果server服务器没有配置客户端域名对应的ip host文件,server找不到该域名对应的主机,因此清不掉session,导致无法退出。

解决方案:在server服务器配置host文件,配置客户端域名和ip映射。

2.如果客户端是集群模式

 

  1. 单点注销为保证注销成功,需保证配置的域名、IP地址在单点登录服务端可以请求到,这样单点登录在做回调的时候才可以通过存储st信息把登录的客户端注销成功
  2. 集群环境下单点登录注销会存在注销不成功情况,原因是单点登录服务在做回调的时候回可能分发在非登录的tomcat服务上

 

解决方案:

 

1:Nginx+tomcat+redis session共享,该实现方案,不管注销落在哪个tomcat都可以把redis中session注销成功。(未实践)

 

2:修改cas-client源码,单点登录客户端集群采用广播的方式,注销时向所有节点发起注销session操作。(实践成功)

基于cas-client-core-3.4.1实现

参考 http://blog.youkuaiyun.com/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:\n" + 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"));
    }
}

 

转载于:https://www.cnblogs.com/jason123/p/8618624.html

<think>好的,我现在需要帮助用户解决关于CAS overlay template 5.3版本的退出方法或配置的问题。首先,我需要回顾CAS的登出机制,确保理解其工作原理。CAS的登出通常涉及单点登出(SLO),这意味着用户从一个应用登出后,其他所有通过CAS登录的应用也会自动登出。 用户提到使用的是5.3版本,所以我要确认这个版本的相关配置是否有变化。根据引用[4],CAS 5.3.x基于JDK8,而6.x需要更高版本,所以配置方法应该还是类似的,但需要参考5.3的文档。接下来,我需要检查用户的配置步骤是否正确,特别是application.properties或cas.properties中的相关设置。 首先,单点登出功能默认是启用的,但用户可能需要确认是否显式启用了它。查看引用[5]提到的github仓库,可以找到5.3版本的配置示例。然后,前端登出链接通常指向/cas/logout,但需要确保应用正确重定向到这个端点。后端方面,CAS Server需要配置登出后的重定向URL,可能需要在配置文件中添加allowed的URL列表,如引用[1]中提到的子系统Drupal和Moodle的集成,可能需要将这些应用的登出回调URL加入白名单。 另外,CAS支持两种登出方式:前端通道和后端通道。前端通过浏览器重定向,后端通过服务发送请求。用户可能需要配置这两个通道,确保所有子系统都能正确接收登出通知。例如,在Spring Security集成中(引用[3]),可能需要设置LogoutFilter来处理CAS的登出请求。 还要考虑CAS Server的版本特定配置,比如在5.3中是否有一些过时的配置项需要替换。例如,引用的cas-server-webapp-tomcat-5.2.1.war(引用[2])可能和5.3的配置有差异,需要注意版本兼容性。此外,CAS的文档或GitHub仓库中的示例可能有具体的配置说明,需要参考这些资源。 最后,用户可能需要测试登出流程,确认所有相关应用都能正确终止会话。如果遇到问题,可能需要检查日志文件,查看是否有登出请求被拒绝或配置错误。例如,未正确配置allowedService参数可能导致登出后无法重定向到指定页面。</think>### CAS Overlay Template 5.3 登出配置与方法 #### 1. **单点登出(SLO)配置** CAS 5.3 默认支持单点登出(Single Logout, SLO),需确保以下配置已启用: - 在 `application.properties` 或 `cas.properties` 中添加: ```properties cas.logout.followServiceRedirects=true cas.logout.redirectParameter=service ``` 此配置允许登出后重定向到指定服务[^1]。 #### 2. **前端登出链接** 在客户端应用中,登出链接应指向 CAS Server 的 `/cas/logout` 端点,并携带 `service` 参数指定登出后跳转的地址: ```html <a href="https://cas-server-domain:8443/cas/logout?service=https://your-client-app/home">退出登录</a> ``` #### 3. **后端登出回调配置** CAS Server 需配置允许的登出回调服务(避免未授权跳转): - 修改 `deployerConfigContext.xml` 或通过属性文件配置: ```properties cas.logout.allowedServiceRedirects=https://your-client-app/.* ``` #### 4. **单点登出协议支持** 确保客户端应用(如 Drupal、Moodle)支持 CAS 的 SLO 协议: - 对于 Spring Boot 应用,需配置 `SingleLogoutFilter` 和 `LogoutFilter`[^3]: ```java @Configuration public class CasConfig { @Autowired private CasProperties casProperties; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl(casProperties.getServerLogoutUrl()) ); return http.build(); } } ``` #### 5. **验证登出流程** 1. 用户从客户端发起登出请求,重定向到 `https://cas-server:8443/cas/logout`。 2. CAS Server 销毁全局会话,并向所有已登录的客户端发送 SAML 或 CAS 协议的登出请求。 3. 客户端应用接收登出通知后,销毁本地会话。 #### 6. **常见问题排查** - **登出后无法重定向**:检查 `allowedServiceRedirects` 是否包含目标服务 URL。 - **单点登出失效**:确认客户端应用正确实现了 `SingleLogoutFilter` 或类似拦截器[^3]。 - **CAS Server 日志分析**:查看 `cas.log` 中是否包含登出请求记录。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值