一、环境说明
服务端:cas-server-3.5.2
客户端:cas-client-3.2.1+spring mvc
说明:服务端与客户端均是走的Https
客户端配置文件:
applicationContext-cas.xml
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">
web.xml
Archetype Created Web Application
contextConfigLocation
classpath*:/applicationContext-cas.xml
org.springframework.web.context.ContextLoaderListener
org.jasig.cas.client.session.SingleSignOutHttpSessionListener
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
encodingFilter
/*
REQUEST
FORWARD
springServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/spring-mvc.xml
1
springServlet
/
cas oss info
com.gqshao.cas.servlet.InfoServlet
cas oss info
/info
cas oss logout
com.gqshao.cas.servlet.LogoutServlet
cas oss logout
/logout
CAS Single Sign Out Filter
org.springframework.web.filter.DelegatingFilterProxy
targetBeanName
singleSignOutFilter
CAS Single Sign Out Filter
/*
CAS Validation Filter
org.springframework.web.filter.DelegatingFilterProxy
targetBeanName
ticketValidationFilter
CAS Validation Filter
/*
CAS Authentication Filter
org.springframework.web.filter.DelegatingFilterProxy
targetBeanName
authenticationFilter
CAS Authentication Filter
/*
CAS HttpServletRequest Wrapper Filter
org.springframework.web.filter.DelegatingFilterProxy
targetBeanName
httpServletRequestWrapperFilter
CAS HttpServletRequest Wrapper Filter
/*
CAS Assertion Thread Local Filter
org.springframework.web.filter.DelegatingFilterProxy
targetBeanName
assertionThreadLocalFilter
CAS Assertion Thread Local Filter
/*
二、问题描述
服务端与客户端的部署从网上找了很多资料,总算是部署好了,数据库中查询用户、单点登录都没有问题,在测试单点登出时发现总是不能进入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() throwsException {
HttpURLConnection connection= null;
BufferedReader in= null;try{if(log.isDebugEnabled()) {
log.debug("Attempting to access " +url);
}final URL logoutUrl = newURL(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 = newDataOutputStream(connection.getOutputStream());
printout.writeBytes(output);
printout.flush();
printout.close();
in= new BufferedReader(newInputStreamReader(connection.getInputStream()));while (in.readLine() != null) {//nothing to do
}if(log.isDebugEnabled()) {
log.debug("Finished sending message to" +url);
}return true;
}catch (finalSocketTimeoutException e) {
log.warn("Socket Timeout Detected while attempting to send message to [" + url + "].");return false;
}catch (finalException e) {
log.warn("Error Sending message to url endpoint [" + url + "]. Error is [" + e.getMessage() + "]");return false;
}finally{if (in != null) {try{
in.close();
}catch (finalIOException e) {//can't do anything
}
}if (connection != null) {
connection.disconnect();
}
}
}
并未针对启用SSL的客户端进行处理,故始终无法向客户端发送消息,为了方便起见,直接对源码进行修改来支持SSL的客户端
修改的部分
if(url.startsWith("https:"))
{
TrustManager[] trustAllCerts= new TrustManager[] { newX509TrustManager() {publicjava.security.cert.X509Certificate[] getAcceptedIssuers() {return null;
}public voidcheckClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}public voidcheckServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
} };
httpsconnection=(HttpsURLConnection) logoutUrl.openConnection();
httpsconnection.setDefaultHostnameVerifier(newNullHostNameVerifier());
SSLContext sc= SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, newjava.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 = newDataOutputStream(httpsconnection.getOutputStream());
printout.writeBytes(output);
printout.flush();
printout.close();
in= new BufferedReader(newInputStreamReader(httpsconnection.getInputStream()));while (in.readLine() != null) {//nothing to do
}
}
View Code
至此,解决了当下遇到的问题
整个过程走了不少弯路,能读懂源码,能正确通过DEBUG排除问题真的是一项需要持续锻炼的技能
在此mark一下
文中源码阅读部分感谢作者eg366:
http://blog.csdn.net/eg366/article/details/14054949