CAS 单点登出失效的问题(源码跟踪)

一、环境说明

服务端:cas-server-3.5.2

客户端:cas-client-3.2.1+spring mvc

说明:服务端与客户端均是走的Https

客户端配置文件:

applicationContext-cas.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:sec="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">
    <!-- 读取配置文件 -->
    <context:property-placeholder location="classpath*:cas.properties" ignore-unresolvable="true" />

    <bean name="singleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" />

    <bean name="authenticationFilter" class="org.jasig.cas.client.authentication.AuthenticationFilter" p:renew="false"
        p:gateway="false" p:casServerLoginUrl="${cas.server.login.url}" p:serverName="${server.name}" />

    <bean name="ticketValidationFilter" class="org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter"
        p:redirectAfterValidation="true" p:serverName="${server.name}">
        <property name="ticketValidator">
            <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
                <constructor-arg index="0" value="${cas.server.url}" />
            </bean>
        </property>
    </bean>

    <bean name="httpServletRequestWrapperFilter" class="org.jasig.cas.client.util.HttpServletRequestWrapperFilter" />

    <bean name="assertionThreadLocalFilter" class="org.jasig.cas.client.util.AssertionThreadLocalFilter" />

</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    metadata-complete="true">
    <display-name>Archetype Created Web Application</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:/applicationContext-cas.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- https://wiki.jasig.org/display/CASC/Configuring+Single+Sign+Out -->
    <!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 The SingleSignOutFilter can affect character
        encoding. -->
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>

    <!-- Filter 定义 -->
    <!-- Character Encoding filter -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- Spring mvc -->
    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>cas oss info</servlet-name>
        <servlet-class>com.gqshao.cas.servlet.InfoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>cas oss info</servlet-name>
        <url-pattern>/info</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>cas oss logout</servlet-name>
        <servlet-class>com.gqshao.cas.servlet.LogoutServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>cas oss logout</servlet-name>
        <url-pattern>/logout</url-pattern>
    </servlet-mapping>

    <!--1.用于单点退出 -->
    <filter>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>singleSignOutFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--2.负责Ticket校验 -->
    <filter>
        <filter-name>CAS Validation Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>ticketValidationFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Validation Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 3. 单点登录验证 -->
    <filter>
        <filter-name>CAS Authentication Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>authenticationFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Authentication Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--4. CAS HttpServletRequest Wrapper Filter 这个是HttpServletRequet的包裹类,让他支持getUserPrincipal,getRemoteUser方法来取得用户信息 -->
    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>httpServletRequestWrapperFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--5. CAS Assertion Thread Local Filter 这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息 -->
    <filter>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>assertionThreadLocalFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

二、问题描述

服务端与客户端的部署从网上找了很多资料,总算是部署好了,数据库中查询用户、单点登录都没有问题,在测试单点登出时发现总是不能进入CAS客户端单点登录的方法,

执行单点登出后 通过跟踪源码发现

SingleSignOutFilter.java类dofilter方法中以下代码块始终不能进入

        if (handler.isTokenRequest(request)) {
            handler.recordSession(request);
        } else if (handler.isLogoutRequest(request)) {
            handler.destroySession(request);
            // Do not continue up filter chain
            return;
        } else {
            log.trace("Ignoring URI " + request.getRequestURI());
        }

        filterChain.doFilter(servletRequest, servletResponse);

于是排查服务端相关代码

服务端登出逻辑主要是通过 LogoutController.java 类handleRequestInternal 方法中的代码段

        if (ticketGrantingTicketId != null) {
            this.centralAuthenticationService
                .destroyTicketGrantingTicket(ticketGrantingTicketId);

            this.ticketGrantingTicketCookieGenerator.removeCookie(response);
            this.warnCookieGenerator.removeCookie(response);
        }

其中

centralAuthenticationService
                .destroyTicketGrantingTicket(ticketGrantingTicketId);

