当前位置: 移动技术网 > IT编程>开发语言>Java > Shiro使用Redis作存储之后更新Session失败的问题

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

2018年11月10日  | 移动技术网IT编程  | 我要评论

问题

因为想在多个应用之间共享用户的登录态,因此实现了自己的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实则不是一个好注意,应该考虑其它的机制。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网