天天看點

登出登陸 清除緩存session CAS shiro redis

内嵌系統登出,session統一登出,redis管理,單機與叢集

     在項目中遇到了這麼一個問題,在這裡記錄當時解決的方式

    出現的問題:系統内嵌系統,當時外系統登出使用者後發現内嵌iframe的另一個系統沒有登出,緩存仍然在,依然可以使用使用者身份通路,外系統登陸另一個使用者賬号時,内嵌系統任然是上一次或第一次登陸的使用者資訊,導緻使用者資訊不比對和其它操作的漏洞。

    調試很多次,發現問題出在由于外系統和内嵌系統session不是一個session,沒有做session共享登出時CAS會通知外系統下的所有子系統退出,由于内嵌系統session沒有清除而緻。

    解決過程:

配置檔案:位址根據自己項目配置

# spring.redis
spring.redis.cluster.nodes=localhost:7001,localhost:7002,localhost:7003,localhost:7004,localhost:7005,localhost:7006
spring.redis.pool.max-active=300
spring.redis.database=0
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=100
spring.redis.pool.min-idle=20
spring.redis.timeout=60000

# redis session
spring.session.store-type=redis
# spring.cas
spring.cas.cas-server-login-url = https://localhost:7443/cas/login
spring.cas.cas-server-url-prefix = https://localhost:7443/cas
spring.cas.server-name = http://localhost/web/index
shiro-cas.casServerUrlPrefix = https://local:7443/cas
shiro-cas.casLoginUrl = ${shiro-cas.casServerUrlPrefix}/login
shiro-cas.casLogoutUrl = ${shiro-cas.casServerUrlPrefix}/logout
shiro-cas.shiroServerUrlPrefix = 内嵌的位址
shiro-cas.casFilterUrlPattern = /cas
shiro-cas.logout = ${shiro-cas.shiroServerUrlPrefix}/logout
shiro-cas.logoutFilter = /logout
shiro-cas.loginUrl = ${shiro-cas.casLoginUrl}?service=${shiro-cas.shiroServerUrlPrefix}${shiro-cas.casFilterUrlPattern}
shiro-cas.logoutUrl = ${shiro-cas.casLogoutUrl}?service=${shiro-cas.casLoginUrl}?service=${shiro-cas.shiroServerUrlPrefix}${shiro-cas.casFilterUrlPattern}
           

原項目沒接Shiro,隻用CAS做單點,修改後,将cas的配置注釋掉

//@Configuration
public class CasConfig {
           

重新使用shiro-cas。

pom檔案 注釋cas-client-core,新加shiro-spring,shiro-ehcache,shiro-cas

<!--cas -->
        <!--<dependency>-->
            <!--<groupId>org.jasig.cas.client</groupId>-->
            <!--<artifactId>cas-client-core</artifactId>-->
            <!--<version>3.4.1</version>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-cas</artifactId>
            <version>1.2.4</version>
        </dependency>
           

配置類:

1.CasShiroConfig CasShiro的配置檔案,常用配置,以及攔截的請求規則

package com.demo;

import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 *FILE : CasShiroConfig
 *DATE : 
 *AUTHOR 
 */

@Configuration
public class CasShiroConfig {

    @Autowired
    ShrioCasAutoconfig autoconfig;

    private String casFilterName = "casFilter";




    @Bean
    public MyShiroCasRealm myShiroCasRealm() {
        MyShiroCasRealm realm = new MyShiroCasRealm();

        realm.setCasServerUrlPrefix(autoconfig.getCasServerUrlPrefix());
        /**用戶端回調位址**/
        realm.setCasService(autoconfig.getShiroServerUrlPrefix() + autoconfig.getCasFilterUrlPattern());
        return realm;
    }

    /**
     * 注冊單點登出listener
     * @return
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public ServletListenerRegistrationBean singleSignOutHttpSessionListener(){
        ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
        bean.setListener(new SingleSignOutHttpSessionListener());
        bean.setEnabled(true);
        return bean;
    }

    /**
     * 注冊單點登出filter
     * @return
     */
    @Bean
    public FilterRegistrationBean singleSignOutFilter(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setName("singleSignOutFilter");
        bean.setFilter(new SingleSignOutFilterExt());
        bean.addUrlPatterns("/*");
//        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        bean.setEnabled(true);

        return bean;
    }