负责销毁TGT,通过调用 ————————>CentralAuthenticationServiceImpl.java类中的destroyTicketGrantingTicket方法————————>  ticket.expire();见:
        Assert.notNull(ticketGrantingTicketId);

        if (log.isDebugEnabled()) {
            log.debug("Removing ticket [" + ticketGrantingTicketId + "] from registry.");
        }
        final TicketGrantingTicket ticket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);

        if (ticket == null) {
            return;
        }

        if (log.isDebugEnabled()) {
            log.debug("Ticket found.  Expiring and then deleting.");
        }
        ticket.expire();
        this.ticketRegistry.deleteTicket(ticketGrantingTicketId);

——————>调用

AbstractWebApplicationService.java类中的 logOutOfService方法,------------------主脚出现了
        if (this.httpClient != null) {
            return this.httpClient.sendMessageToEndPoint(getOriginalUrl(), logoutRequest, true);
        }

该处通过基于

HttpURLConnection

实现的HttpClient.java类向客户端发送数据,HttpClient中主要代码

        public Boolean call() throws Exception {
            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.setRequestMethod("POST");
                connection.setReadTimeout(this.readTimeout);
                connection.setConnectTimeout(this.connectionTimeout);
                connection.setInstanceFollowRedirects(this.followRedirects);
                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 SocketTimeoutException e) {
                log.warn("Socket Timeout Detected while attempting to send message to [" + url + "].");
                return false;
            } catch (final Exception e) {
                log.warn("Error Sending message to url endpoint [" + url + "].  Error is [" + e.getMessage() + "]");
                return false;
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (final IOException e) {
                        // can‘t do anything
                    }
                }
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }

并未针对启用SSL的客户端进行处理,故始终无法向客户端发送消息,为了方便起见,直接对源码进行修改来支持SSL的客户端

修改的部分

if(url.startsWith("https:"))
                {
                    TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }

                        public void checkClientTrusted(
                                java.security.cert.X509Certificate[] certs, String authType) {
                        }

                        public void checkServerTrusted(
                                java.security.cert.X509Certificate[] certs, String authType) {
                        }
                    } };
                    httpsconnection = (HttpsURLConnection) logoutUrl.openConnection();
                    httpsconnection.setDefaultHostnameVerifier(new NullHostNameVerifier());
                    SSLContext sc = SSLContext.getInstance("SSL");
                    sc.init(null, trustAllCerts, new java.security.SecureRandom());
                    httpsconnection
                            .setDefaultSSLSocketFactory(sc.getSocketFactory());
                    httpsconnection.setDoInput(true);
                    httpsconnection.setDoOutput(true);
                    httpsconnection.setRequestMethod("POST");
                    httpsconnection.setReadTimeout(this.readTimeout);
                    httpsconnection.setConnectTimeout(this.connectionTimeout);
                    httpsconnection.setInstanceFollowRedirects(this.followRedirects);
                    httpsconnection.setRequestProperty("Content-Length", Integer.toString(output.getBytes().length));
                    httpsconnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                    final DataOutputStream printout = new DataOutputStream(httpsconnection.getOutputStream());
                    printout.writeBytes(output);
                    printout.flush();
                    printout.close();

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

                    while (in.readLine() != null) {
                        // nothing to do
                    }
                }

至此,解决了当下遇到的问题

整个过程走了不少弯路,能读懂源码,能正确通过DEBUG排除问题真的是一项需要持续锻炼的技能

在此mark一下

文中源码阅读部分感谢作者eg366

http://blog.csdn.net/eg366/article/details/14054949



				
时间: 2024-07-29 23:20:59

CAS 单点登出失效的问题(源码跟踪)的相关文章

cas单点登出

由于项目需求要实现单点登出需要在网上找了N久终于实现单点登出. 使用cas-server-core-3.3.3.jar(CAS Server 3.3.3) 使用cas-client-core-3.1.3.jar(CAS Client 3.1.3) 项目结合CAS SpringSecurity SSH 普通项目(没有结合Spring Security)的可以在web.xml中加入如下代码 [xhtml] view plaincopy <filter> <filter-name>CAS

SSO CAS单点登录搭建详细步骤及源码

