首页 > 编程语言 > 详细

Spring Boot分布式系统实践【扩展1】shiro+redis实现session共享、simplesession反序列化失败的问题定位及反思改进

时间:2019-03-18 16:06:07      阅读:263      评论:0      收藏:0      [点我收藏+]

前言

调试之前请先关闭Favicon配置

spring:
????favicon:
??????enabled: false

不然会发现有2个请求(如果用nginx+ 浏览器调试的话)
技术分享图片

序列化工具类【fastjson版本1.2.37】

```public class FastJson2JsonRedisSerializer implements RedisSerializer {

????public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

????private Class clazz;

????public FastJson2JsonRedisSerializer(Class clazz) {
????????super();
????????this.clazz = clazz;
????}

????@Override
????public byte[] serialize(T t) throws SerializationException {
????????if (t == null) {
????????????return new byte[0];
????????}
????????return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
????}

????@Override
????public T deserialize(byte[] bytes) throws SerializationException {
????????if (bytes == null || bytes.length <= 0) {
????????????return null;
????????}
????????String str = new String(bytes, DEFAULT_CHARSET);

????????return (T) JSON.parseObject(str, clazz);

????}
}


 `org.apache.shiro.session.mgt.SimpleSession存储到redis中会发现已经丢失了所有属性`

![Image [1].png](https://upload-images.jianshu.io/upload_images/231328-ab9c9ca3c2b43710.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 #### 查看SimpleSession源码:

public class SimpleSession implements ValidatingSession, Serializable {

????private transient Serializable id;
????private transient Date startTimestamp;
????private transient Date stopTimestamp;
????private transient Date lastAccessTime;
????private transient long timeout;
????private transient boolean expired;
????private transient String host;
????private transient Map<Object, Object> attributes;
/* Serializes this object to the specified output stream for JDK Serialization.

  • @param out output stream used for Object serialization.
  • @throws IOException if any of this object‘s fields cannot be written to the stream.
  • @since 1.0
    */
    private void writeObject(ObjectOutputStream out) throws IOException {
    ????out.defaultWriteObject();
    ????short alteredFieldsBitMask = getAlteredFieldsBitMask();
    ????out.writeShort(alteredFieldsBitMask);
    ????if (id != null) {
    ????????out.writeObject(id);
    ????}
    ????if (startTimestamp != null) {
    ????????out.writeObject(startTimestamp);
    ????}
    ????if (stopTimestamp != null) {
    ????????out.writeObject(stopTimestamp);
    ????}
    ????if (lastAccessTime != null) {
    ????????out.writeObject(lastAccessTime);
    ????}
    ????if (timeout != 0l) {
    ????????out.writeLong(timeout);
    ????}
    ????if (expired) {
    ????????out.writeBoolean(expired);
    ????}
    ????if (host != null) {
    ????????out.writeUTF(host);
    ????}
    ????if (!CollectionUtils.isEmpty(attributes)) {
    ????????out.writeObject(attributes);
    ????}
    }

/*

  • Reconstitutes this object based on the specified InputStream for JDK Serialization.
  • @param in the input stream to use for reading data to populate this object.
  • @throws IOException????????????if the input stream cannot be used.
  • @throws ClassNotFoundException if a required class needed for instantiation is not available in the present JVM
  • @since 1.0
    */
    @SuppressWarnings({"unchecked"})
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {

***发现transient修饰,所以Fastjson不会对这些transient属性进行持久化,所以有了方案二,重写可以json序列化的对象
同时发现有writeObject()方法写着“ Serializes this object to the specified output stream for JDK Serialization.”,
所以有了方案一,修改序列化工具( 默认使用JdkSerializationRedisSerializer,这个序列化模式会将value序列化成字节码)
问题我们就好对症下药了***
## 方案一:

修改序列化工具类 (`这个方式其实有问题`)

public class FastJson2JsonRedisSerializer implements RedisSerializer {
????private Class clazz;
????public FastJson2JsonRedisSerializer(Class clazz) {
????????super();
????????this.clazz = clazz;
????}
????@Override
????public byte[] serialize(T t) {
????????return ObjectUtils.serialize(t);
????}
????@Override
????public T deserialize(byte[] bytes) {
????????return (T) ObjectUtils.unserialize(bytes);
????}
}

### ObjectUtils的方法如下:

/**

  • 序列化对象
  • @param object
  • @return
    */
    public static byte[] serialize(Object object) {
    ???ObjectOutputStream oos = null;
    ???ByteArrayOutputStream baos = null;
    ???try {
    ??????if (object != null){
    ?????????baos = new ByteArrayOutputStream();
    ?????????oos = new ObjectOutputStream(baos);
    ?????????oos.writeObject(object);
    ?????????return baos.toByteArray();
    ??????}
    ???} catch (Exception e) {
    ??????e.printStackTrace();
    ???}
    ???return null;
    }

/**

  • 反序列化对象
  • @param bytes
  • @return
    */
    public static Object unserialize(byte[] bytes) {
    ???ByteArrayInputStream bais = null;
    ???try {
    ??????if (bytes != null && bytes.length > 0){
    ?????????bais = new ByteArrayInputStream(bytes);
    ?????????ObjectInputStream ois = new ObjectInputStream(bais);
    ?????????return ois.readObject();
    ??????}
    ???} catch (Exception e) {
    ??????e.printStackTrace();
    ???}
    ???return null;
    }

***`此方案会严重依赖对象class,如果反序列化时class对象不存在则会报错
修改为: JdkSerializationRedisSerializer
`***
 
![Image [2].png](https://upload-images.jianshu.io/upload_images/231328-900964ebbd4757e2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/900)

### 方案二:

***继承SimpleSession并重写
让相关的字段可以被序列化(不被transient修饰)
重写之后一定要重写SessionManager里的方法***

@Override
protected Session newSessionInstance(SessionContext context) {
SimpleSession session = new MyRedisSession(context.getHost());
// session.setId(IdGen.uuid());
session.setTimeout(SessionUtils.SESSION_TIME);
return session;
}

#### 由方案二引发的另一个问题就是:
**`在微服务开发过程中,为了使用方便经常会将频繁访问的信息如用户、权限等放置到SESSION中,便于服务访问,而且,微服务间为了共享SESSION,通常会使用Redis共享存储。但是这样就会有一个问题,在封装Request对象时会将当前SESSION中所有属性对象反序列化,反序列化都成功以后,将SESSION对象生成。如果有一个微服务将本地的自定义Bean对象放置到SESSION中,则其他微服务都将出现反序列化失败,请求异常,服务将不能够使用了,这是一个灾难性问题。`**
##### 以下是为了解决下面问题提出来的一种思路。
反序列化失败在于Attribute中添加了复杂对象,由此推出以下解决方案:

1. 将复杂对象的(即非基本类型的)Key进行toString转换(转换之后再MD5缩减字符串,或者用类名代替)
2. 将复杂对象的(即非基本类型的)Value进行JSON化(不使用不转换的懒加载模式)

`注意:
日期对象的处理(单独处理)`

??/**
?????* 通过类型转换,将String反序列化成对象
?????* @param key
?????* @param value
?????* @return
?????*/
????public Object getObjectValue(String key,String value){
????????if(key == null || value == null){
???????????return null;
????????}
????????String clz = key.replace(FLAG_STR,"");
????????try {
???????????Class aClass = Class.forName(clz);
???????????if(aClass.equals(Date.class)){
???????????????return DateUtils.parseDate(value);
???????????}
??????????return???JSONObject.parseObject(value,aClass);
????????} catch (ClassNotFoundException e) {
????????????e.printStackTrace();
????????}
//????????如果反序列化失败就进行json化处理
????????return JSONObject.parseObject(value);
????}

```
经过如此处理可以在所有系统里共享缓存
唯一缺点就是太复杂了,可能引起其他系统的修改导致反序列化失败(这个下次再讨论或者实验,因为有这么复杂的功夫,就可以考虑用JWT)

还有一种方案是将复杂对象放到redis中去,实行懒加载机制(不用的复杂对象,不从redis里获取,暂未实现测试)

Spring Boot分布式系统实践【扩展1】shiro+redis实现session共享、simplesession反序列化失败的问题定位及反思改进

原文:https://www.cnblogs.com/Halburt/p/10552582.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!