shiro SimpleSession的序列化

不知道有没有人会问这个序列化它,能有什么用。

假如你和我一样从一个有历史背景的项目中 “脱颖而杀出一条血路” 的话,你可能会觉得这个有用【笑哭了的表情】

项目用的 Shiro,权限 + session 强绑定的技术背景,应用服务通过依赖 shiro sdk 而多点访问 redis 读取session 的业务现状(redisSessionDao 技术兄弟们对这个可能不陌生),多点访问 redis 意味着session 协议升级,与应用服务迭代的相互影响,以及可能会存在小机率的事务性问题(说到这终于想起来为什么经常有人反馈些,顽固的、莫名其妙的、用户状态问题),

直入技术要害:默认的 session 叫 SimpleSession ,它有几个属性是 transient 修饰的,贴出来方便交流:

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;

受 transient 的影响 ,所以如果我们想把 SimpleSession 直接交给 feignclient 去请求传输是行不通的, feignclient 默认的序列化(json序列化)这关就过不了,我们还得借助 org.crazycake.shiro.serializer.ObjectSerializer 来实现序列化,以下是代码片断:

private RedisSerializer valueSerializer = new ObjectSerializer();
public SessionResult readSession(@RequestBody SessionReq req) {
    SessionResult res = new SessionResult<>();
    try {
        Session session = redisSessionDAO.readSession((Serializable)req.getSid());
        byte[] serialize = valueSerializer.serialize(session);
        String base64str = Base64.getEncoder().encodeToString(serialize);
        res.setData(base64str);
    } catch (UnknownSessionException e) {
        // do sth...
    } catch (SerializationException e) {
        // do sth...
    }


    log.debug("auth 返回:::::: {}", res);
    return res;
}

如果是用的 feignclient 的话,还需要对指定的 feignclient 做点 config 本文就不展开了。

能把 Session 的完整信息通过网络进行传输, 序列化的问题就解决掉了,当然这样做也有缺点比如效率问题等,最终我也不会让这种状态一直存在,会通过架构升级来解决这些问题。当前这个方案其实还有进一步优化空间,假如架构角度没有风险的话,可以直接从 redis 读出 byte[] 数组,再进行 base64 ,这点我们可以读下 RedisSessionDao 的源码看看它是怎么处理的,实际上 shiro 从 redis 中拿 session,也是用的 org.crazycake.shiro.serializer.ObjectSerializer 做的序列化和反序列化【哈哈笑的表情】:

protected Session doReadSession(Serializable sessionId) {
   if (sessionId == null) {
      logger.warn("session id is null");
      return null;
   }
   Session s = getSessionFromThreadLocal(sessionId);

   if (s != null) {
      return s;
   }

   logger.debug("read session from redis");
   try {
      s = (Session) valueSerializer.deserialize(redisManager.get(keySerializer.serialize(getRedisSessionKey(sessionId))));
      setSessionToThreadLocal(sessionId, s);
   } catch (SerializationException e) {
      logger.error("read session error. settionId=" + sessionId);
   }
   return s;
}

就标红的这一行,基本上已经说明我们的问题了。  valueSerializer 就是一个 org.crazycake.shiro.serializer.ObjectSerializer 。redisManager 得到的是 session 序列化后的 byte[] 数组。

稍微贴下它 ObjectSerializer  的源码一探究竟何方神圣:

@Override
public byte[] serialize(Object object) throws SerializationException {
    byte[] result = new byte[0];

    if (object == null) {
        return result;
    }
    ByteArrayOutputStream byteStream = new ByteArrayOutputStream(BYTE_ARRAY_OUTPUT_STREAM_SIZE);
    if (!(object instanceof Serializable)) {
        throw new SerializationException("requires a Serializable payload "
                + "but received an object of type [" + object.getClass().getName() + "]");
    }
    try {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream);
        objectOutputStream.writeObject(object);
        objectOutputStream.flush();
        result =  byteStream.toByteArray();
    } catch (IOException e) {
        throw new SerializationException("serialize error, object=" + object, e);
    }

    return result;
}

@Override
public Object deserialize(byte[] bytes) throws SerializationException {
    Object result = null;

    if (bytes == null || bytes.length == 0) {
        return result;
    }

    try {
        ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new ObjectInputStream(byteStream);
        result = objectInputStream.readObject();
    } catch (IOException e) {
        throw new SerializationException("deserialize error", e);
    } catch (ClassNotFoundException e) {
        throw new SerializationException("deserialize error", e);
    }

    return result;
}

大呼上当,也没什么神奇的也就是 ByteArrayOutputStream + ObjectOutputStream 而已。。

可能也有人会说为什么不壮士断碗,直接把权限弄到 gateway 里面去。本文就不讨论了,有条件能一步到位的还是鼓励一步到位。

上一篇:16-shiro整合springboot之授权的基本使用


下一篇:SpringBoot Shiro,解决Shiro中自定义Realm Autowired属性为空问题