1. 程式人生 > >shiro+cas+spring-data-redis實現多系統單點登入和分散式專案的session同步

shiro+cas+spring-data-redis實現多系統單點登入和分散式專案的session同步

CSDN開通很久了,但是一直沒寫東西,2018年了,這是我CSDN的第一篇文章,歡迎各位評論探討和指點。  

一、背景:

現在公司的業務系統要做多臺分散式叢集,由於是web專案,要做session同步,想到的方案是用目前火熱的redis資料庫儲存session,還有業務系統已經是使用shiro+cas做了單點登入的。

   參考了一些行家的文章,自己加工寫了一個sharesession的專案,抽取成了一個jar包,可匯入需要同步session的業務系統。

二、專案簡介:

    程式碼結構圖

使用gradle構建的專案

三、原始碼分析

1.gradle的構建檔案build.gradle如下

group 'com.gaojccn'
version '1.0.0-SNAPSHOT'

apply plugin: 'idea'
apply plugin: 'java'
apply plugin: "maven"
apply plugin: 'groovy'

sourceCompatibility = 1.7
compileJava.options.encoding = 'UTF-8'
compileJava.options.compilerArgs = ["-Xlint:unchecked", "-Xlint:deprecation"]

compileTestJava.options.encoding = 'UTF-8'
compileTestJava.options.compilerArgs = ["-Xlint:unchecked", "-Xlint:deprecation"]

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
}

repositories {
    mavenCentral()
    /*本地中央倉庫*/
    maven {
        url "http://192.168.35.26:8081/nexus/content/repositories/central/"
    }
    maven {
        url "http://192.168.35.26:8081/nexus/content/repositories/thirdparty/"
    }
    maven {
        url "http://192.168.35.26:8081/nexus/content/repositories/releases/"
    }
    maven {
        url "http://192.168.35.26:8081/nexus/content/repositories/snapshots/"
    }
}


dependencies {
    compile 'org.apache.shiro:shiro-core:1.2.4'
    compile 'org.apache.shiro:shiro-cas:1.2.4'
    compile 'org.apache.shiro:shiro-web:1.2.4'
    compile 'org.apache.shiro:shiro-all:1.2.4'
    compile 'org.apache.shiro:shiro-ehcache:1.2.4'
    compile("redis.clients:jedis:2.1.0")
    compile 'org.springframework.data:spring-data-redis:1.0.2.RELEASE'
    compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'

    //testCompile 'org.springframework:spring-test:3.1.2.RELEASE'
    //testCompile 'com.github.springtestdbunit:spring-test-dbunit:1.0.1'
    //testCompile 'org.unitils:unitils-dbunit:3.3'
   // testCompile group: 'junit', name: 'junit', version: '4.12'
}

project.ext {
    versionFile = file('version.properties') //版本屬性
}

class ProjectVersion {
    Integer major
    Integer minor
    Integer bugfix
    Boolean release

    ProjectVersion(Integer major, Integer minor, Integer bugfix) {
        this.major = major
        this.minor = minor
        this.bugfix = bugfix
        this.release = Boolean.FALSE
    }

    ProjectVersion(Integer major, Integer minor, Integer bugfix, Boolean release) {
        this.major = major
        this.minor = minor
        this.bugfix = bugfix
        this.release = release
    }

    @Override
    String toString() {
        "$major.$minor.$bugfix${release ? '' : '-SNAPSHOT'}"
    }
}

task printVersion {
    doLast {
        logger.quiet("version:$version")
    }
}
task loadVersion {
    project.version = readVersion()
}

def isRelease() {
    ProjectVersion projectVersion = readVersion()
    projectVersion.release
}

ProjectVersion readVersion() {
    logger.quiet('Reading the version file.')
    if (!versionFile.exists()) {
        throw new GradleException("Required version file does not exists:$versionFile.canonicalPath")
    }
    Properties versionProps = new Properties()
    versionFile.withInputStream { stream ->
        versionProps.load(stream)
    }
    new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.bugfix.toInteger(), versionProps.release.toBoolean())
}

task writeVersionFile << {
    def versionFilePath = 'version.json'
    File file = new File(versionFilePath)
    if (!file.exists()) file.createNewFile()

    def versionFile = new File(versionFilePath)
    versionFile.text = '{"version":"' + version + '"}'
}

