问题描述:
##### 在做netty和shiro整合测试时,程序启动并正常运行一段时间之后会发现shiro出现异常,异常信息为There is no session with id [xxxxxx]!重启之后可以恢复但是运行一会儿又会出现该情况,由于我这里没有使用shiro的web认证机制,网上一些解决类似此情况的方法无效。以下为我自己排查分析并解决的过程
第一步GC优化
dubugg模式下发现报错的地方为如下所示:
##### 从dubug模式发现,context此时的实例是DefaultSubjectContext实例,而createSubject方法中设置的三个属性值被封装到DefaultSubjectContext类的backingMap属性中,此时都还正常
private final Map<String, Object> backingMap;
public void setAuthenticated(boolean authc) {
put(AUTHENTICATED, authc);
}
//put方法内容
public Object put(String s, Object o) {
return backingMap.put(s, o);
}
##### 继续向下执行突然发现SubjectContext的值被清空了!也就是HashMap没了!由此最先怀疑的是shiro是否将hashMap公用了,然后被其他某个线程给干掉了,查看creatSubjectContext源码发现每次都会重新实例化一个SubjectContext类,因此我怀疑是给GC干掉了
protected SubjectContext createSubjectContext() {
return new DefaultSubjectContext();
}
##### 使用java自带的jvm分析程序,检查GC执行情况,截图如下,由于我没有进行jvm调优,导致GC执行频率非常高,每条能有好几次回收,因此可以确定是GC导致HashMap中的数据被回收,从而导致shiro认证失败,则优化GC即可
优化之后,HashMap中的值不再被GC回收,但是问题依然存在
第二步,根结所在
分析自己得代码加上网上查到得资料,原因应该是并发访问shiro导致shiro得session冲突导致,查看SecurityUtils.getSubject();源码发现shiro是从ThreadLocal中先获取Subject,如果Subject不存在则重新创建,这里有个问题,ThreadLocal在netty并发登录后会导致多个线程获取到同一个Subject实例,从而得到得sessionkey会与实际得sessionkey不一致从而抛出“There is no session with id”异常
//代码中获取Subject得方法
Subject sub = SecurityUtils.getSubject();
AuthenticationToken token = createJwtToken( ctx, msg);
sub.login(token);
// SecurityUtils.getSubject();对应源码
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
解决思路:
1. 在执行getSubject()方法之前,先执行ThreadContext.remove();方法,将当前shiro线程得TreadLocal缓存中缓存得Subjec实例删除,则每次执行login之前都重新创建Subject实例,则问题解决
2. 参考互联网大佬得解决方案:
将Subject sub = SecurityUtils.getSubject();获取Subject得方式改成“Subject sub = SecurityUtils.getSecurityManager().createSubject(new DefaultSubjectContext());”
到此,问题被彻底解决!