本文使用的环境为:
CAS = 4.1.10;
shiro = 1.2.2;
公司产品线较多,需要使用单点登录来贯通各个产品项目,遂采用CAS + shiro进行单点登录的实现。
完成配置后,发现一个问题:ABC三个应用在单点登录环境下是可以一次登录各处使用,在A登录后BC均可直接使用。
但是,卧槽为啥我A登出之后BC没有跟着登出呢?这不坑么?难不成还要让客户挨个项目点退出去?经理会撕了我的……
查官方文档……发现并没有相关的说明。
好吧,目测shiro提供的shiro-cas支持包并不全面啊。
查百度,浪费了一天之后,得出结论:靠天靠地不如靠自己。
不要怂,就是刚,抄起键盘就是干!
经过跟踪调查,shiro并没有对单点登出进行支持。也就是说需要完全自己实现。
创建一个package,在里面创建下列几个类:
单点登出执行类
SingleSignOutHandler
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
/**
* 单点登出执行类
*
*/
public final class SingleSignOutHandler {
/**
* 强制踢出用户标示符
*/
public static final String SESSION_FORCE_BAN_KEY="BAND";
/**
* 用户登出标示符
*/
public static final String SESSION_FORCE_LOGOUT_KEY="LOGOUT";
/** 日志 */
private final Log log = LogFactory.getLog(getClass());
/** 请求识别关键字 用来标记请求中票据保存的key */
private String artifactParameterName = "ticket";
/** 请求识别关键字 用来标记请求中登出信息的key */
private String logoutParameterName = "logoutRequest";
/** 强制登出指令名 */
private String banParameterName = "banRequest";
private static HashMapBackedSessionMappingStorage storage = new HashMapBackedSessionMappingStorage();
/**
* 获取记录的token与sessionID对应信息
* @return storage
*/
public static HashMapBackedSessionMappingStorage getSessionMappingStorage(){
return storage;
}
protected SingleSignOutHandler(){
init();
}
/**
* @param name Name of the authentication token parameter.
*/
public void setArtifactParameterName(final String name) {
this.artifactParameterName = name;
}
/**
* @param name Name of parameter containing CAS logout request message.
*/
public void setLogoutParameterName(final String name) {
this.logoutParameterName = name;
}
protected String getLogoutParameterName() {
return this.logoutParameterName;
}
/**
* Initializes the component for use.
*/
public void init() {
CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null.");
}
/**
* 检测是否是一个token验证请求
*
* @param request HTTP reqest.
*
* @return True if request contains authentication token, false otherwise.
*/
public boolean isTokenRequest(final HttpServletRequest request) {
return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName));
}
/**
* 检测是否是一个CAS登出通知请求
*
* @param request HTTP request.
*
* @return True if request is logout request, false otherwise.
*/
public boolean isLogoutRequest(final HttpServletRequest request) {
return "POST".equals(request.getMethod()) && !isMultipartRequest(request) &&
CommonUtils.isNotBlank(request.getParameter(this.logoutParameterName));
}
/**
* 检测请求是否为强制踢出指令
*
* @param request HTTP request.
*
* @return True if request is ban request, false otherwise.
*/
public boolean isBanRequest(final HttpServletRequest request) {
return "POST".equals(request.getMethod()) && !isMultipartRequest(request) &&
CommonUtils.isNotBlank(request.getParameter(this.banParameterName));
}
/**
* 记录请求中的token和sessionID的映射对
*
* @param request HTTP request containing an authentication token.
*/
public void recordSession(final HttpServletRequest request) {
Session session = SecurityUtils.getSubject().getSession();
final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName);
if (log.isDebugEnabled())