一、环境说明
服务端: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-10-07 05:26:43