一、redis 环境准备
- 打开redis配置文件redis.conf
- 修改redis.conf文件
notify-keyspace-events Egx
- 重启redis服务
二、POM文件依赖引入
<!-- session共享, 采用spring-session-data-redis依赖 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
二、web.xml 配置
在所有filter的最前面,增加如下filter(不要改这个filter名称)
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
三、spring application context xml配置
增加如下配置
<!-- 让Spring Session不再执行config命令 -->
<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
<bean id="redisSessionSerializer"
class="com.some.company.CustomGenericFastJsonRedisSerializer" />
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<bean id="sessionRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:valueSerializer-ref="redisSessionSerializer"
p:hashValueSerializer-ref="redisSessionSerializer"
p:connectionFactory-ref="redisConnectionFactory" />
<context:annotation-config />
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"
p:defaultRedisSerializer-ref="redisSessionSerializer" p:configureRedisAction="NO_OP" />
四、session 序列化器定制化
上面第三步有个定制化的序列化器, 用于处理session(本质是个map)中存储的,不容易做序列化的属性对象。
例如没有实现Serilizable接口的类, 或者说,使用通用的序列化方法,不太容易做序列化(反序列化)的对象。
需要自己定制序列化策略:
- 调用定制化策略, 得到包含某个session对象S的字符串A。
- 将字符串A按照标准的字符串序列化方式序列化到redis中。
- 反序列化时, 用标准的字符串反序列化方式从redis中反序列化出来字符串A。
- 从A中提取信息,用定制的反序列化策略,构造对应的session对象类S。
序列化元数据类
@Data
public class CustomGenericFastJsonRedisBean {
// bean 序列化原始类全路径类名
private String className;
// 原始类定制序列化策略生成的字符串
private String classContent;
// 如果是列表对象,列表中元素的全路径类名
private String arrayItemClass;
}
定制序列化
package com.some.company.util;
...
public class CustomGenericFastJsonRedisSerializer extends GenericJackson2JsonRedisSerializer {
private static final Logger log = LoggerFactory.getLogger(CustomGenericFastJsonRedisSerializer.class);
@Autowired
private UserService userService;
@Override
public byte[] serialize(Object source) throws SerializationException {
try {
if (source == null) {
log.info("source null");
return new byte[0];
} else {
String classname = source.getClass().getName();
CustomGenericFastJsonRedisBean customGenericFastJsonRedisBean = new CustomGenericFastJsonRedisBean();
Gson gson = new Gson();
String objectStrOrigin;
if (StringUtils.equals("com.some.company.User", classname)) {
objectStrOrigin = Jackson2Helper.toJsonString(source);
} else if (StringUtils.equals("org.springframework.security.core.context.SecurityContextImpl",
classname)) {
SecurityContextImpl securityContext = (SecurityContextImpl) source;
CasAuthenticationToken oneToGo = (CasAuthenticationToken) securityContext.getAuthentication();
objectStrOrigin = buildCasAuthenticationTokenStr(oneToGo);
log.info("redis class : >{}< bean content : >{}<", classname, objectStrOrigin);
} else if (StringUtils.equals("java.util.ArrayList", classname)) {
ArrayList<?> oneList = (ArrayList<?>) source;
if (CollectionUtils.isNotEmpty(oneList)) {
String itemClassname = oneList.get(0).getClass().getName();
customGenericFastJsonRedisBean.setArrayItemClass(itemClassname);
objectStrOrigin = JSON.toJSONString(source);
log.info("redis class : >{}<", itemClassname);
} else {
objectStrOrigin = JSON.toJSONString(source);
log.info("redis class : Object");
}
} else {
objectStrOrigin = gson.toJson(source);
log.info("redis class : >{}< bean content : >{}<", classname, objectStrOrigin);
}
// String objectStrOrigin = Jackson2Helper.toJsonString(source);
customGenericFastJsonRedisBean.setClassName(classname);
customGenericFastJsonRedisBean.setClassContent(objectStrOrigin);
String objectStr = gson.toJson(customGenericFastJsonRedisBean);
return super.serialize(objectStr);
}
} catch (Exception e) {
log.warn("serialize session Object error!", e);
}
return new byte[0];
}
@HibernateSessionAround
@Override
public Object deserialize(@Nullable byte[] bytes) {
Object deserialObj = null;
try {
if (bytes == null || bytes.length <= 0) {
log.warn("bytes empty");
} else {
String outStr = super.deserialize(bytes, String.class);
if (StringUtils.isBlank(outStr)) {
log.warn("empty outStr");
} else {
Gson gson = new Gson();
CustomGenericFastJsonRedisBean customGenericFastJsonRedisBean =
gson.fromJson(outStr, CustomGenericFastJsonRedisBean.class);
if (customGenericFastJsonRedisBean == null) {
log.warn("customGenericFastJsonRedisBean is null");
} else {
String redisOutClassName = customGenericFastJsonRedisBean.getClassName();
String redisOutContent = customGenericFastJsonRedisBean.getClassContent();
String arrayItemClass = customGenericFastJsonRedisBean.getArrayItemClass();
if (StringUtils.isBlank(redisOutClassName)) {
log.warn("customGenericFastJsonRedisBean class name null");
} else {
Class redisOutClass = Class.forName(redisOutClassName);
if (StringUtils.equals("com.some.company.User", redisOutClassName)) {
com.some.company.User userOrigin =
Jackson2Helper.parsonObject(redisOutContent,
new TypeReference<com.some.company.User.User>() { } );
if (userOrigin != null) {
Long userId = userOrigin.getId();
com.some.company.User user = userService.findByUserId(userId);
log.info("user dept name {}", user.getDeptName());
deserialObj = user;
} else {
deserialObj = null;
}
log.info("redisOutClassName: >{}<, redisOutContent: >{}<",
redisOutClassName, redisOutContent);
} else if (StringUtils.equals(
"org.springframework.security.core.context.SecurityContextImpl",
redisOutClassName)) {
JSONObject jsonObject = JSON.parseObject(redisOutContent);
CasAuthenticationToken casAuthenticationToken =
buildCasAuthenticationToken(jsonObject);
SecurityContextImpl securityContext = new SecurityContextImpl();
securityContext.setAuthentication(casAuthenticationToken);
deserialObj = securityContext;
} else if (StringUtils.equals("java.util.ArrayList", redisOutClassName)) {
if (StringUtils.isNotBlank(arrayItemClass)) {
Class redisOutClassArray = Class.forName(arrayItemClass);
deserialObj = JSON.parseArray(redisOutContent, redisOutClassArray);
log.info("arrayItemClass : >{}<", arrayItemClass);
} else {
deserialObj = JSON.parseArray(redisOutContent, Object.class);
log.info("arrayItemClass : >Object<");
}
} else {
deserialObj = gson.fromJson(redisOutContent, redisOutClass);
log.info("redisOutClassName: >{}<, redisOutContent: >{}<",
redisOutClassName, redisOutContent);
}
}
}
}
}
} catch (Exception e) {
log.warn("deserialize session Object error!", e);
}
return deserialObj;
}
private String buildCasAuthenticationTokenStr(CasAuthenticationToken oneToGo) {
JSONObject jsonObject = new JSONObject();
...
return JSON.toJSONString(jsonObject);
}
private CasAuthenticationToken buildCasAuthenticationToken(JSONObject jsonObject) {
...
CasAuthenticationToken casAuthenticationToken =
new CasAuthenticationToken("an_id_for_this_auth_provider_only",
principal,
credentials,
grantedAuthorityList,
userDetails,
assertionImpl);
return casAuthenticationToken;
}
}
五、总结
理论上,用spring data redis 做 session共享,可以根据自己工程的鉴权场景,做任意的序列化定制,来支持服务多实例间session共享。