1.因为是本地模拟sso环境,而sso的环境测试需要域名,所以需要虚拟几个域名出来,步骤如下: 2.进入目录C:\Windows\System32\drivers\etc 3.修改hosts文件 127.0.0.1 jeesz.cn 127.0.0.1 sso1.jeesz.cn 127.0.0.1 sso2.jeesz.cn 4.生成认证证书 注意:我们可以根据sso单点登录的架构图可以看到,在客户端和服务端进行交互的时候,是需要认证的,在这里我们使用jdk中的keytool方案生成证书(一般

源代码解读Cas实现单点登出(single sign out)功能实现原理

关于Cas实现单点登入(single sing on)功能的文章在网上介绍的比较多,想必大家多多少少都已经有所了解,在此就不再做具体介绍.如果不清楚的,那只能等我把single sign on这块整理出来后再了解了.当然去cas官方网站也是有很多的文章进行介绍.cas官网http://www.ja-sig.org/products/cas/. ok,现在开始本文的重点内容讲解,先来了解一下cas 实现single sign 的原理,如图所示: 登出原理图 从第一张图中,当一个web浏览器登录到应

源代码解读Cas实现单点登出(single sign out)功能实现原理--转

关于Cas实现单点登入(single sing on)功能的文章在网上介绍的比较多,想必大家多多少少都已经有所了解,在此就不再做具体介绍.如果不清楚的,那只能等我把single sign on这块整理出来后再了解了.当然去cas官方网站也是有很多的文章进行介绍.cas官网http://www.ja-sig.org/products/cas/. ok,现在开始本文的重点内容讲解,先来了解一下cas 实现single sign out的原理,如图所示: 图一                     

单点登录CAS使用记(六):单点登出、单点注销

单点登出基本上没有啥配置 直接在原来logout的时候,重定向到Cas-Server的logout方法 @RequestSecurity @RequestMapping(value = "loginout", method = { RequestMethod.GET, RequestMethod.POST }) public String loginout(HttpSession session) { session.invalidate(); return "redirec

【试水CAS-4.0.3】第08节_CAS客户端配置单点登出

本文内容包括配置单点登出.登出后自动跳转指定资源.CASServer禁用单点登出等 /** * @see ------------------------------------------------------------------------------------------------------------------------ * @see CAS客户端配置单点登出 * @see 与单点登录相对应,通过CASServer登出所有的CASClient,登录的URL是/login,

LayoutInflater效率分析及源码跟踪

一.效率分析 测试设备 测试设配:魅族MX4 操作系统:Android5.1操作系统 CPU型号:联发科MT6595 内存: 2GB 测试方法 使用LayoutInflater对3组不同复杂度的xml布局进行解析,每次解析100次,测试10次,求其100次的平均运行时间.单位为ms. 测试结果 | | Xml文件 |Time(ms/100)| |---------------|-----------------------|------------| |第一组(简单) |深度2节点4 属性30个

1 weekend110的hdfs源码跟踪之打开输入流 + hdfs源码跟踪之打开输入流总结

3种形式的元数据,fsimage是在磁盘上,meta.data是在内存上, 我们继续,前面呢,断点是打在这一行代码处, FileSystem fs = FileSystem.get(conf); weekend110的hdfs下载数据源码跟踪铺垫  +  hdfs下载数据源码分析-getFileSystem 现在,开始weekend110的hdfs源码跟踪之打开输入流 1.  清掉之前,所有的断点, dfs是fs的成员, 此刻,断点过不去了,为什么?因为,这需要在服务器上运行. 由此可见,new

Java源码跟踪阅读技巧

转:https://www.jianshu.com/p/ab865109070c 本文基于Eclipse IDE 1.Quick Type Hierarchy 快速查看类继承体系. 快捷键:Ctrl + T Quick Type Hierarchy.png 查看类很多人可能都知道,可源码阅读的时候更多用来查看方法体系更重要,可以方便快速的定位到方法的实现类.如: getBean.png 此时如果想查看getBean()方法如何实现,可能会让你失望.结果如下: image.png 进入到了Bean