uploadArchives {
    dependsOn build
    configuration = configurations.archives
    repositories.mavenDeployer {
        repository(url: 'http://192.168.35.26:8081/nexus/content/repositories/releases/') {
            authentication(userName: "admin", password: "admin123")
        }

        snapshotRepository(url: 'http://192.168.35.26:8081/nexus/content/repositories/snapshots/') {
            authentication(userName: "admin", password: "admin123")
        }

        pom.project {
            name 'gaojccn'
            packaging 'jar'
            description 'none'
//            url 'http://192.168.35.26:8081/nexus/content/repositories/releases/'
            url 'http://192.168.35.26:8081/nexus/content/repositories/snapshots/'
            groupId "com.gaojccn"
            artifactId "sharesession"
            version version
        }
    }
}

processResources {
    dependsOn writeVersionFile
}

jar {
    baseName = 'sharesession'
    version version
    manifest {
        attributes 'Implementation-Title': 'session',
                'Implementation-Version': version,
                'Created-By': 'gaojc'
    }
}

task makeReleaseVersion(group: 'versioning', description: 'Makes project a release version.') << {
    ant.propertyfile(file: versionFile) {
        entry(key: 'release', type: 'string', operation: '=', value: 'true')
    }
}

2.主配置檔案share_session.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util-3.0.xsd">

    <!--redis連線配置-->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <property name="maxActive" value="${redis.maxActive}"/>
        <property name="maxWait" value="${redis.maxWait}"/>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>

    <bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="database" value="${redis.database}"/>
        <property name="timeout" value="${redis.timeout}"/>
        <property name="poolConfig" ref="poolConfig"/>
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="redisConnectionFactory"/>
    </bean>

    <!--shiro配置-->

    <!-- Shiro生命週期處理器-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- session管理 -->
        <property name="sessionManager" ref="sessionManager"/>
        <property name="subjectFactory" ref="casSubjectFactory"/>
        <property name="realms">
            <list>
                <ref bean="casRealm"/>
            </list>
        </property>
    </bean>

    <!-- session管理 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- 會話超時時間,單位:毫秒 -->
        <property name="deleteInvalidSessions" value="true"></property>
        <property name="sessionDAO" ref="sessionDao"></property>
        <property name="cacheManager" ref="cacheManager" />
        <!-- sessionIdCookie的實現,用於重寫覆蓋容器預設的JSESSIONID -->
        <property name="sessionIdCookie" ref="sharesession"/>
        <!-- 定時清理失效會話, 清理使用者直接關閉瀏覽器造成的孤立會話   -->
        <property name="sessionValidationInterval" value="${session.sessionValidationInterval}"/>
        <!--session事件監聽器-->
        <property name="sessionListeners">
            <list>
                <ref bean="redisSessionListener"/>
            </list>
        </property>
    </bean>

    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />

    <!-- sessionIdCookie的實現,用於重寫覆蓋容器預設的JSESSIONID -->
    <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- cookie的name,對應的預設是 JSESSIONID -->
        <constructor-arg value="${session.cookieName}"/>
        <!-- jsessionId的path為 / 用於多個系統共享jsessionId -->
        <property name="path" value="/"/>
        <property name="httpOnly" value="true"/>
        <!--maxAge=-1表示瀏覽器關閉時失效此Cookie-->
        <property name="maxAge" value="-1"/>
    </bean>

    <!--casSubjectFactory-->
    <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory"/>

    <!--casRealm-->
    <bean id="casRealm" class="org.apache.shiro.cas.CasRealm">
        <property name="defaultRoles" value="${casRealm.roles}"/>
        <property name="casServerUrlPrefix" value="${casRealm.casServerUrlPrefix}"/>
        <property name="casService" value="${casRealm.casService}"/>
    </bean>

    <!-- 相當於呼叫SecurityUtils.setSecurityManager(securityManager) -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
        <property name="arguments" ref="securityManager"/>
    </bean>

    <!-- 安全認證過濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="${loginUrl}"/>
        <property name="successUrl" value="/index.html" />
        <property name="unauthorizedUrl" value="/error.jsp"/>
        <property name="filters">
            <util:map>
                <entry key="casFilter" value-ref="casFilter"/>
                <entry key="authc" value-ref="authc"/>
                <entry key="roles" value-ref="roles"/>
                <entry key="rest" value-ref="rest"/>
                <entry key="logout" value-ref="logout"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions" value="${filterChainDefinitions}"/>
    </bean>

    <!--CAS單點登入filter-->
    <bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
        <property name="failureUrl" value="${casFilter.failureUrl}"/>
    </bean>

    <!--authc filter-->
    <bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/>

    <!--roles filter-->
    <bean id="roles" class="org.apache.shiro.web.filter.authz.RolesAuthorizationFilter"/>

    <!--rest filter-->
    <bean id="rest" class="org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter">
        <property name="loginUrl" value="${loginUrl}"/>
    </bean>

    <!--logout filter-->
    <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <property name="redirectUrl" value="${logout.redirectUrl}"/>
    </bean>