    /**
     * 注冊DelegatingFilterProxy(Shiro)
     *
     * @return
     * @author
     * @create  
     */
    @Bean
    public FilterRegistrationBean delegatingFilterProxy() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
        //  該值預設為false,表示生命周期由SpringApplicationContext管理,設定為true則表示由ServletContainer管理
        filterRegistration.addInitParameter("targetFilterLifecycle", "true");
        filterRegistration.setEnabled(true);
        filterRegistration.addUrlPatterns("/*");
//        filterRegistration.setOrder(Integer.MAX_VALUE-10);
        return filterRegistration;
    }


    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
        daap.setProxyTargetClass(true);
        return daap;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroCasRealm myShiroCasRealm) {
        DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
        dwsm.setRealm(myShiroCasRealm);
        dwsm.setSubjectFactory(new CasSubjectFactory());
        return dwsm;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(securityManager);
        return aasa;
    }


    /**
     * CAS過濾器
     *
     * @return
     * @author 
     * @create  
     */
    @Bean(name = "casFilter")
    public CasFilter getCasFilter() {
        CasFilter casFilter = new CasFilter();
        casFilter.setName(casFilterName);
        casFilter.setEnabled(true);
        // 登入失敗後跳轉的URL,也就是 Shiro 執行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer驗證tiket
        // 我們選擇認證失敗後再打開登入頁面
        casFilter.setFailureUrl(autoconfig.getCasLoginUrl());
        return casFilter;
    }

    /**
     * ShiroFilter
     * 
     * 讀取資料庫相關配置,配置到 shiroFilterFactoryBean 的通路規則中。實際項目中,請使用自己的Service來處理業務邏輯。
     *
     * @param securityManager
     * @param casFilter
     * @return
     * @author 
     * @create 
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必須設定 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl(autoconfig.getLoginUrl());
//         登入成功後要跳轉的連接配接
        shiroFilterFactoryBean.setSuccessUrl("/index");
//        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        // 添加casFilter到shiroFilter中
        Map<String, Filter> filters = new HashMap<>();
        filters.put("casFilter", casFilter);
//        LogoutFilter logoutFilter = new LogoutFilter();
//        logoutFilter.setRedirectUrl(autoconfig.getLogoutUrl());
//        filters.put("logoutFilter",logoutFilter);
        shiroFilterFactoryBean.setFilters(filters);

        loadShiroFilterChain(shiroFilterFactoryBean);
        return shiroFilterFactoryBean;
    }

    /**
     * 加載shiroFilter權限控制規則(從資料庫讀取然後配置),角色/權限資訊由MyShiroCasRealm對象提供doGetAuthorizationInfo實作擷取來的
     *
     * @author 
     * @create 
     */
    private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
        /// 下面這些規則配置最好配置到配置檔案中 ///
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        // authc:該過濾器下的頁面必須登入後才能通路,它是Shiro内置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
        // anon: 可以了解為不攔截
        // user: 登入了就不攔截
        // roles["admin"] 使用者擁有admin角色
        // perms["permission1"] 使用者擁有permission1權限
        // filter順序按照定義順序比對,比對到就驗證,驗證完畢結束。
        // url比對通配符支援:? * **,分别表示比對1個,比對0-n個(不含子路徑),比對下級所有路徑

        //1.shiro內建cas後,首先添加該規則
        filterChainDefinitionMap.put("/cas", casFilterName);
//        filterChainDefinitionMap.put("/logout","logoutFilter");

        //2.不攔截的請求,根據自己兩目配置
        filterChainDefinitionMap.put("/static/**","anon");
        filterChainDefinitionMap.put("/css/**","anon");
        filterChainDefinitionMap.put("/js/**","anon");
        filterChainDefinitionMap.put("/login", "anon");
//        filterChainDefinitionMap.put("/logout","anon");
        filterChainDefinitionMap.put("/error","anon");
        filterChainDefinitionMap.put("/api/**","anon");

        //3.攔截的請求(從本地資料庫擷取或者從casserver擷取(webservice,http等遠端方式),看你的角色權限配置在哪裡)

        //需要登入
        filterChainDefinitionMap.put("/web/*", "authc");



        //4.登入過的不攔截
        filterChainDefinitionMap.put("/**", "user");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }

}

           

2.自定義MyShiroCasRealm繼承CasRealm

package com.demo;

import com.demo.SessionUserUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



