单点登出功能跟单点登录功能是相对应的,旨在通过Cas Server的登出使所有的Cas Client都登出。
Cas Server的登出是通过请求“/logout”发生的,即如果你的Cas Server部署的访问路径为“https://localhost:8443/cas”时,
通过访问“https://localhost:8443/cas/logout”可以触发Cas Server的登出操作,进而触发Cas Client的登出。
在请求Cas Server的logout时,Cas Server会将客户端携带的TGC删除,同时回调该TGT对应的所有service,即所有的Cas Client。
Cas Client如果需要响应该回调,进而在Cas Client端进行登出操作的话就需要有对应的支持。
具体来说,需要在Cas Client应用的web.xml文件中添加如下Filter和Listener。
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
我们分别看一下这两个类的源码:
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
// 转换参数
final HttpServletRequest request = (HttpServletRequest) servletRequest;
//判断参数中是否具有artifactParameterName属性指定的参数名称,默认是ticket
if (handler.isTokenRequest(request)) {
// 如果存在,在本地sessionMappingStorage中记录session。
handler.recordSession(request);
} else if (handler.isLogoutRequest(request)) {//判断是否具有logoutParameterName参数指定的参数,默认参数名称为logoutRequest
// 如果存在,则在sessionMappingStorage中删除记录,并注销session。
handler.destroySession(request);
// 注销session后,立刻停止执行后面的过滤器
return;
} else {
log.trace("Ignoring URI " + request.getRequestURI());
}
//条件都不满足,继续执行下面的过滤器
filterChain.doFilter(servletRequest, servletResponse);
}
用户通过浏览器访问系统A www.a.com/pageA
,这个 pageA
是个需要登录才能访问的页面,系统A发现用户没有登录,这时候系统A需要做一个额外的操作,就是重定向到认证中心: www.sso.com/login?service=www.a.com/pageA
。
这个 service
参数是一个回跳的url,认证完成后会重定向到系统A,另外有一个作用就是注册服务,简单来说注册服务为的是让我们的认证中心能够知道有哪些系统在我们这里完成过登录,其中一个重要目的是为了完成单点退出的功能。
注册服务时保存一是回跳的service地址,二是对应的ticket。
拿着ticket回跳到客户端后,客户端一看地址的请求参数有ticket的时候,singleSignOutFilter注册带有ticket作为id的session到sessionMappingStorage。
当用户访问认证中心的 /logout
需要退出的时候,认证中心先把TGT干掉,然后给之前注册过那些服务的地址发送退出登录的请求,并且携带之前登录的ticket,客户端一看请求中参数有logoutRequest的时候,
客户端的singleSignOutFilter根据传过来的这个 ticket
来将对应的用户 session
干掉即可。
那么什么时候干掉sessionMappingStorage呢?(sessionMappingStorage是一个map,key为ticket,value为session)
这是靠SingleSignOutHttpSessionListener来实现的,当有session被注销的时候,触发将sessionMappingStorage中对应的sessionId中的数据删除,
所以在配置单点登出的时候,一定要配置这个监听器,否则客户端很容易导致内存溢出。
SingleSignOutHttpSessionListener 源码:
/**
* Listener to detect when an HTTP session is destroyed and remove it from the map of
* managed sessions. Also allows for the programmatic removal of sessions.
* <p>
* Enables the CAS Single Sign out feature.
*
* Scott Battaglia
* @version $Revision$ Date$
* @since 3.1
*/
public final class SingleSignOutHttpSessionListener implements HttpSessionListener { 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.removeBySessionById(session.getId());
} /**
* Obtains a {@link SessionMappingStorage} object. Assumes this method will always return the same
* instance of the object. It assumes this because it generally lazily calls the method.
*
* @return the SessionMappingStorage
*/
protected static SessionMappingStorage getSessionMappingStorage() {
return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
}
}