</beans>
<!--redis連線配置--> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.maxIdle}"/> <property name="maxActive" value="${redis.maxActive}"/> <property name="maxWait" value="${redis.maxWait}"/> <property name="testOnBorrow" value="${redis.testOnBorrow}"/> </bean> <bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${redis.host}"/> <property name="port" value="${redis.port}"/> <property name="database" value="${redis.database}"/> <property name="timeout" value="${redis.timeout}"/> <property name="poolConfig" ref="poolConfig"/> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="redisConnectionFactory"/> </bean> <!--shiro配置--> <!-- Shiro生命週期處理器--> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- SecurityManager --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- session管理 --> <property name="sessionManager" ref="sessionManager"/> <property name="subjectFactory" ref="casSubjectFactory"/> <property name="realms"> <list> <ref bean="casRealm"/> </list> </property> </bean> <!-- session管理 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- 會話超時時間,單位:毫秒 --> <property name="deleteInvalidSessions" value="true"></property> <property name="sessionDAO" ref="sessionDao"></property> <property name="cacheManager" ref="cacheManager" /> <!-- sessionIdCookie的實現,用於重寫覆蓋容器預設的JSESSIONID --> <property name="sessionIdCookie" ref="sharesession"/> <!-- 定時清理失效會話, 清理使用者直接關閉瀏覽器造成的孤立會話 --> <property name="sessionValidationInterval" value="${session.sessionValidationInterval}"/> <!--session事件監聽器--> <property name="sessionListeners"> <list> <ref bean="redisSessionListener"/> </list> </property> </bean> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" /> <!-- sessionIdCookie的實現,用於重寫覆蓋容器預設的JSESSIONID --> <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- cookie的name,對應的預設是 JSESSIONID --> <constructor-arg value="${session.cookieName}"/> <!-- jsessionId的path為 / 用於多個系統共享jsessionId --> <property name="path" value="/"/> <property name="httpOnly" value="true"/> <!--maxAge=-1表示瀏覽器關閉時失效此Cookie--> <property name="maxAge" value="-1"/> </bean> <!--casSubjectFactory--> <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory"/> <!--casRealm--> <bean id="casRealm" class="org.apache.shiro.cas.CasRealm"> <property name="defaultRoles" value="${casRealm.roles}"/> <property name="casServerUrlPrefix" value="${casRealm.casServerUrlPrefix}"/> <property name="casService" value="${casRealm.casService}"/> </bean> <!-- 相當於呼叫SecurityUtils.setSecurityManager(securityManager) --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean> <!-- 安全認證過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="${loginUrl}"/> <property name="successUrl" value="/index.html" /> <property name="unauthorizedUrl" value="/error.jsp"/> <property name="filters"> <util:map> <entry key="casFilter" value-ref="casFilter"/> <entry key="authc" value-ref="authc"/> <entry key="roles" value-ref="roles"/> <entry key="rest" value-ref="rest"/> <entry key="logout" value-ref="logout"/> </util:map> </property> <property name="filterChainDefinitions" value="${filterChainDefinitions}"/> </bean> <!--CAS單點登入filter--> <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> <property name="failureUrl" value="${casFilter.failureUrl}"/> </bean> <!--authc filter--> <bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/> <!--roles filter--> <bean id="roles" class="org.apache.shiro.web.filter.authz.RolesAuthorizationFilter"/> <!--rest filter--> <bean id="rest" class="org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter"> <property name="loginUrl" value="${loginUrl}"/> </bean> <!--logout filter--> <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <property name="redirectUrl" value="${logout.redirectUrl}"/> </bean> </beans>

3.src目錄下的java檔案

 3.1 RedisManager

package com.gaojccn.sharesession;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.dao.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Service
public class RedisManager {
    private static Logger logger = LoggerFactory.getLogger(RedisManager.class);
    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    private RedisSerializer<String> serializer = new StringRedisSerializer();

    /**
     * 新增快取資料(給定key已存在,進行覆蓋)
     *
     * @param key
     * @param obj
     * @throws DataAccessException
     */
    public <T> void set(String key, T obj) throws DataAccessException {
        final byte[] bkey = serializer.serialize(key);
        final byte[] bvalue = serializer.serialize(obj.toString());

        logger.info("set key {} value {}", key, obj);
        redisTemplate.execute(new RedisCallback<Void>() {
            @Override
            public Void doInRedis(RedisConnection connection) throws DataAccessException {
                connection.set(bkey, bvalue);
                return null;
            }
        });
    }