/**
 * Created by 
 */
public class MyShiroCasRealm extends CasRealm {

//    @Autowired
//    protected UserService userService;

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 權限認證,為目前登入的Subject授予角色和權限
     * @see :本例中該方法的調用時機為需授權資源被通路時
     * @see :并且每次通路需授權資源時都會執行該方法中的邏輯,這表明本例中預設并未啟用AuthorizationCache
     * @see :如果連續通路同一個URL(比如重新整理),該方法不會被重複調用,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置),超過這個時間間隔再重新整理頁面,該方法會被執行
     */
    @SuppressWarnings("unchecked")
	@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        System.out.println(SessionUserUtil.getCurrentUsername());
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo authc = super.doGetAuthenticationInfo(token);

        String userName = (String) authc.getPrincipals().getPrimaryPrincipal();
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//        可在此處理session塞入一些需要的資訊
        return authc;
    }
}
           

3.RedisSessionConfig 裡面又CAS過期時間的配置

package com.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@Configuration

@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 28800)

public class RedisSessionConfig {


}
           

4.ShrioCasAutoconfig shiro-cas的配置,配置檔案中讀取加載配置進config,CAS退出、登陸的Url

package com.demo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Created 
 */
@Component
@ConfigurationProperties(prefix = "shiro-cas")
public class ShrioCasAutoconfig {

    private String casServerUrlPrefix;

    private String casLoginUrl;

    private String casLogoutUrl;

    private String shiroServerUrlPrefix;

    private String casFilterUrlPattern;

    private String loginUrl;

    private String logoutUrl;

    private String logout;

    public String getCasServerUrlPrefix() {
        return casServerUrlPrefix;
    }

    public void setCasServerUrlPrefix(String casServerUrlPrefix) {
        this.casServerUrlPrefix = casServerUrlPrefix;
    }

    public String getCasLoginUrl() {
        return casLoginUrl;
    }

    public void setCasLoginUrl(String casLoginUrl) {
        this.casLoginUrl = casLoginUrl;
    }

    public String getCasLogoutUrl() {
        return casLogoutUrl;
    }

    public void setCasLogoutUrl(String casLogoutUrl) {
        this.casLogoutUrl = casLogoutUrl;
    }

    public String getShiroServerUrlPrefix() {
        return shiroServerUrlPrefix;
    }

    public void setShiroServerUrlPrefix(String shiroServerUrlPrefix) {
        this.shiroServerUrlPrefix = shiroServerUrlPrefix;
    }

    public String getCasFilterUrlPattern() {
        return casFilterUrlPattern;
    }

    public void setCasFilterUrlPattern(String casFilterUrlPattern) {
        this.casFilterUrlPattern = casFilterUrlPattern;
    }

    public String getLoginUrl() {
        return loginUrl;
    }

    public void setLoginUrl(String loginUrl) {
        this.loginUrl = loginUrl;
    }

    public String getLogoutUrl() {
        return logoutUrl;
    }

    public void setLogoutUrl(String logoutUrl) {
        this.logoutUrl = logoutUrl;
    }

    public String getLogout() {
        return logout;
    }

    public void setLogout(String logout) {
        this.logout = logout;
    }
}
           

5.SingleSignOutExtHandler 登出過濾的處理

handler.isTokenRequest(request)方法:判斷參數中是否具有artifactParameterName屬性指定的參數名稱,預設是ticket在SingleSignOutFilterExt.java中配置 。如果存在,在redis中記錄session。

handler.isLogoutRequest(request):判斷是否具有logoutParameterName參數指定參數,預設logoutRequest在SingleSignOutFilterExt.java中配置。如果存在,則在redis中删除記錄,登出session。

recordSession: 儲存session,其中addRedisHash方法中,注釋掉的是單機redis方式

removeRedisLoginInfoByTicket方法:通過存儲的票據(Ticket)删除登陸時存入的資訊以及退出使用者的票據。相當于我們在登陸的時候,為了可以找到登陸的session,多存了一個redisHash,以項目名稱和ticket為key,session為value,退出的時候将兩個地方同時删除。

package com.demo;

import com.demo.JedisUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.session.HashMapBackedSessionMappingStorage;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisCluster;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * Performs CAS single sign-out operations in an API-agnostic fashion.
 *
 * @author Marvin S. Addison
 * @version $Revision$ $Date$
 * @since 3.1.12
 *
 */
