1、環境說明java
服務端:cas-server-3.5.2web
客戶端:cas-client-3.2.1+spring mvcspring
說明:服務端與客戶端均是走的Https數據庫
客戶端配置文件:spring-mvc
applicationContext-cas.xmlsession
<?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.xmlmvc
<?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>
2、問題描述app
服務端與客戶端的部署從網上找了不少資料,總算是部署好了,數據庫中查詢用戶、單點登陸都沒有問題,在測試單點登出時發現老是不能進入CAS客戶端單點登陸的方法,dom
執行單點登出後 經過跟蹤源碼發現ide
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