    /**
     * 新增快取資料(給定key已存在,不進行覆蓋,直接返回false)
     *
     * @param key
     * @param obj
     * @return 操作成功返回true,否則返回false
     * @throws DataAccessException
     */
    public <T> boolean setNX(String key, T obj) throws DataAccessException {
        final byte[] bkey = serializer.serialize(key);
        final byte[] bvalue = serializer.serialize(obj.toString());
        logger.info("setNX key {} value {}", key, obj);
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.setNX(bkey, bvalue);
            }
        });

        return result;
    }

    /**
     * 新增快取資料,設定快取失效時間
     *
     * @param key
     * @param obj
     * @param expireSeconds 過期時間,單位 秒
     * @throws DataAccessException
     */
    public <T> void setEx(String key, T obj, final long expireSeconds) throws DataAccessException {
        final byte[] bkey = serializer.serialize(key);
        final byte[] bvalue = serializer.serialize(obj.toString());
        logger.info("setEx key {} value {}", key, obj);
        redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                connection.setEx(bkey, expireSeconds/1000, bvalue);
                return true;
            }
        });
    }

    /**
     * 獲取key對應value
     *
     * @param key
     * @return
     * @throws DataAccessException
     */
    public <T> T get(final String key) throws DataAccessException {
        final byte[] keyStr = serializer.serialize(key);
        return get(keyStr);
    }

    /**
     * 根據 key位元組陣列 獲取value
     *
     * @param keyStr
     * @param <T>
     * @return
     */
    public <T> T get(final byte[] keyStr) {
        T result = redisTemplate.execute(new RedisCallback<T>() {
            public T doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] value = connection.get(keyStr);
                return deseriaValueByte(value);
            }
        });
        return result;
    }

    /**
     * 反序列化value位元組陣列
     *
     * @param value
     * @param <T>
     * @return
     */
    private <T> T deseriaValueByte(byte[] value) {
        if (value == null) {
            return null;
        }
        String valueStr = serializer.deserialize(value);
        T retStr;
        try {
            retStr = SerializableUtils.deserialize(valueStr);
        } catch (Exception e) {
            logger.error("deseriaValueByte {} happen RuntimeException {}", value, e.getMessage());
            return null;
        }
        return retStr;
    }

    /**
     * 刪除指定key資料
     *
     * @param key
     * @return 返回操作影響記錄數
     */
    public Long delete(final String key) throws DataAccessException {
        logger.info("delete key {} from redis", key);
        if (StringUtils.isEmpty(key)) {
            return 0l;
        }
        Long delNum = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keys = serializer.serialize(key);
                return connection.del(keys);
            }
        });
        return delNum;
    }

    /**
     * 根據key模糊查詢value set集合
     *
     * @param key
     * @param <T>
     * @return
     * @throws DataAccessException
     */
    public <T> Set<T> keys(final String key) throws DataAccessException {
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        Set<T> res = redisTemplate.execute(new RedisCallback<Set<T>>() {
            public Set<T> doInRedis(RedisConnection connection)
                    throws DataAccessException {
                Set<T> tSet = new HashSet<>();
                byte[] keys = serializer.serialize(key);
                Set<byte[]> keysByteSet = connection.keys(keys);
                if (keysByteSet != null && keysByteSet.size() > 0)
                    for (byte[] key : keysByteSet) {
                        byte[] valueByte = connection.get(key);
                        T value = deseriaValueByte(valueByte);
                        tSet.add(value);
                    }
                return tSet;
            }
        });
        return res;
    }

    /**
     * 清空快取
     *
     * @return
     */
    public boolean flushDB() throws DataAccessException {
        logger.info("flushDB in redis...");
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            public Boolean doInRedis(RedisConnection connection)
                    throws DataAccessException {
                connection.flushDb();
                return true;
            }
        });
        return result;
    }

}


3.2  RedisSessionDao

package com.gaojccn.sharesession;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.Collection;
import java.util.Set;

/**
 * RedisSessionDao
 */
@Service("sessionDao")
public class RedisSessionDao extends AbstractSessionDAO {
    private static Logger logger = LoggerFactory.getLogger(RedisSessionDao.class);

    @Autowired
    private RedisManager redisManager;

    //設定過期時間
    @Value("${session.expireTime}")
    private long expireTime;

    // The Redis key prefix for the sessions
    @Value("${session.keyPrefix}")
    private String keyPrefix;