public final class SingleSignOutExtHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(SingleSignOutExtHandler.class);

    /** Logger instance */
    private final Log log = LogFactory.getLog(getClass());

    /** Mapping of token IDs and session IDs to HTTP sessions */
    private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage();

    /** The name of the artifact parameter.  This is used to capture the session identifier. */
    private String artifactParameterName = "ticket";

    /** Parameter name that stores logout request */
    private String logoutParameterName = "logoutRequest";

    public void setSessionMappingStorage(final SessionMappingStorage storage) {
        this.sessionMappingStorage = storage;
    }

    public SessionMappingStorage getSessionMappingStorage() {
        return this.sessionMappingStorage;
    }

    /**
     * @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;
    }

    /**
     * Initializes the component for use.
     */
    public void init() {
        CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
        CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null.");
        CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannote be null.");
    }

    /**
     * Determines whether the given request contains an authentication 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));
    }

    /**
     * Determines whether the given request is a CAS logout request.
     *
     * @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(CommonUtils.safeGetParameter(request, this.logoutParameterName));
    }

    /**
     * Associates a token request with the current HTTP session by recording the mapping
     * in the the configured {@link SessionMappingStorage} container.
     *
     * @param request HTTP request containing an authentication token.
     */
    public void recordSession(final HttpServletRequest request) {
        final HttpSession session = request.getSession(true);

        final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName);
        if (log.isDebugEnabled()) {
            log.debug("Recording session for token " + token);
        }

        try {
            this.sessionMappingStorage.removeBySessionById(session.getId());
        } catch (final Exception e) {
            // ignore if the session is already marked as invalid.  Nothing we can do!
        }
        sessionMappingStorage.addSessionById(token, session);


        addRedisHash(token, token, session.getId());
    }

    /**
     * Destroys the current HTTP session for the given CAS logout request.
     *
     * @param request HTTP request containing a CAS logout message.
     */
    public void destroySession(final HttpServletRequest request) {
        final String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName);
        if (log.isTraceEnabled()) {
            log.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) {
                String sessionID = session.getId();

                if (log.isDebugEnabled()) {
                    log.debug ("Invalidating session [" + sessionID + "] for token [" + token + "]");
                }
                try {
                    session.invalidate();
                } catch (final IllegalStateException e) {
                    log.debug("Error invalidating session.", e);
                }
            }
        }

        //根據ticket,查找到對應的sessionID,并從redis中清除
        removeRedisLoginInfoByTicket(token);
    }

    private boolean isMultipartRequest(final HttpServletRequest request) {
        return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart");
    }


    private void addRedisHash(String hash, String key, String value) {
        new Thread(new Runnable() {
			//需要擷取項目名稱,以便在緩存中可以取到
            String applicationName = SpringUtilExt.getPropertyValue("spring.application.name");
            
            @Override
            public void run() {
//                JedisConnectionFactory jedisConnectionFactory = SpringUtilExt.getBean(JedisConnectionFactory.class);
//                RedisConnection redisConnection = jedisConnectionFactory.getConnection();
//                hash的名稱是類似bcs:ST-8142-Qy1cb554R9dJBXtbaEBV-cas01.example.org
//                ((Jedis)redisConnection.getNativeConnection()).hset(applicationName + ":" + hash, key, value);
//                LOGGER.info("add to redis hash {}:{}->{}", new Object[]{applicationName + ":" + hash, key, value});
//                redisConnection.close();
                try{
                    JedisUtil.setHashJedisCluster(applicationName + ":" + hash, key, value);
                    LOGGER.info("add to redis hash {}:{}->{}", new Object[]{applicationName + ":" + hash, key, value});
                }catch (Exception e){
                    LOGGER.info("==========增加session異常",e);
                }

            }
        }).start();
    }

    private void removeRedisLoginInfoByTicket(String token) {
        new Thread(new Runnable() {
            @Override
            public void run() {
 //               JedisConnectionFactory jedisConnectionFactory = SpringUtilExt.getBean(JedisConnectionFactory.class);
//                Jedis jedisConnection = (Jedis)jedisConnectionFactory.getConnection().getNativeConnection();
                String applicationName = SpringUtilExt.getPropertyValue("spring.application.name");
//                String sessionId = jedisConnection.hget(applicationName + ":" +token, token);
                try{
                    JedisCluster jedisCluster = JedisUtil.getJedisClusterConnection();
                    String sessionId = jedisCluster.hget(applicationName + ":" +token, token);
                    LOGGER.info("擷取到自定義中ticket:{}對應的sessionId:{}", new Object[]{token, sessionId});
                    String casRedisHash = "spring:session:" + applicationName + ":sessions:" + sessionId;
                    jedisCluster.del(casRedisHash);
                    LOGGER.info("成功删除redis中cas存儲的hash{}", new Object[]{casRedisHash});
                    jedisCluster.del(applicationName + ":"+token);
                    LOGGER.info("成功删除自定義cas存儲的hash{}", new Object[]{applicationName + ":"+token});
                    jedisCluster.close();
                }catch (Exception e){
                    LOGGER.info("清除session方法異常,{}",e);
                }

            }
        }).start();
    }
}
           

