`

【原创】CAS总结之单点退出篇(CAS到底有没有实现单点退出?)

阅读更多

 

         CAS 总结之单点退出篇

 

CAS 到底有没有实现单点退出?本人阅读了 JA-SIG CAS v3.3 ,以及 JA-SIG CAS-CLIENT 3.1.9 的源代码,发现表面上好像实现了单点退出,但实际上却没有真正实现。

 

现将 CAS logout 接口的实现整理如下。

 

首先看一下 CAS logout 功能的序列图。



                                                 CAS logout 功能的序列图

 

 

从图中可以看出, CAS logout 功能有两步,一是调用 TGT 对象中各个 Service logoutOfService 方法,二是在缓存中清除 TGT 对象。

 

我们看一下 CAS AbstractWebApplicationService logoutOfService 方法的实现。

 

public synchronized boolean logOutOfService(final String sessionIdentifier) {
        if (this.loggedOutAlready) {
            return true;
        }

        LOG.debug("Sending logout request for: " + getId());

        final String logoutRequest = "<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\""
            + GENERATOR.getNewTicketId("LR")
            + "\" Version=\"2.0\" IssueInstant=\"" + SamlUtils.getCurrentDateAndTime()
            + "\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@</saml:NameID><samlp:SessionIndex>"
            + sessionIdentifier + "</samlp:SessionIndex></samlp:LogoutRequest>";
        
        this.loggedOutAlready = true;
        
        if (this.httpClient != null) {
            return this.httpClient.sendMessageToEndPoint(getOriginalUrl(), logoutRequest);
        }
        
        return false;
    }

 

 

    另外 HttpClient 类中 sendMessageToEndPoint 方法的实现如下:

 

public boolean sendMessageToEndPoint(final String url, final String message) {
        HttpURLConnection connection = null;
        BufferedReader in = null;
        try {
            if (log.isDebugEnabled()) {
                log.debug("Attempting to access " + url);
            }
            final URL logoutUrl = new URL(url);
            final String output = "logoutRequest=" + URLEncoder.encode(message, "UTF-8");

            connection = (HttpURLConnection) logoutUrl.openConnection();
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setReadTimeout(this.readTimeout);
            connection.setConnectTimeout(this.connectionTimeout);
            connection.setRequestProperty("Content-Length", ""
                + Integer.toString(output.getBytes().length));
            connection.setRequestProperty("Content-Type",
                "application/x-www-form-urlencoded");
           
            final DataOutputStream printout = new DataOutputStream(connection
                .getOutputStream());
            printout.writeBytes(output);
            printout.flush();
            printout.close();

            in = new BufferedReader(new InputStreamReader(connection
                .getInputStream()));

            while (in.readLine() != null) {
                // nothing to do
            }
            
            if (log.isDebugEnabled()) {
                log.debug("Finished sending message to" + url);
            }
            
            return true;
        } catch (final Exception e) {
            log.error(e,e);
            return false;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (final IOException e) {
                    // can't do anything
                }
            }
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

 

 

通过阅读代码可以发现, logOutOfService 方法是调用 serivce originUrl 接口,利用 HttpURLConnection 的方式把退出请求发送给 service 注意没有给 HttpURLConnection 设置 requestMethod ,因此用的是默认的 GET 方法。 sessionIdentifier 的值是 ST 的值。 service response 中会解析 logoutRequest 参数中的 sessionIdentifier 的值,然后把 sessionIdentifier 标识的 session kill 掉就可以了。这时我们发现,原理上是可以单点退出的。

 

再来看客户端的实现,客户端和单点退出有关的类包括:

  • SingleSignOutFilter :用来解析 logoutRequest 参数。
  • SessionMappingStorage :一个接口,定义了 Session 存储器的方法。  


  • HashMapBackedSessionMappingStorage Session 存储器的实现类,定义了 2 Map 来存储 Session

MANAGED_SESSIONS:key ST 的值, value session

ID_TO_SESSION_KEY_MAPPING key sessionId,value ST 的值。

 

  • SingleSignOutHttpSessionListener :此 Listener 监听到 session destroy 的事件后,用 sessionId 从上述 ID_TO_SESSION_KEY_MAPPING 中取出 ST 的值,然后依据 ST 的值从 MANAGED_SESSIONS 中取出 session, 然后就可以执行其 invalidate 方法了。

 

我们看一下 SingleSignOutFilter 中的 doFilter 方法。

 

public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
       
        if ("POST".equals(request.getMethod())) {
            final String logoutRequest = CommonUtils.safeGetParameter(request, "logoutRequest");

            if (CommonUtils.isNotBlank(logoutRequest)) {

                if (log.isTraceEnabled()) {
                    log.trace ("Logout request=[" + logoutRequest + "]");
                }
                
                final String sessionIdentifier = XmlUtils.getTextForElement(logoutRequest, "SessionIndex");

                if (CommonUtils.isNotBlank(sessionIdentifier)) {
                	final HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier);

                	if (session != null) {
                        String sessionID = session.getId();

                        if (log.isDebugEnabled()) {
                            log.debug ("Invalidating session [" + sessionID + "] for ST [" + sessionIdentifier + "]");
                        }
                        
                        try {
                        	session.invalidate();
                        } catch (final IllegalStateException e) {
                        	log.debug(e,e);
                        }
                	}
                  return;
                }
            }
        } else {
        	final String artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName);
            final HttpSession session = request.getSession(false);

            if (session != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Storing session identifier for " + session.getId());
                }
                if (CommonUtils.isNotBlank(artifact)) {
                    try {
                        SESSION_MAPPING_STORAGE.removeBySessionById(session.getId());
                    } catch (final Exception e) {
                        // ignore if the session is already marked as invalid.  Nothing we can do!
                    }
                    SESSION_MAPPING_STORAGE.addSessionById(artifact, session);
                }
            } else {
                log.debug("No Session Found, so ignoring.");
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
}

 

       非常奇怪,这个方法首先判断了 request 的方法,如果是 POST, 则会执行 session.invalidate 方法,从而实现单点退出,如果是 GET ,则只会在存在 ticket 参数的情况下,把 session 存进 SessionMappingStorage ,永远也不执行 session.invalidate 方法,不能单点退出。因为 CAS logoutRequest 请求是用 GET 方法发过来的,所以,单点登录功能没有实现。

 

       本人对 SingleSignOutFilter 中的 doFilter 方法重写了一下,代码如下。经验证,确实实现了单点退出。

 

public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        
    	final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final String logoutRequest = CommonUtils.safeGetParameter(request, "logoutRequest");
        Enumeration ff = request.getParameterNames();
        String a = request.getQueryString();
        if (CommonUtils.isNotBlank(logoutRequest)) {
        	 final String sessionIdentifier = XmlUtils.getTextForElement(logoutRequest, "SessionIndex");

             if (CommonUtils.isNotBlank(sessionIdentifier)) {
             	final HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier);

             	if (session != null) {
                     String sessionID = session.getId();                   
                     try {
                     	session.invalidate();
                     } catch (final IllegalStateException e) {
                     	
                     }
             	}
             }
         }
        
        else{
        	final String artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName);
            final HttpSession session = request.getSession(false);
            
            if (CommonUtils.isNotBlank(artifact) && session!=null) {
                try {
                    SESSION_MAPPING_STORAGE.removeBySessionById(session.getId());
                } catch (final Exception e) {
                    
                }
                SESSION_MAPPING_STORAGE.addSessionById(artifact, session);
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

 

      另外还需要注意的是,因为客户端部署了三个 Filter:AuthenticationFilter ServiceValidationFilter SingleSignOutFilter ,所以三个 Filter 的顺序需要注意,我的顺序为 AuthenticationFilter ServiceValidationFilter SingleSignOutFilter ,一开始不行,因为执行退出功能时, CAS 服务端用 HttpURLConnection 访问客户端,没有把 sessionId 代过来,所以在 AuthenticationFilter 中就被 redirect CAS 了,到不了 SingleSignOutFilter ,我做了一个改动,就是在 AuthenticationFilter 中的 redirectUrl ,后面加上了 session ID 的值,格式如:“ ;jsessionid= , 这样 CAS 端解析 service 参数生成 WebApplicationService 时, orginUrl 里就有 sessionId 了。

 

虽然这样就实现了单点退出,但我感觉 CAS 的这种采用 Filter 的方式太麻烦了,不如让客户应用提供一个 callback url ,CAS 直接调用这个 callback url 来退出更好一些,但这样的话,对 CAS 的改动非常大。

 

     本人博客 :http://zhenkm0507.iteye.com

  • 大小: 14.4 KB
分享到:
评论
18 楼 Readiay 2016-10-11  
                
17 楼 zqj15011 2014-05-06  
前台如何实现啊
16 楼 sodarfish 2012-06-18  


tiger121987 写道
cas的client应用是集群,现在遇到也实现不了单点退出,如果把集群的服务器撤下只剩一台,单点退出又可以,多台就不行。有什么好的办法解决吗,不管是tomcat集群还是was集群都一样。


一个简单的方法: 各应用提供一个url ,访问这个url就会把当前域下 cookie 都设置过期,并把当前session 失效掉。

这样cas只需要生成一个页面,页面中分别get请求各个系统的这个url就可以了,可以生成多个iframe并指定其src 来实现。

这方法和cas的机制没多大关系,比较简单,而且在业务系统有集群的情况下也可以工作良好。
15 楼 tiger121987 2012-05-11  
cas的client应用是集群,现在遇到也实现不了单点退出,如果把集群的服务器撤下只剩一台,单点退出又可以,多台就不行。有什么好的办法解决吗,不管是tomcat集群还是was集群都一样。
14 楼 张小宇 2012-02-10  
castte 写道
遇到了一样的问题,在自己的logout方法中增加了:getHttpSession().removeAttribute(org.jasig.cas.client.util.AbstractCasFilter.CONST_CAS_ASSERTION);

可以实现单点登出


是在DoFilter方法中加入这句话么? 能具体点么?
13 楼 张小宇 2012-02-10  
碰到了类似问题,按照你说的方法改了下,仍然存在问题,是不是修改的地方不止doFilter方法呢?
12 楼 castte 2011-11-11  
遇到了一样的问题,在自己的logout方法中增加了:getHttpSession().removeAttribute(org.jasig.cas.client.util.AbstractCasFilter.CONST_CAS_ASSERTION);

可以实现单点登出
11 楼 waitingmyself 2010-10-22  
cas-client-core-3.1.10.jar  客户端断点发现发过来的确实是post方式
10 楼 JasonMing 2010-06-21  
对了,还有你的图(CAS logout 功能的序列图)无效了~~~补个图吧~~~我觉得有图会舒服一点~~~
9 楼 JasonMing 2010-06-21  
zhenkm0507 写道
非常感谢kevindurant关注!!不过在我的实现里面,SingleSignOutFilter 放在最后也是可以的,前提是做过更改。没必要说顺序都搞错了,ok?


默认配置是sign out的放在最前面的,因为这样就可以避免被Authentication Filter拦截了。

不过我用了一个笨方法。。。
自己写了个filter,当访问项目的/logout地址的时候直接清除Cookie和Session。

不过这样项目多了就郁闷了。。。

还是期待CAS4吧,我在他们的SVN上看到应该差不多完成了。
8 楼 ferreousbox 2010-03-02  
估计是忘记写了,呵呵,我在3.3.5中看到的代码是有的,如下:

URL logoutUrl = new URL(url);
            String output = (new StringBuilder()).append("logoutRequest=").append(URLEncoder.encode(message, "UTF-8")).toString();
            connection = (HttpURLConnection)logoutUrl.openConnection();
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            connection.setReadTimeout(readTimeout);
            connection.setConnectTimeout(connectionTimeout);
            connection.setRequestProperty("Content-Length", Integer.toString(output.getBytes().length));
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            DataOutputStream printout = new DataOutputStream(connection.getOutputStream());
            printout.writeBytes(output);
            printout.flush();
            printout.close();
7 楼 乐在地狱 2010-02-09  
<filter>
      <!-- CAS 登出-->
         <filter-name>CAS Single Sign Out Filter</filter-name>
          <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
         <filter-name>CAS Single Sign Out Filter</filter-name>
         <url-pattern>/logout</url-pattern>
</filter-mapping>

这样配置拦截器 无效 是什么原因?
6 楼 zhenkm0507 2009-12-30  
非常感谢kevindurant关注!!不过在我的实现里面,SingleSignOutFilter 放在最后也是可以的,前提是做过更改。没必要说顺序都搞错了,ok?
5 楼 kevindurant 2009-12-30  
AuthenticationFilter 、 ServiceValidationFilter 、 SingleSignOutFilter

顺序都搞错了。。SingleSignOutFilter  要放在最前面
4 楼 kevindurant 2009-12-30  
只能说你没有研究透彻源码。。。。。
3 楼 zhenkm0507 2009-12-15  
多谢sillycat回复啊,谢谢你提醒了我单点退出时还要考虑集群的问题!
2 楼 sillycat 2009-12-13  
我测试CAS3.3.3是实现了单点退出的。过程和你测试的一样,CAS通知各个CLIENT,各个CLIENT里面有filter来接受到SAML2.0的请求,然后实现了退出。不过我遇到的问题是,我是采用loadbalance来配置的各个client和CAS,所以登出的时候这个SAML2.0已经不知道把退出的请求发给loadbalance后面的哪个client了,这里又不是IE,而是server向各个地方发SAML2的请求,所以也不能session stick,所以我是采用比较土的办法,改写了退出的地方,让它一个一个去调用各个client的退出。我的client是用的spring security2.0。
CAS的论坛上倒是在说CAS4.0出来以后要实现真正的单点退出,解决这种集群里面的问题,不光是考虑CAS认证中心的集群,还要考虑到各个CLIENT也可能是个集群。不过这个可能就不知道啥时候去了,我一直关心CAS的站点,都没有看到要出4.0.。。。。
1 楼 zhenkm0507 2009-12-12  
大家有没有碰到类似的问题,是如何解决的?

相关推荐

    单点登陆实现(完全跨域、单点退出)

    在这个例子中,我们基于Spring MVC、Maven、WebService和Memcached来实现一个功能完善的单点登录系统,同时支持完全跨域和单点退出。 1. **Spring MVC**: Spring MVC是Spring框架的一个模块,主要用于构建Web应用...

    spring boot整合CAS Client实现单点登陆验证的示例

    Spring Boot 整合 CAS Client 实现单点登录验证的示例 Spring Boot 整合 CAS Client 是一种流行的解决方案,用于实现单点登录(Single Sign-On,简称 SSO)。在多个应用系统中,用户只需要登录一次就可以访问所有...

    CAS单点登录,退出后ticket失效报出异常解决办法—换jar包

    CAS单点登录,退出后ticket失效报出异常解决办法——换jar包 把客户端的 casclient.jar 包换成我的这个。

    cas实现单点登录 功能

    总结,CAS 作为一个强大的单点登录解决方案,为企业级应用提供了高效的身份验证管理。通过理解 CAS 的核心原理和实践步骤,开发者可以轻松地在 Java 应用中集成 CAS,提升系统的安全性与用户体验。

    cas实现单点登录,登出(java和php客户端)

    总之,CAS单点登录系统为多应用环境提供了一种高效的身份验证解决方案。通过Java和PHP客户端的集成,可以在多种技术栈的项目中实现一致的用户体验,同时也简化了用户管理和权限控制。在实际应用中,应根据项目需求...

    struts2+spring+cas单点登录

    使用struts2+spring+cas实现的单点登录功能,里面包括cas-server3.5.2项目一个,cas-client3.2.1 web项目两个,数据库脚本,请按照里面的说明文档进行部署,希望你们也能配置成功。

    CAS单点登录,退出后ticket失效报出异常解决办法——换jar包

    CAS单点登录,退出后ticket失效报出异常解决办法——换jar包 把客户端的 casclient.jar 包换成我的这个。

    CAS多数据库配置单点登录

    * 单点退出监听器:org.jasig.cas.client.session.SingleSignOutHttpSessionListener * 单点登出过滤器:org.jasig.cas.client.session.SingleSignOutFilter * 认证过滤器:org.jasig.cas.client.authentication....

    CAS单点登录(SSO)服务端自定义认证+CAS客户端配置+CAS完整使用文档+CAS4.2.7 cas-serv服务端源码 cas-client客户端源码

    CAS(Central Authentication Service)是Java开发的一个开源的单点登录(Single Sign-On,简称SSO)框架,主要用于解决网络应用中的身份验证问题。本压缩包提供了CAS服务端自定义认证的实现,以及CAS客户端的配置...

    CAS单点登录Demo

    在本文中,我们将深入探讨CAS单点登录的基本原理、工作流程以及如何通过提供的Demo进行实践操作。 **CAS基本原理** CAS的核心思想是用户只需在一个应用系统中验证身份,之后访问其他所有支持CAS的应用系统时都不再...

    单点登录cas参考

    ### CAS单点登录框架 #### 一、CAS简介 单点登录(Single Sign On,简称SSO)是一种用户登录管理方式,用户只需要在一处登录,即可访问多个应用系统。这种方式为用户和系统管理员提供了便利,增强了用户体验,简化...

    sso单点登录之cas配置全过程

    此文档自己亲手从0开始一步一步配置的详尽过程,其中包括keytool创建 、ticket、tomcat配置cas、自定义登录页面,处理服务器返回的乱码,服务退出、cas服务器返回多数据等等文档 包括SSO原理图,以及认证流程图等

    discuz x2.5和cas集成,实现cas单点登录 sso

    X2.5与CAS(Central Authentication Service)集成是一种常见的身份验证解决方案,它能够实现单点登录(Single Sign-On, SSO)功能。在SSO系统中,用户只需登录一次,就可以在多个相互独立的应用系统之间自由切换,...

    使用CAS在Tomcat中实现单点登录参考代码及配置

    ### 使用CAS在Tomcat中实现单点登录的关键知识点 #### 一、CAS简介与特性 - **CAS**(Central Authentication Service)是由耶鲁大学发起的一个开源项目,它为Web应用程序提供了一种简单可靠且功能强大的单点登录...

    cas单点登录

    CAS(Central Authentication Service...总之,CAS单点登录是提高用户体验和安全管理的重要工具,其核心在于集中化的身份验证和票证管理。理解和掌握CAS的工作原理及实施要点,有助于在实际项目中有效地应用这一技术。

    spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制

    CAS(Central Authentication Service)是一种开源的身份验证协议,提供了单点登录和单点退出功能。CAS Server 是一个独立的服务器,负责处理身份验证请求。 Spring Boot 集成 Shiro+Cas 要在 Spring Boot 应用...

    单点登录cas服务器demo及springboot客户端demo

    在实际应用中,单点退出(Single Sign-Out,简称SSO)也是重要的一环。当用户在一个应用中注销时,SSO机制会通知其他所有关联应用,同步注销状态,确保用户在整个系统中的会话都被终止。 总结起来,这个"单点登录...

    cas.zip_CAS_单点登录

    在实现CAS单点登录的过程中,主要涉及以下几个核心知识点: 1. **CAS服务器**:CAS服务器是整个单点登录的核心,它负责处理用户的登录请求,验证用户的凭证,并生成服务票证(Ticket Granting Ticket,TGT)。一旦...

    CAS单点登录 for Tomcat

    CAS(Central Authentication Service...总的来说,这些文档提供了全面的指导,帮助开发者在Linux上的Tomcat环境中实现CAS单点登录,涵盖了从基础理论到具体实践的各个层面,对于理解SSO机制和实际操作具有很大的价值。

Global site tag (gtag.js) - Google Analytics