3、 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客户端:http://blog.csdn.net/dovejing/article/details/44426547

CAS服务器端:http://blog.csdn.net/dovejing/article/details/44523545

应用系统web.xml部分代码

[html]  view plain copy
  1. <listener>  
  2.     <listener-class>com.master.client.listener.SingleSignOutHttpSessionListener</listener-class>  
  3. </listener>  
  4. <filter>  
  5.     <filter-name>CAS Single Sign Out Filter</filter-name>  
  6.     <filter-class>com.master.client.filter.SingleSignOutFilter</filter-class>  
  7. </filter>  

应用系统登出方法logout

[java]  view plain copy
  1. public String logout(HttpSession session, HttpServletResponse response,  
  2.         HttpServletRequest request) {  
  3.     try {  
  4.         Properties conf = PropertiesUtil.getConfigProperties();  
  5.         String service = ssoProperties.getProperty("service");  
  6.         String logoutUrl = ssoProperties.getProperty("casServerLogoutUrl");  
  7.         String serverName = ssoProperties.getProperty("serverName");  
  8.         //生成CAS服务器端的登出URL  
  9.         String serviceUrl = CommonUtils.constructServiceUrl(request, response, service, serverName, "ticket"true);  
  10.   
  11.         response.sendRedirect(logoutUrl + "?service=" + URLEncoder.encode(serviceUrl, "utf-8"));  
  12.     } catch (URISyntaxException e) {  
  13.         e.printStackTrace();  
  14.     } catch (UnsupportedEncodingException e) {  
  15.         e.printStackTrace();  
  16.     } catch (IOException e) {  
  17.         e.printStackTrace();  
  18.     }  
  19.           
  20.     return null;  
  21. }  

当我们在应用系统中,执行注销或退出操作时,会执行logout方法。应用系统登出方法logout要做是获取单点登录的配置文件(参考http://blog.csdn.net/dovejing/article/details/44426547),获取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 plain copy
  1. <bean id="handlerMappingC" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">  
  2.     <property name="mappings">  
  3.         <props>  
  4.             <prop key="/logout">logoutController</prop><!-- 登出 -->  
  5.             <prop key="/serviceValidate">serviceValidateController</prop>  
  6.             <prop key="/validate">legacyValidateController</prop>  
  7.             <prop key="/proxy">proxyController</prop>  
  8.             <prop key="/proxyValidate">proxyValidateController</prop>  
  9.             <prop key="/samlValidate">samlValidateController</prop>  
  10.             <prop key="/services/add.html">addRegisteredServiceSimpleFormController</prop>  
  11.             <prop key="/services/edit.html">editRegisteredServiceSimpleFormController</prop>  
  12.             <prop key="/services/loggedOut.html">serviceLogoutViewController</prop>  
  13.             <prop key="/services/viewStatistics.html">viewStatisticsController</prop>  
  14.             <prop key="/services/*">manageRegisteredServicesMultiActionController</prop>  
  15.             <prop key="/openid/*">openIdProviderController</prop>  
  16.             <prop key="/authorizationFailure.html">passThroughController</prop>  
  17.             <prop key="/403.html">passThroughController</prop>  
  18.             <prop key="/status">healthCheckController</prop>  
  19.             <prop key="/error">extErrorController</prop>  
  20.         </props>  
  21.     </property>  
  22.     <property name="alwaysUseFullPath" value="true" />  
  23. </bean>  
logoutController配置信息
[html]  view plain copy
  1. <bean id="logoutController" class="org.jasig.cas.web.LogoutController"  
  2.     p:centralAuthenticationService-ref="centralAuthenticationService"  
  3.     p:logoutView="casLogoutView" p:warnCookieGenerator-ref="warnCookieGenerator"  
  4.     p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"  
  5.     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 plain copy
  1. protected ModelAndView handleRequestInternal(  
  2.     final HttpServletRequest request, final HttpServletResponse response)  
  3.     throws Exception {  
  4.     //获取TGT  
  5.     final String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);  
  6.     //获取service  
  7.     final String service = request.getParameter("service");  
  8.           
  9.     //如果TGT不为空  
  10.     if (ticketGrantingTicketId != null) {  
  11.         //销毁TGT  
  12.         this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);  
  13.         //销毁TGTcookie  
  14.         this.ticketGrantingTicketCookieGenerator.removeCookie(response);  
  15.         //销毁warnCookieValue  
  16.         this.warnCookieGenerator.removeCookie(response);  
  17.     }  
  18.   
  19.     //如果service不会空  
  20.     if (this.followServiceRedirects && service != null) {  
  21.         final RegisteredService rService = this.servicesManager.findServiceBy(new SimpleWebApplicationServiceImpl(service));  
  22.   
  23.         if (rService != null && rService.isEnabled()) {  
  24.             //跳转到service  
  25.             return new ModelAndView(new RedirectView(service));  
  26.         }  
  27.     }  
  28.       
  29.     return new ModelAndView(this.logoutView);  
  30. }  
