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部分代码


	com.master.client.listener.SingleSignOutHttpSessionListener


	CAS Single Sign Out Filter
	com.master.client.filter.SingleSignOutFilter

应用系统登出方法logout

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要做是获取单点登录的配置文件(参考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部分代码


	
		
			logoutController
			serviceValidateController
			legacyValidateController
			proxyController
			proxyValidateController
			samlValidateController
			addRegisteredServiceSimpleFormController
			editRegisteredServiceSimpleFormController
			serviceLogoutViewController
			viewStatisticsController
			manageRegisteredServicesMultiActionController
			openIdProviderController
			passThroughController
			passThroughController
			healthCheckController
			extErrorController
		
	
	
logoutController配置信息

根据配置信息,当CAS单点登录服务器端截获http://127.0.0.1:8081/cas-server/logout?service=http://127.0.0.1:8090/webapp/main.do链接时,会进入到LogoutController的handleRequestInternal方法。

LogoutController的handleRequestInternal方法

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方法

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方法

public synchronized void expire() {
    this.expired = true;
    logOutOfServices();
}

private void logOutOfServices() {
    for (final Entry 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方法

public synchronized boolean logOutOfService(final String sessionIdentifier) {
    if (this.loggedOutAlready) {
		return true;
    }

    LOG.debug("Sending logout request for: " + getId());

    final String logoutRequest = "@NOT_USED@"
        + sessionIdentifier + "";
        
    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方法

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类源码

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单点登录,java,源码,CAS,单点登录,单点登出)