在shiro-cas中实现 Jasig-cas的Single Sign Out 功能
1 Single Sign Out 功能
即单点登出功能。也就是在任意子系统进行登出操作后,其他子系统会自动登出。
实际CAS登出的步骤为

所以每个子系统都需要实现一个sso登出响应。
cas-client-core包中有Single Sign Out的Session容器实现。
具体在包 org.jasig.cas.client.session 中。

2 实现Shiro的SSO登出功能
1 实现CasSecurityManager
主要目的是为了在登陆成功后保存 ST票据,并与 Shiro的sessionId进行关系映射。
/** * 安全管理中心。<br> * 主要目的是保存session和ticket之间的关系。 * @author Weicl * @since 2016.4.25 */ public class CasSecurityManager extends DefaultWebSecurityManager{ Logger logger = LoggerFactory.getLogger(getClass()); @Override protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) { if (token instanceof CasToken) { logger.info("save token info: " + token.getCredentials() + " -> " + subject.getSession(false).getId()); SsoUtils.putTokenCache((String)token.getCredentials(), subject.getSession(false).getId()); subject.getSession(false).setAttribute("_serviceTicket_", token.getCredentials()); } super.onSuccessfulLogin(token, info, subject); } }
PS: SsoUtils的putTokenCache。可以用ehcache或HashMap实现,其实没关系。
2 实现LogoutSloFilter
这个拦截器用来处理sso登出请求。
/** * 单点登出处理 * @author Weicl * @since 2016.4.25 */ public class LogoutSloFilter extends AdviceFilter{ private final Logger logger = LoggerFactory.getLogger(getClass()); private final Pattern pattern = Pattern.compile("<samlp:SessionIndex>([^<]*)</samlp:SessionIndex>"); @Autowired private NativeSessionManager nativeSessionManager; @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { try { String logoutRequest = request.getParameter("logoutRequest"); String serviceTicket = extractServiceTicket(logoutRequest); logger.info(" slo serviceTicket : " + serviceTicket); String sessionId = (String)SsoUtils.getTokenCache(serviceTicket); nativeSessionManager.stop(new DefaultSessionKey(sessionId)); } catch (Exception e) { e.printStackTrace(); } response.getWriter().write("OK"); return false; } /** * 获取登出请求中的Ticket * @param logoutRequest * @return */ private String extractServiceTicket(String logoutRequest) { Matcher matcher = pattern.matcher(logoutRequest); if (matcher.find()) { return matcher.group(1); } return ""; } }
3 实现TicketSessionListener
这个类的作用是当session终止的时候,释放 SsoUtil 中的tokenCache。因为应用的session不存在了,保存这个映射关系也没有意义,而且浪费缓存空间。
/** * 票据及session监听器 * @author Weicl * @since 2016.4.25 */ public class TicketSessionListener implements SessionListener{ Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onStart(Session session) { } @Override public void onStop(Session session) { logger.info("==============================="); logger.info("stop session:" + session.getId()); String ticket = (String)session.getAttribute("_serviceTicket_"); if (ticket != null) { logger.info("remove serviceTicket: " + ticket); SsoUtils.removeTokenCache(ticket); } } @Override public void onExpiration(Session session) { onStop(session); } }
4 配置到spring中
一下为添加/修正的关键代码。其他shiro的配置这边就不贴出来了。
<!-- Shiro权限过滤过滤器定义 -->
<bean name="shiroFilterChainDefinitions" class="java.lang.String">
<constructor-arg>
<value>
/static/** = anon
/cas = cas
/login = authc
/logout = logout
/logoutSlo = logoutSlo
/** = user
</value>
</constructor-arg>
</bean>
<!-- 安全认证过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="${cas.server.url}/login?service=${cas.project.url}${adminPath}/cas" />
<!--
<property name="loginUrl" value="${adminPath}/login" /> -->
<property name="successUrl" value="${adminPath}/login" />
<property name="filters">
<map>
<entry key="cas" value-ref="casFilter"/>
<entry key="authc" value-ref="formAuthenticationFilter"/>
<entry key="logout" value-ref="logoutFilter"></entry>
<entry key="logoutSlo" value-ref="logoutSloFilter"></entry>
</map>
</property>
<property name="filterChainDefinitions">
<ref bean="shiroFilterChainDefinitions"/>
</property>
</bean>
<bean id="logoutSloFilter" class="cn.xxxxxx.base.modules.sys.security.LogoutSloFilter">
</bean>
<!-- 定义Shiro安全管理配置 -->
<bean id="securityManager" class="cn.xxxxxx.base.common.security.shiro.session.CasSecurityManager">
<!-- <property name="realm" ref="systemAuthorizingRealm" /> -->
<property name="realm" ref="systemCasRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
浙公网安备 33010602011771号