欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Shiro使用Redis作存储之后更新Session失败的问题

程序员文章站 2022-05-14 08:21:38
问题 因为想在多个应用之间共享用户的登录态,因此实现了自己的 ,使用Kryo把 序列化然后放到redis之中去,同时也使用了 来使用shiro自己的存储。然而之后一直出现丢失更新的问题,例如 分析 DEBUG之后发现,从Subject中取到的Session并不是我们在SessionDAO中创建的Si ......

问题

因为想在多个应用之间共享用户的登录态,因此实现了自己的sessiondao,使用kryo把simplesession序列化然后放到redis之中去,同时也使用了shiro.usernativesessionmanager: true来使用shiro自己的存储。然而之后一直出现丢失更新的问题,例如

session session = securityutils.getsubject().getsession();
user user = (user) session.getattribute(membershipconst.sessionkey.user);
user.setname("newname");  // 名称没有更新

分析

debug之后发现,从subject中取到的session并不是我们在sessiondao中创建的simplesession,而是delegatingsubject$stoppingawareproxiedsession,这是一个代理类,本身并不做任何事情,而是通过delegatingsession调用真正的方法。而delegatingsession实则也并没有真正的调用simplesession,而是调用的sessionmanager中的方法:

/**
* @see session#setattribute(object key, object value)
*/
public void setattribute(object attributekey, object value) throws invalidsessionexception {
    if (value == null) {
        removeattribute(attributekey);
    } else {
        sessionmanager.setattribute(this.key, attributekey, value);
    }
}

而默认的defaultsessionmanager在进行任何写操作之前总是会先通过sessiondao读一次,如setattribute方法

public void setattribute(sessionkey sessionkey, object attributekey, object value) throws invalidsessionexception {
    if (value == null) {
        removeattribute(sessionkey, attributekey);
    } else {
        session s = lookuprequiredsession(sessionkey);
        s.setattribute(attributekey, value);
        onchange(s);
    }
}

这就是了,实际上我们并未显式的将session写回redis,而是更新lastaccesstime的时候一并写回去的,而更新访问时间的时候调用了touch()方法,sessionmanager又通过sessiondao读取了一次,重新读取了redis然后反序列化出一个新的session,原来session的各种改动自然也就丢失了。

解决

首先是在sessiondao上加上缓存,一来避免频繁的redis读取,二来避免出现每次读取返回一个新session的问题。然后在我们的场景中并不需要最后访问时间,因此重写了shirofilterfactorybean,不在更新最后访问时间,当session需要更新的时候,直接调用sessiondao写回redis,避免sessionmanager做二传手。

当然这不是完美的解决方案,并发场景下依然会有更新问题。调式中可以看出shiro通过sessiondao进行的读写操作非常频繁,显然在设计时并未将它当作一个涉及外部io的类。因此将session放在redis实则不是一个好注意,应该考虑其它的机制。