6.SingleSignOutFilterExt

doFilter方法中判斷分支判斷handler進入登陸時儲存或者時登出時登出,或者是已經登陸過,進行不同處理。

package com.demo;

import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.util.AbstractConfigurationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Implements the Single Sign Out protocol.  It handles registering the session and destroying the session.
 *
 * @author Scott Battaglia
 * @version $Revision$ $Date$
 * @since 3.1
 */
public final class SingleSignOutFilterExt extends AbstractConfigurationFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(SingleSignOutFilterExt.class);

    private static final SingleSignOutExtHandler handler = new SingleSignOutExtHandler();

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        if (!isIgnoreInitConfiguration()) {
            handler.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket"));
            handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", "logoutRequest"));
        }
        handler.init();
    }

    public void setArtifactParameterName(final String name) {
        handler.setArtifactParameterName(name);
    }

    public void setLogoutParameterName(final String name) {
        handler.setLogoutParameterName(name);
    }

    public void setSessionMappingStorage(final SessionMappingStorage storage) {
        handler.setSessionMappingStorage(storage);
    }

    @Override
    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        if (handler.isTokenRequest(request)) {
            handler.recordSession(request);
        } else if (handler.isLogoutRequest(request)) {
            handler.destroySession(request);
            // Do not continue up filter chain
            return;
        } else {
            log.trace("Ignoring URI " + request.getRequestURI());
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        // nothing to do
    }

    protected static SingleSignOutExtHandler getSingleSignOutHandler() {
        return handler;
    }
}

           

7.SpringCasAutoConfig spring.cas的配置

package com.demo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * @author:
 * @date:
 * Description:
 */
@Component
@ConfigurationProperties(prefix = "spring.cas")
public class SpringCasAutoConfig {

    private static final String SEPARATOR = ",";

    private String validateFilters;
    private String signOutFilters;
    private String authFilters;
    private String assertionFilters;
    private String requestWrapperFilters;

    private String casServerUrlPrefix;
    private String casServerLoginUrl;
    private String serverName;
    private boolean useSession = true;
    private boolean redirectAfterValidation = true;

    public List<String> getValidateFilters() {
        return Arrays.asList(validateFilters.split(SEPARATOR));
    }

    public void setValidateFilters(String validateFilters) {
        this.validateFilters = validateFilters;
    }

    public List<String> getSignOutFilters() {
        return Arrays.asList(signOutFilters.split(SEPARATOR));
    }

    public void setSignOutFilters(String signOutFilters) {
        this.signOutFilters = signOutFilters;
    }

    public List<String> getAuthFilters() {
        return Arrays.asList(authFilters.split(SEPARATOR));
    }

    public void setAuthFilters(String authFilters) {
        this.authFilters = authFilters;
    }

    public List<String> getAssertionFilters() {
        return Arrays.asList(assertionFilters.split(SEPARATOR));
    }

    public void setAssertionFilters(String assertionFilters) {
        this.assertionFilters = assertionFilters;
    }

    public List<String> getRequestWrapperFilters() {
        return Arrays.asList(requestWrapperFilters.split(SEPARATOR));
    }

    public void setRequestWrapperFilters(String requestWrapperFilters) {
        this.requestWrapperFilters = requestWrapperFilters;
    }

    public String getCasServerUrlPrefix() {
        return casServerUrlPrefix;
    }

    public void setCasServerUrlPrefix(String casServerUrlPrefix) {
        this.casServerUrlPrefix = casServerUrlPrefix;
    }

