问题表现:两个站点使用同一个cas单点登录,登录后,在A系统登出,B系统也同时登;而在B系统登出A系统未登出;
问题原因:A系统没有实现cas的logout逻辑。需要实现logoutFilter逻辑;在请求cas.example.com/cas/logout时,Cas Server会将客户端携带的TGT删除,同时回调该TGT对应的所有service;所以cas client需要实现登出逻辑,使会话失效;
参考:http://elim.iteye.com/blog/2144265
-----------------------------------------------我来分隔-----------------------------------------------------
一开始并不知道这套逻辑;只从表现出来的差异去查找。
表现:在A系统登出,再去刷新B系统页面;会得到一个新的JSESSIONID, 也就是服务端把当前连接当成新的会话;
查找JSESSIONID的生成代码。一开始想到应该是tomcat生成的,所以根据http://stackoverflow.com/questions/595872/under-what-conditions-is-a-jsessionid-created;提供的说法,在调用request.getSession()或request.getSession(true)时触发是否生成JSESSIONID(根据请求头是否带有JSESSIONID的cookie);其中找到:
JSESSIONID这个字符串值定义在: tomcat-embed-core.jar下的org.apache.catalina.util.SessionConfig类下的一个常量属性;
把JSESSIONID植入cookie的是 org.apache.catalina.connector.Request类的doGetSession方法;
如果使用了Shiro,shiro会拦截以上步骤,覆盖为自己的实现;
shiro中也默认是JSESSIONID,定义在org.apache.shiro.web.servlet.ShiroHttpSession类下的属性;
session生成是org.apache.shiro.web.session.mgt.DefaultSessionManager类的create方法;
cookie的生成是在org.apache.shiro.web.session.mgt.DefaultSessionManager类的storeSessionId方法;
shiro的seesionId默认使用的是UUID.randomUUID()方法;
shiro会在认证失败(会先获取当前session,并判断是否已认证)的拦截器的onAccessDenied()方法中调用saveRequestAndRedirectToLogin(...).保存当前请求到新session.并且跳转到cas登录页
-------------------------------------------下面是logoutFilter的实现逻辑----------------------------------
/**
* 拦截所有请求
* --如果请求中包含了ticket参数(成功登陆cas后,cas server会自动跳转到cas client并在url中带上ticket),
* 记录ticket和sessionID的映射
*
* --如果请求中包含logoutRequest参数(说明是cas logout, cas server回调ticket相同的所有cas client),
* 标记session为无效 如果session不为空,且被标记为无效,则登出
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @return 是logoutRequest请求返回false,否则返回true
* @throws Exception if there is any error.
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest req = (HttpServletRequest) request;
if (handler.isTokenRequest(req)) {
// 是否是登陆成功的回调。请求链接中含有ticket参数,记录ticket和sessionID
handler.recordSession(req);
return true;
} else if (handler.isLogoutRequest(req)) {
// cas服务器发送的请求,链接中含有logoutRequest参数,在之前记录的session中设置logoutRequest属性为true
// 因为Subject是和线程是绑定的,所以无法获取登录的Subject直接logout
handler.invalidateSession(req);
// Do not continue up filter chain
return false;
} else {
logger.trace("Ignoring URI " + req.getRequestURI());
}
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession(false);
if (session != null && session.getAttribute(handler.getLogoutParameterName()) != null) {
//如果session存在,且属性中有logoutRequest值,则登出
try {
subject.logout();
} catch (SessionException ise) {
logger.debug("Encountered session exception during logout. This can generally safely be ignored.",
ise);
}
}
return true;
}