LogoutController的handleRequestInternal要做是从request的cookies中获取TGC,从request中获取service,同时销毁服务器缓存的TGT和response中的TGC。 并跳转到应用系统的service(http://127.0.0.1:8090/webapp/main.do)页面。

CentralAuthenticationServiceImpl的destroyTicketGrantingTicket方法

[java]  view plain copy
  1. public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) {  
  2.     Assert.notNull(ticketGrantingTicketId);  
  3.   
  4.     if (log.isDebugEnabled()) {  
  5.             log.debug("Removing ticket [" + ticketGrantingTicketId + "] from registry.");  
  6.     }  
  7.     final TicketGrantingTicket ticket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId,   
  8.         TicketGrantingTicket.class);  
  9.   
  10.     if (ticket == null) {  
  11.         return;  
  12.     }  
  13.   
  14.     if (log.isDebugEnabled()) {  
  15.         log.debug("Ticket found.  Expiring and then deleting.");  
  16.     }  
  17.     ticket.expire();  
  18.     this.ticketRegistry.deleteTicket(ticketGrantingTicketId);  
  19. }  
之前在CAS服务器端创建TGT的时候,我们把TGT放到了ticketRegistry对象中,所以此时需要对ticketRegistry中的TGT进行操作,从ticketRegistry对象中获取TGT,并执行TGT的expire方法,设置TGT的属性为过期,expire方法同会时执行logOutOfServices方法。

TicketGrantingTicket的expire方法和logOutOfServices方法

[java]  view plain copy
  1. public synchronized void expire() {  
  2.     this.expired = true;  
  3.     logOutOfServices();  
  4. }  
  5.   
  6. private void logOutOfServices() {  
  7.     for (final Entry<String, Service> entry : this.services.entrySet()) {  
  8.   
  9.         if (!entry.getValue().logOutOfService(entry.getKey())) {  
  10.             LOG.warn("Logout message not sent to [" + entry.getValue().getId() + "]; Continuing processing...");     
  11.         }  
  12.     }  
  13. }  
logOutOfServices方法会 循环所有的Service(AbstractWebApplicationService实现类),并执行logOutOfService方法。

AbstractWebApplicationService的logOutOfServices方法

[java]  view plain copy
  1. public synchronized boolean logOutOfService(final String sessionIdentifier) {  
  2.     if (this.loggedOutAlready) {  
  3.         return true;  
  4.     }  
  5.   
  6.     LOG.debug("Sending logout request for: " + getId());  
  7.   
  8.     final String logoutRequest = "<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\""  
  9.         + GENERATOR.getNewTicketId("LR")  
  10.         + "\" Version=\"2.0\" IssueInstant=\"" + SamlUtils.getCurrentDateAndTime()  
  11.         + "\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@</saml:NameID><samlp:SessionIndex>"  
  12.         + sessionIdentifier + "</samlp:SessionIndex></samlp:LogoutRequest>";  
  13.           
  14.     this.loggedOutAlready = true;  
  15.           
  16.     if (this.httpClient != null) {  
  17.         return this.httpClient.sendMessageToEndPoint(getOriginalUrl(), logoutRequest, true);  
  18.     }  
  19.           
  20.     return false;  
  21. }  

logOutOfService会构造logoutRequest对象,同时执行HttpClient的sendMessageToEndPoint方法访问客户端,此时,客户端的过滤器会拦截这个请求,并对logoutRequest进行解析,获取sessionIndex(sessionIdentifier),并根据sessionIndex销毁session信息。

SingleSignOutFilter的doFilter方法

[java]  view plain copy
  1. public void doFilter(ServletRequest servletRequest,  
  2.         ServletResponse servletResponse, FilterChain filterChain)  
  3.         throws IOException, ServletException {  
  4.     HttpServletRequest request = (HttpServletRequest) servletRequest;  
  5.     //request是否有ticket  
  6.     if (handler.isTokenRequest(request)) {  
  7.         //sessionMappingStorage记录session。  
  8.         handler.recordSession(request);  
  9.     } else {  
  10.         //request是否有logoutRequest  
  11.         if (handler.isLogoutRequest(request)) {  
  12.             //sessionMappingStorag注销session。  
  13.             handler.destroySession(request);  
  14.             //不执行后续过滤器   
  15.             return;  
  16.         }  
  17.         this.log.trace("Ignoring URI " + request.getRequestURI());  
  18.     }  
  19.   
  20.     filterChain.doFilter(servletRequest, servletResponse);  
  21. }  

SingleSignOutFilter的doFilter方法,要做的是如果request有ticket参数,则记录session到sessionMappingStorage中,执行后续过滤器。如果request有logoutRequest参数,则从sessionMappingStorage销毁session,不执行后续过滤器。

SingleSignOutHttpSessionListener类源码

[java]  view plain copy
  1. private SessionMappingStorage sessionMappingStorage;  
  2.   
  3. public void sessionCreated(final HttpSessionEvent event) {  
  4.     // nothing to do at the moment  
  5. }  
  6. public void sessionDestroyed(final HttpSessionEvent event) {  
  7.     if (sessionMappingStorage == null) {  
  8.         sessionMappingStorage = getSessionMappingStorage();  
  9.     }  
  10.     final HttpSession session = event.getSession();  
  11.     //从sessionMappingStorage删除session  
  12.     sessionMappingStorage.removeBySessionById(session.getId());  
  13. }  
  14.   
  15. protected static SessionMappingStorage getSessionMappingStorage() {  
  16.     return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();  
  17. }  
当session销毁时,会触发SingleSignOutHttpSessionListener类 (父类HttpSessionListener)的销毁事件,此时,sessionDestroyed方法会从sessionMappingStorage中删除session信息。 完成之后,会继续执行LogoutController的handleRequestInternal方法,并跳转到应用系统的service(http://127.0.0.1:8090/webapp/main.do)页面。

至此,CAS的单点登出操作流程已经完成,正常情况下会显示CAS的登录页面。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值