3、 CAS单点登录源码解析之【单点登出】
2016-01-23 17:42
435 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。
1.应用系统webapp(http://127.0.0.1:8090/webapp/main.do)
2.CAS单点登录服务器端(http://127.0.0.1:8081/cas-server/)
本次讨论包括CAS单点登录客户端的部分源码,以及在此基础上进行单点登出二次开发,因此需要修改部分CAS客户端的源码,源码部分的修改在下面进行讨论。关于CAS客户端和服务器端的源码分析,请参考另外两篇文章
CAS客户端:/article/11163792.html
CAS服务器端:/article/11163793.html
应用系统web.xml部分代码
[html] view
plaincopy
<listener>
<listener-class>com.master.client.listener.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>com.master.client.filter.SingleSignOutFilter</filter-class>
</filter>
应用系统登出方法logout
[java] view
plaincopy
public String logout(HttpSession session, HttpServletResponse response,
HttpServletRequest request) {
try {
Properties conf = PropertiesUtil.getConfigProperties();
String service = ssoProperties.getProperty("service");
String logoutUrl = ssoProperties.getProperty("casServerLogoutUrl");
String serverName = ssoProperties.getProperty("serverName");
//生成CAS服务器端的登出URL
String serviceUrl = CommonUtils.constructServiceUrl(request, response, service, serverName, "ticket", true);
response.sendRedirect(logoutUrl + "?service=" + URLEncoder.encode(serviceUrl, "utf-8"));
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
当我们在应用系统中,执行注销或退出操作时,会执行logout方法。应用系统登出方法logout要做是获取单点登录的配置文件(参考/article/11163792.html),获取service、logoutUrl和serverName参数。生成CAS服务器端的登出URL(http://127.0.0.1:8081/cas-server/logout?service=http://127.0.0.1:8090/webapp/main.do),response重定向到CAS服务器端的登出URL。
CAS服务器端cas-server.xml部分代码
[html] view
plaincopy
<bean id="handlerMappingC" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/logout">logoutController</prop><!-- 登出 -->
<prop key="/serviceValidate">serviceValidateController</prop>
<prop key="/validate">legacyValidateController</prop>
<prop key="/proxy">proxyController</prop>
<prop key="/proxyValidate">proxyValidateController</prop>
<prop key="/samlValidate">samlValidateController</prop>
<prop key="/services/add.html">addRegisteredServiceSimpleFormController</prop>
<prop key="/services/edit.html">editRegisteredServiceSimpleFormController</prop>
<prop key="/services/loggedOut.html">serviceLogoutViewController</prop>
<prop key="/services/viewStatistics.html">viewStatisticsController</prop>
<prop key="/services/*">manageRegisteredServicesMultiActionController</prop>
<prop key="/openid/*">openIdProviderController</prop>
<prop key="/authorizationFailure.html">passThroughController</prop>
<prop key="/403.html">passThroughController</prop>
<prop key="/status">healthCheckController</prop>
<prop key="/error">extErrorController</prop>
</props>
</property>
<property name="alwaysUseFullPath" value="true" />
</bean>
logoutController配置信息
[html] view
plaincopy
<bean id="logoutController" class="org.jasig.cas.web.LogoutController"
p:centralAuthenticationService-ref="centralAuthenticationService"
p:logoutView="casLogoutView" p:warnCookieGenerator-ref="warnCookieGenerator"
p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"
p:servicesManager-ref="servicesManager" p:followServiceRedirects="${cas.logout.followServiceRedirects:true}" />
根据配置信息,当CAS单点登录服务器端截获http://127.0.0.1:8081/cas-server/logout?service=http://127.0.0.1:8090/webapp/main.do链接时,会进入到LogoutController的handleRequestInternal方法。
LogoutController的handleRequestInternal方法
[java] view
plaincopy
protected ModelAndView handleRequestInternal(
final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
//获取TGT
final String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
//获取service
final String service = request.getParameter("service");
//如果TGT不为空
if (ticketGrantingTicketId != null) {
//销毁TGT
this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
//销毁TGTcookie
this.ticketGrantingTicketCookieGenerator.removeCookie(response);
//销毁warnCookieValue
this.warnCookieGenerator.removeCookie(response);
}
//如果service不会空
if (this.followServiceRedirects && service != null) {
final RegisteredService rService = this.servicesManager.findServiceBy(new SimpleWebApplicationServiceImpl(service));
if (rService != null && rService.isEnabled()) {
//跳转到service
return new ModelAndView(new RedirectView(service));
}
}
return new ModelAndView(this.logoutView);
}
LogoutController的handleRequestInternal要做是从request的cookies中获取TGC,从request中获取service,同时销毁服务器缓存的TGT和response中的TGC。并跳转到应用系统的service(http://127.0.0.1:8090/webapp/main.do)页面。
CentralAuthenticationServiceImpl的destroyTicketGrantingTicket方法
[java] view
plaincopy
public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
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);
}
之前在CAS服务器端创建TGT的时候,我们把TGT放到了ticketRegistry对象中,所以此时需要对ticketRegistry中的TGT进行操作,从ticketRegistry对象中获取TGT,并执行TGT的expire方法,设置TGT的属性为过期,expire方法同会时执行logOutOfServices方法。
TicketGrantingTicket的expire方法和logOutOfServices方法
[java] view
plaincopy
public synchronized void expire() {
this.expired = true;
logOutOfServices();
}
private void logOutOfServices() {
for (final Entry<String, Service> entry : this.services.entrySet()) {
if (!entry.getValue().logOutOfService(entry.getKey())) {
LOG.warn("Logout message not sent to [" + entry.getValue().getId() + "]; Continuing processing...");
}
}
}
logOutOfServices方法会循环所有的Service(AbstractWebApplicationService实现类),并执行logOutOfService方法。
AbstractWebApplicationService的logOutOfServices方法
[java] view
plaincopy
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, true);
}
return false;
}
logOutOfService会构造logoutRequest对象,同时执行HttpClient的sendMessageToEndPoint方法访问客户端,此时,客户端的过滤器会拦截这个请求,并对logoutRequest进行解析,获取sessionIndex(sessionIdentifier),并根据sessionIndex销毁session信息。
SingleSignOutFilter的doFilter方法
[java] view
plaincopy
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
//request是否有ticket
if (handler.isTokenRequest(request)) {
//sessionMappingStorage记录session。
handler.recordSession(request);
} else {
//request是否有logoutRequest
if (handler.isLogoutRequest(request)) {
//sessionMappingStorag注销session。
handler.destroySession(request);
//不执行后续过滤器
return;
}
this.log.trace("Ignoring URI " + request.getRequestURI());
}
filterChain.doFilter(servletRequest, servletResponse);
}
SingleSignOutFilter的doFilter方法,要做的是如果request有ticket参数,则记录session到sessionMappingStorage中,执行后续过滤器。如果request有logoutRequest参数,则从sessionMappingStorage销毁session,不执行后续过滤器。
SingleSignOutHttpSessionListener类源码
[java] view
plaincopy
private SessionMappingStorage sessionMappingStorage;
public void sessionCreated(final HttpSessionEvent event) {
// nothing to do at the moment
}
public void sessionDestroyed(final HttpSessionEvent event) {
if (sessionMappingStorage == null) {
sessionMappingStorage = getSessionMappingStorage();
}
final HttpSession session = event.getSession();
//从sessionMappingStorage删除session
sessionMappingStorage.removeBySessionById(session.getId());
}
protected static SessionMappingStorage getSessionMappingStorage() {
return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
}
当session销毁时,会触发SingleSignOutHttpSessionListener类(父类HttpSessionListener)的销毁事件,此时,sessionDestroyed方法会从sessionMappingStorage中删除session信息。完成之后,会继续执行LogoutController的handleRequestInternal方法,并跳转到应用系统的service(http://127.0.0.1:8090/webapp/main.do)页面。
至此,CAS的单点登出操作流程已经完成,正常情况下会显示CAS的登录页面。
前期准备
已经搭建好了集成了CAS客户端的应用系统和CAS服务器1.应用系统webapp(http://127.0.0.1:8090/webapp/main.do)
2.CAS单点登录服务器端(http://127.0.0.1:8081/cas-server/)
本次讨论包括CAS单点登录客户端的部分源码,以及在此基础上进行单点登出二次开发,因此需要修改部分CAS客户端的源码,源码部分的修改在下面进行讨论。关于CAS客户端和服务器端的源码分析,请参考另外两篇文章
CAS客户端:/article/11163792.html
CAS服务器端:/article/11163793.html
应用系统web.xml部分代码
[html] view
plaincopy
<listener>
<listener-class>com.master.client.listener.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>com.master.client.filter.SingleSignOutFilter</filter-class>
</filter>
应用系统登出方法logout
[java] view
plaincopy
public String logout(HttpSession session, HttpServletResponse response,
HttpServletRequest request) {
try {
Properties conf = PropertiesUtil.getConfigProperties();
String service = ssoProperties.getProperty("service");
String logoutUrl = ssoProperties.getProperty("casServerLogoutUrl");
String serverName = ssoProperties.getProperty("serverName");
//生成CAS服务器端的登出URL
String serviceUrl = CommonUtils.constructServiceUrl(request, response, service, serverName, "ticket", true);
response.sendRedirect(logoutUrl + "?service=" + URLEncoder.encode(serviceUrl, "utf-8"));
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
当我们在应用系统中,执行注销或退出操作时,会执行logout方法。应用系统登出方法logout要做是获取单点登录的配置文件(参考/article/11163792.html),获取service、logoutUrl和serverName参数。生成CAS服务器端的登出URL(http://127.0.0.1:8081/cas-server/logout?service=http://127.0.0.1:8090/webapp/main.do),response重定向到CAS服务器端的登出URL。
CAS服务器端cas-server.xml部分代码
[html] view
plaincopy
<bean id="handlerMappingC" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/logout">logoutController</prop><!-- 登出 -->
<prop key="/serviceValidate">serviceValidateController</prop>
<prop key="/validate">legacyValidateController</prop>
<prop key="/proxy">proxyController</prop>
<prop key="/proxyValidate">proxyValidateController</prop>
<prop key="/samlValidate">samlValidateController</prop>
<prop key="/services/add.html">addRegisteredServiceSimpleFormController</prop>
<prop key="/services/edit.html">editRegisteredServiceSimpleFormController</prop>
<prop key="/services/loggedOut.html">serviceLogoutViewController</prop>
<prop key="/services/viewStatistics.html">viewStatisticsController</prop>
<prop key="/services/*">manageRegisteredServicesMultiActionController</prop>
<prop key="/openid/*">openIdProviderController</prop>
<prop key="/authorizationFailure.html">passThroughController</prop>
<prop key="/403.html">passThroughController</prop>
<prop key="/status">healthCheckController</prop>
<prop key="/error">extErrorController</prop>
</props>
</property>
<property name="alwaysUseFullPath" value="true" />
</bean>
logoutController配置信息
[html] view
plaincopy
<bean id="logoutController" class="org.jasig.cas.web.LogoutController"
p:centralAuthenticationService-ref="centralAuthenticationService"
p:logoutView="casLogoutView" p:warnCookieGenerator-ref="warnCookieGenerator"
p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"
p:servicesManager-ref="servicesManager" p:followServiceRedirects="${cas.logout.followServiceRedirects:true}" />
根据配置信息,当CAS单点登录服务器端截获http://127.0.0.1:8081/cas-server/logout?service=http://127.0.0.1:8090/webapp/main.do链接时,会进入到LogoutController的handleRequestInternal方法。
LogoutController的handleRequestInternal方法
[java] view
plaincopy
protected ModelAndView handleRequestInternal(
final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
//获取TGT
final String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
//获取service
final String service = request.getParameter("service");
//如果TGT不为空
if (ticketGrantingTicketId != null) {
//销毁TGT
this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
//销毁TGTcookie
this.ticketGrantingTicketCookieGenerator.removeCookie(response);
//销毁warnCookieValue
this.warnCookieGenerator.removeCookie(response);
}
//如果service不会空
if (this.followServiceRedirects && service != null) {
final RegisteredService rService = this.servicesManager.findServiceBy(new SimpleWebApplicationServiceImpl(service));
if (rService != null && rService.isEnabled()) {
//跳转到service
return new ModelAndView(new RedirectView(service));
}
}
return new ModelAndView(this.logoutView);
}
LogoutController的handleRequestInternal要做是从request的cookies中获取TGC,从request中获取service,同时销毁服务器缓存的TGT和response中的TGC。并跳转到应用系统的service(http://127.0.0.1:8090/webapp/main.do)页面。
CentralAuthenticationServiceImpl的destroyTicketGrantingTicket方法
[java] view
plaincopy
public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
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);
}
之前在CAS服务器端创建TGT的时候,我们把TGT放到了ticketRegistry对象中,所以此时需要对ticketRegistry中的TGT进行操作,从ticketRegistry对象中获取TGT,并执行TGT的expire方法,设置TGT的属性为过期,expire方法同会时执行logOutOfServices方法。
TicketGrantingTicket的expire方法和logOutOfServices方法
[java] view
plaincopy
public synchronized void expire() {
this.expired = true;
logOutOfServices();
}
private void logOutOfServices() {
for (final Entry<String, Service> entry : this.services.entrySet()) {
if (!entry.getValue().logOutOfService(entry.getKey())) {
LOG.warn("Logout message not sent to [" + entry.getValue().getId() + "]; Continuing processing...");
}
}
}
logOutOfServices方法会循环所有的Service(AbstractWebApplicationService实现类),并执行logOutOfService方法。
AbstractWebApplicationService的logOutOfServices方法
[java] view
plaincopy
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, true);
}
return false;
}
logOutOfService会构造logoutRequest对象,同时执行HttpClient的sendMessageToEndPoint方法访问客户端,此时,客户端的过滤器会拦截这个请求,并对logoutRequest进行解析,获取sessionIndex(sessionIdentifier),并根据sessionIndex销毁session信息。
SingleSignOutFilter的doFilter方法
[java] view
plaincopy
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
//request是否有ticket
if (handler.isTokenRequest(request)) {
//sessionMappingStorage记录session。
handler.recordSession(request);
} else {
//request是否有logoutRequest
if (handler.isLogoutRequest(request)) {
//sessionMappingStorag注销session。
handler.destroySession(request);
//不执行后续过滤器
return;
}
this.log.trace("Ignoring URI " + request.getRequestURI());
}
filterChain.doFilter(servletRequest, servletResponse);
}
SingleSignOutFilter的doFilter方法,要做的是如果request有ticket参数,则记录session到sessionMappingStorage中,执行后续过滤器。如果request有logoutRequest参数,则从sessionMappingStorage销毁session,不执行后续过滤器。
SingleSignOutHttpSessionListener类源码
[java] view
plaincopy
private SessionMappingStorage sessionMappingStorage;
public void sessionCreated(final HttpSessionEvent event) {
// nothing to do at the moment
}
public void sessionDestroyed(final HttpSessionEvent event) {
if (sessionMappingStorage == null) {
sessionMappingStorage = getSessionMappingStorage();
}
final HttpSession session = event.getSession();
//从sessionMappingStorage删除session
sessionMappingStorage.removeBySessionById(session.getId());
}
protected static SessionMappingStorage getSessionMappingStorage() {
return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
}
当session销毁时,会触发SingleSignOutHttpSessionListener类(父类HttpSessionListener)的销毁事件,此时,sessionDestroyed方法会从sessionMappingStorage中删除session信息。完成之后,会继续执行LogoutController的handleRequestInternal方法,并跳转到应用系统的service(http://127.0.0.1:8090/webapp/main.do)页面。
至此,CAS的单点登出操作流程已经完成,正常情况下会显示CAS的登录页面。
相关文章推荐
- Xcode 调试与快捷键
- 使用ExecuteReader时报错“阅读器关闭时尝试调用Read无效”的解决办法
- nginx和tomcat的区别
- Cocos高低版本场景切换比较
- 2、[置顶] CAS单点登录源码解析之【服务器端】
- 对Memcached使用的总结和使用场景
- Js模型和封装
- DP------Unique Paths
- libjpeg:读取jpeg头获取图像基本信息(jpeg_read_header)
- 使用Itext结合Jfreechart图表导出带图表的word文档
- 设计模式之策略模式
- 工具栏图标切换
- tiny210 / Smart210 (S5PV210)内存DDR2-SDRAM初始化顺序及相关难点浅析———210SDRAM初始化学习记
- POJ3268Silver Cow Party(单源最短路径变形)
- Stairway to SQLCLR Level 5: Development (Using .NET within SQL Server)
- Java IDL与javaRMI
- 冒泡排序算法详解
- Android如何集成百度、高德、腾讯地图
- Android源码阅读-Filter过滤器
- 漫谈并发编程(三):共享受限资源