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,到此结束,测试使用吧。