在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>

 

posted on 2016-12-23 13:29  little fat  阅读(1053)  评论(0编辑  收藏  举报