    @Override
    public Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, formatSessionId(sessionId));
        logger.info("session id is" + session.getId());
        this.saveSession(session);
        return session.getId();
    }

    private String formatSessionId(Serializable sid) {
        try {
            String sessionId = String.valueOf(sid).replace("-", "").toUpperCase();
            return sessionId;
        } catch (Exception e) {
            logger.error("formatSessionId happen exception {}", e.getMessage());
            return null;
        }
    }

    @Override
    public Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            logger.error("session id is null");
            return null;
        }
        Session s = redisManager.get(keyPrefix + sessionId);
        return s;
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        this.saveSession(session);
    }

    private void saveSession(Session session) {
        if (session == null || session.getId() == null) {
            logger.error("session or session id is null");
            return;
        }
        session.setTimeout(expireTime);
        redisManager.setEx(keyPrefix + session.getId(), SerializableUtils.serialize(session), expireTime);
    }

    @Override
    public void delete(Session session) {
        if (session == null || session.getId() == null) {
            logger.error("session or session id is null");
            return;
        }
        redisManager.delete(keyPrefix + session.getId());
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<Session> sessions = redisManager.keys(this.keyPrefix + "*");
        return sessions;
    }

}


3.3  RedisSessionListener

package com.gaojccn.sharesession;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * redisSession事件監聽器
 * author:gaojc
 */
@Service
public class RedisSessionListener implements SessionListener {
    private static final Logger logger = LoggerFactory.getLogger(RedisSessionListener.class);

    @Autowired
    private RedisSessionDao sessionDao;

    @Override
    public void onStart(Session session) {//會話建立時觸發
        logger.debug("會話建立:" + session.getId());
    }

    @Override
    public void onExpiration(Session session) {//會話過期時觸發
        logger.debug("會話過期:" + session.getId());
        sessionDao.delete(session);
    }

    @Override
    public void onStop(Session session) {//退出時觸發
        logger.info("會話停止:" + session.getId());
        sessionDao.delete(session);
    }
}


3.4 SerializableUtils

package com.gaojccn.sharesession;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.Session;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableUtils {
    public static String serialize(Session session) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(session);
            return Base64.encodeToString(bos.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException("serialize session error", e);
        }
    }

    public static <T> T deserialize(String sessionStr) {
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(Base64.decode(sessionStr));
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (T) ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException("deserialize session error", e);
        }
    }
}

4. web專案中的使用

 4.1 匯入sharesession.jar    

 4.2 web.xml裡面加入shiro過濾器

<filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>

而且用contextLoaderListener載入spring配置檔案

 <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

4.3 spring-context.xml裡面加入相關檔案

<!--共享session properties配置-->
    <context:property-placeholder location="classpath:/properties/share_session.properties"
                                  file-encoding="UTF-8" ignore-unresolvable="true"/>
    <!--共享session bean配置-->
    <import resource="classpath*:share_session.xml"/>

4.4 share_session.properties

##redis連線引數
redis.host=192.168.1.109
#這裡用的是簡單的單節點
redis.port=6379
redis.database=0
redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true
#當客戶端閒置多長時間後關閉連線,如果指定為0,表示關閉該功能
redis.timeout=10000

#session有效時間30分鐘 單位毫秒
session.expireTime=1800000
#sessionId字首
session.keyPrefix=redis_session:
#redis session alias (session cookieName)(session key名稱,用和web容器預設一樣的名稱jsessionid防止出錯)
session.cookieName=jsessionid
#定時清理失效會話間隔時間 20分鐘
session.sessionValidationInterval=1200000


#shiro url配置
loginUrl=https://cas-ad.share.gaojccn.com:8443/cas/login?service=http://netpay-web.gaojccn.com:8080/netpay/shiro-cas
casFilter.failureUrl=/error.jsp
logout.redirectUrl=https://cas-ad.share.gaojccn.com:8443/cas/logout

#shiroFilter.webDir=netpayweb

#casRealm
casRealm.roles=ROLE_USER
casRealm.casServerUrlPrefix=https://cas-ad.share.gaojccn.com:8443/cas
casRealm.casService=http://netpay-web.gaojccn.com:8080/netpay/shiro-cas

#filterChainDefinitions 用\n換行來分割多行value
filterChainDefinitions=/shiro-cas = cas\n/rest/version = anon\n/rest/** = authc\n/netpayweb/version.json = anon\n/netpayweb/report/** = anon\n/netpayweb/** = authc\n/logout = logout

ok,到此結束,測試使用吧。