问题描述: 在做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());”

到此,问题被彻底解决!