    public String getCasServerLoginUrl() {
        return casServerLoginUrl;
    }

    public void setCasServerLoginUrl(String casServerLoginUrl) {
        this.casServerLoginUrl = casServerLoginUrl;
    }

    public String getServerName() {
        return serverName;
    }

    public void setServerName(String serverName) {
        this.serverName = serverName;
    }

    public boolean isRedirectAfterValidation() {
        return redirectAfterValidation;
    }

    public void setRedirectAfterValidation(boolean redirectAfterValidation) {
        this.redirectAfterValidation = redirectAfterValidation;
    }

    public boolean isUseSession() {
        return useSession;
    }

    public void setUseSession(boolean useSession) {
        this.useSession = useSession;
    }

}
           

8.SpringUtilExt 工具類,可以取配置檔案資訊

package com.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class SpringUtilExt implements ApplicationContextAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(SpringUtilExt.class);

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringUtilExt.applicationContext == null) {
            SpringUtilExt.applicationContext = applicationContext;
        }
    }

    //擷取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通過name擷取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通過class擷取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通過name,以及Clazz傳回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

    // 通過屬性名稱擷取屬性值
    public static String getPropertyValue(String propertyKey) {
        Environment env = getApplicationContext().getBean(Environment.class);
        String propertyValue = env.getProperty(propertyKey);
        LOGGER.info("擷取到屬性名稱:【{}】 -> 【{}】", new Object[]{propertyKey, propertyValue});
        return propertyValue;
    }
}
           

9.redis叢集方式擷取jedis操作

package com.demo;

import com.demo.SpringUtilExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
 * 
 */
@Service
public class JedisUtil {

    private static Logger logger = LoggerFactory.getLogger(JedisUtil.class);

    private static String clusterNodes = SpringUtilExt.getPropertyValue("spring.redis.cluster.nodes");


    public static String getClusterNodes() {
        return clusterNodes;
    }

    /**
     * 擷取叢集模式的redis連接配接資訊
     * @return
     */
    public static JedisCluster getJedisClusterConnection(){

        JedisCluster cluster = null;
        Set<HostAndPort> nodes = new LinkedHashSet<HostAndPort>();
        for(String item :clusterNodes.split(",")){
            nodes.add(new HostAndPort(item.split(":")[0],Integer.valueOf(item.split(":")[1])));
        }
        try {
            cluster = new JedisCluster(nodes,10000);
        } catch (Exception e) {
            logger.error("系統異常!",e);
        }
        return  cluster;

    }


    /**
     * 擷取叢集模式的redis連接配接資訊
     * @return
     */
    public static void closeJedisClusterConnection(JedisCluster jedisCluster){
        try {
            jedisCluster.close();
        } catch (IOException e) {
            logger.error("jedis關閉異常!",e);
        }
    }

    /**
     * 從叢集模式的redis中擷取資料
     * @param key
     * @return
     */
    public static String getStringFromJedisCluster(String key){
        JedisCluster jedisCluster = getJedisClusterConnection();
        return jedisCluster.get(key);
    }

    /**
     * 向叢集模式的redis中塞值,如果逾時時間設定的<=0,認為不必設定有效期
     * @param key
     * @param value
     * @return
     */
    public static String setString2JedisCluster(String key,String value,int seconds){
        JedisCluster jedisCluster = getJedisClusterConnection();
        if(seconds <= 0){
            return jedisCluster.set(key,value);
        }else{
            return jedisCluster.setex(key,seconds,value);
        }
    }


    /**
     * 向叢集模式的redis中塞值,如果逾時時間設定的<=0,認為不必設定有效期
     * @param key
     * @param value
     * @return
     */
    public static void setHashJedisCluster(String hash,String key,String value){
        JedisCluster jedisCluster = getJedisClusterConnection();
        jedisCluster.hset(hash,key,value);
        try {
            jedisCluster.close();
        } catch (Exception e){
            logger.error("系統異常!",e);
        }
    }

}

           

啟動類加注解

@EnableRedisHttpSession(redisNamespace=“項目名稱”,maxInactiveIntervalInSeconds = 28800)

項目名稱要與存session那邊一緻,否則找不到删除不掉。

到這裡本次問題就差不多解決了,可能有漏記的,再繼續學習補充,小白學習日記

參考了很多大佬的文章,得罪了😄,也忘了有哪些了,就不記錄了。

繼續閱讀