CAS单点登出,调整CAS源码,实现前后端分离单点登出、清除redis、shiro登录状态
前端點(diǎn)擊“登出”按鈕,跳轉(zhuǎn)到CAS的登出。
CAS默認(rèn)配置了單點(diǎn)登出,在登出后,會(huì)向所有客戶端系統(tǒng)發(fā)送這個(gè)用戶登出的報(bào)文。
各客戶端系統(tǒng)有責(zé)任接收并處理這個(gè)用戶登出的報(bào)文,然后在注銷該用戶會(huì)話在本客戶端的信息。
若不進(jìn)行 #CAS配置客戶端地址 或 #客戶端后端 ,則網(wǎng)頁(yè)里的登出按鈕點(diǎn)擊之后就無(wú)法通知其他客戶端系統(tǒng)登出。
目前使用了客戶端集成CAS源碼并修改的方法,來(lái)對(duì)客戶端做了Filter接收CAS登出請(qǐng)求之后的處理。
有悖于CAS的建議"不要修改CAS源碼"。待以后再研究看有沒(méi)有什么優(yōu)化方法。
CAS配置客戶端地址
??CAS的單點(diǎn)登出是默認(rèn)啟用的。
??CAS向客戶端發(fā)送地址,默認(rèn)為客戶端請(qǐng)求的URL。但該請(qǐng)求的URL是客戶端的前端地址(含端口),CAS需要向客戶端的后端發(fā)送請(qǐng)求。因此需要對(duì)對(duì)CAS配置客戶端的指定地址。
??CAS 對(duì)客戶端信息的存儲(chǔ)在/services/目錄下,這里配置jl-iam-client的信息,文件命名為iam-client-10000003.json,內(nèi)容如下。
??其中
- @class - 按默認(rèn)填寫
- serviceId - 正則表達(dá)式匹配客戶端請(qǐng)求到CAS的來(lái)源URL,這里是前端的地址。因?yàn)檎?qǐng)求都是從前端window.location.href過(guò)來(lái)的。
- name - 客戶端名稱
- id - ID,不要重復(fù)
- description - 描述
- evalutionOrder - 順序。一個(gè)客戶端的URL匹配成功了多個(gè)客戶端信息,以evalutionOrder最小的來(lái)處理。
- logoutType - 登出類型。可填"BACK_CHANNEL"或"FRONT_CHANNEL",分別對(duì)應(yīng)后端方式、前端方式。以不配置此項(xiàng),則默認(rèn)是"BACK_CHANNEL"。這里使用后端方式登出。
- logoutUrl - 登出URL。如果不配置此項(xiàng),CAS會(huì)向來(lái)源URL發(fā)送登出請(qǐng)求。在前后端分離的結(jié)構(gòu)下,來(lái)源是前端地址,與后端地址不同。因此需要配置該項(xiàng)為后端地址
該地址最后為上下文根+“/”。然后不需再有其他字符。
上下文根一定要有!
上下文根后的"/"一定要有!
否則無(wú)法訪問(wèn)到客戶端的Filter。另外,由于配置單點(diǎn)登出的Filter過(guò)濾URL樣式為"/*",因此后面不需要加其他字符。
客戶端后端
配置Filter
??客戶端(jl-iam-client)需要配置Filter來(lái)接收CAS單點(diǎn)登出的請(qǐng)求。
??接收請(qǐng)求后需要做這些處理:記錄登出日志、刪除Redis信息、調(diào)用Shiro的logout。
??在cas-client-core中的SingleSignOutFilter清空了Session,但是并沒(méi)有其他處理。
??由于目前還沒(méi)有找到追增處理的方法,因此現(xiàn)在將cas-client-core的SingleSignOutFilter以及SingleSignOutHandler重寫在工程里,對(duì)登出處理做了一些定制。
??客戶端使用springboot構(gòu)建。使用@WebFilter 注解,將定制的SingleSignOutFilter注冊(cè)為Filter。
注意注解的參數(shù)配置。
@WebFilter(urlPatterns = "/*", initParams = {@WebInitParam(name = "casServerUrlPrefix", value = "https://cas.example.com:8443/cas") }) public class SingleSignOutFilter extends AbstractConfigurationFilter {設(shè)置回調(diào)方法
??注銷時(shí)刪除Session的方法,是在SingleSignOutHandler的destroySession()方法中執(zhí)行的,并且CAS的票據(jù)ticket也是在該方法中解析到的。
??在SingleSignOutHandler中,設(shè)置一個(gè)回調(diào)方法,在destroySession()方法中調(diào)用。
回調(diào)方法定義
額外登出策略類 ExtraLogoutStrategy.java
public interface ExtraLogoutStrategy {/*** 登出方法* @param ticket CAS票據(jù)*/void logout(String ticket); }額外登出策略實(shí)現(xiàn)類 CasLogoutStrategy.java
public class CasLogoutStrategy implements ExtraLogoutStrategy {@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate ISysBaseAPI sysBaseAPI;@Resourceprivate BaseCommonService baseCommonService;@Overridepublic void logout(String ticket) {String token = redisUtil.get(CommonConstant.PRIFIX_CAS_TICKET + ticket).toString();String username = JwtUtil.getUsername(token);LoginUser sysUser = sysBaseAPI.getUserByName(username);if(sysUser != null) {baseCommonService.addLog("用戶名: "+sysUser.getRealname()+",退出成功!", CommonConstant.LOG_TYPE_1, null,sysUser);log.info(" 用戶名: "+sysUser.getRealname()+",退出成功!");//清空用戶登錄CAS ticket緩存redisUtil.del(CommonConstant.PRIFIX_CAS_TICKET + ticket);//清空用戶登錄Token緩存redisUtil.del(CommonConstant.PREFIX_USER_TOKEN + token);//清空用戶登錄Shiro權(quán)限緩存redisUtil.del(CommonConstant.PREFIX_USER_SHIRO_CACHE + sysUser.getId());//清空用戶的緩存信息(包括部門信息),例如sys:cache:user::<username>redisUtil.del(String.format("%s::%s", CacheConstant.SYS_USERS_CACHE, sysUser.getUsername()));//調(diào)用shiro的logoutSecurityUtils.getSubject().logout();}} }調(diào)整CAS源碼 SingleSignOutHandler.java
/** 額外登出處理策略回調(diào)類 **/private ExtraLogoutStrategy extraLogoutStrategy; /*** 設(shè)置額外登出策略* @param extraLogoutStrategy 額外登出策略*/public void setExtraLogoutStrategy(ExtraLogoutStrategy extraLogoutStrategy) {this.extraLogoutStrategy = extraLogoutStrategy;}回調(diào)方法設(shè)置
修改部分 調(diào)整CAS源碼 SingleSignOutFilter.java
@WebFilter(urlPatterns = "/*", initParams = {@WebInitParam(name = "casServerUrlPrefix", value = "https://iamlocal.90tech.cn:8443/cas") }) @Qualifier("casLogoutStrategy")@Autowiredprivate ExtraLogoutStrategy casLogoutStrategy; HANDLER.setExtraLogoutStrategy(casLogoutStrategy);完整類 SingleSignOutFilter.java
@Slf4j @WebFilter(urlPatterns = "/*", initParams = {@WebInitParam(name = "casServerUrlPrefix", value = "https://iamlocal.90tech.cn:8443/cas") }) public class SingleSignOutFilter extends AbstractConfigurationFilter {private static final SingleSignOutHandler HANDLER = new SingleSignOutHandler();private final AtomicBoolean handlerInitialized = new AtomicBoolean(false);@Qualifier("casLogoutStrategy")@Autowiredprivate ExtraLogoutStrategy casLogoutStrategy;@Overridepublic void init(final FilterConfig filterConfig) throws ServletException {log.info("SingleSignOutFilter init.");super.init(filterConfig);if (!isIgnoreInitConfiguration()) {setArtifactParameterName(getString(ConfigurationKeys.ARTIFACT_PARAMETER_NAME));setLogoutParameterName(getString(ConfigurationKeys.LOGOUT_PARAMETER_NAME));setRelayStateParameterName(getString(ConfigurationKeys.RELAY_STATE_PARAMETER_NAME));setLogoutCallbackPath(getString(ConfigurationKeys.LOGOUT_CALLBACK_PATH));HANDLER.setArtifactParameterOverPost(getBoolean(ConfigurationKeys.ARTIFACT_PARAMETER_OVER_POST));HANDLER.setEagerlyCreateSessions(getBoolean(ConfigurationKeys.EAGERLY_CREATE_SESSIONS));HANDLER.setExtraLogoutStrategy(casLogoutStrategy);}HANDLER.init();handlerInitialized.set(true);}回調(diào)方法調(diào)用
修改部分 調(diào)整CAS源碼 SingleSignOutHandler.destroySession()
if (this.extraLogoutStrategy != null) {this.extraLogoutStrategy.logout(token);}完整方法 SingleSignOutHandler.destroySession()
/*** Destroys the current HTTP session for the given CAS logout request.** @param request HTTP request containing a CAS logout message.*/private void destroySession(final HttpServletRequest request) {String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters);if (CommonUtils.isBlank(logoutMessage)) {logger.error("Could not locate logout message of the request from {}", this.logoutParameterName);return;}if (!logoutMessage.contains("SessionIndex")) {logoutMessage = uncompressLogoutMessage(logoutMessage);}logger.trace("Logout request:\n{}", logoutMessage);final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");if (CommonUtils.isNotBlank(token)) {final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);if (session != null) {final String sessionID = session.getId();logger.debug("Invalidating session [{}] for token [{}]", sessionID, token);try {session.invalidate();} catch (final IllegalStateException e) {logger.debug("Error invalidating session.", e);}this.logoutStrategy.logout(request);if (this.extraLogoutStrategy != null) {this.extraLogoutStrategy.logout(token);}}}}CAS ticket緩存
??在CAS代碼處理中,是以接收到的ticket為憑據(jù)的。我的系統(tǒng)通過(guò)緩存token實(shí)現(xiàn)認(rèn)證。
??所以,為了CAS能與系統(tǒng)成功交互,需要將CAS ticket緩存,并與系統(tǒng)中的token建立關(guān)聯(lián)。
上文 #額外登出策略實(shí)現(xiàn)類 CasLogoutStrategy.java 中,已經(jīng)通過(guò)CAS ticket緩存取到了token,并將ticket和token的緩存數(shù)據(jù)都刪除了。
修改部分
// 設(shè)置超時(shí)時(shí)間redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME*2 / 1000);redisUtil.set(CommonConstant.PRIFIX_CAS_TICKET + ticket, token);redisUtil.expire(CommonConstant.PRIFIX_CAS_TICKET + ticket, JwtUtil.EXPIRE_TIME*2 / 1000);總結(jié)
以上是生活随笔為你收集整理的CAS单点登出,调整CAS源码,实现前后端分离单点登出、清除redis、shiro登录状态的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android+直播点赞,Android
- 下一篇: oracle sql execute e