org.apache.shiro.session.UnknownSessionException: There is no session with id [xxxx]的解决方案

org.apache.shiro.session.UnknownSessionException: There is no session with id [xxxx]的解决方案

背景描述

SpringBoot项目,使用Shiro进行权限管理。测试过程中发现执行文件导入时最开始一切正常,但是导入几次之后再次执行导入就会报错,此时执行其他功能一切正常

排查过程

Shiro的Cookie名称默认是JSESSIONID,与servlet容器冲突。修改Shiro的SessionID即可

this.operator = (String) SecurityUtils.getSubject().getPrincipal();
// 从SecurityUtils中获取Subject源码如下
// package: org.apache.shiro.SecurityUtils
public static Subject getSubject() {
    Subject subject = ThreadContext.getSubject(); // ①
    if (subject == null) {
        subject = (new Subject.Builder()).buildSubject();
        ThreadContext.bind(subject);
    }
    return subject;
}

// 继续跟进上面①中的方法
// package: org.apache.shiro.util.ThreadContext
public static Subject getSubject() {
    return (Subject) get(SUBJECT_KEY); // ②
}

// 继续跟进上面②中的方法
// package: org.apache.shiro.util.ThreadContext
public static Object get(Object key) {
    if (log.isTraceEnabled()) {
        String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
        log.trace(msg);
    }

    Object value = getValue(key); // ③
    if ((value != null) && log.isTraceEnabled()) {
        String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
                key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
        log.trace(msg);
    }
    return value;
}

// 继续跟进上面③中的方法
// package: org.apache.shiro.util.ThreadContext
private static Object getValue(Object key) {
    Map<Object, Object> perThreadResources = resources.get(); // ④
    return perThreadResources != null ? perThreadResources.get(key) : null;
}

// 上面④中的resources在ThreadContext中定义如下
// package: org.apache.shiro.util.ThreadContext
private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();

问题原因

  1. 假设项目中线程池设置核心线程数量为10,而核心线程默认是不会被超时回收的

ps: 可通过threadPoolExecutor.allowCoreThreadTimeOut(true);设置核心线程超时回收

  1. 当用户A登录后,执行导入操作,从线程池中拿出5个线程,此时这5个线程将绑定用户A的Subject
  2. 当用户A多次执行导入操作后,线程池全部核心线程与用户A的Subject绑定。用户A退出登录后,线程池并不会将核心线程进行销毁。
  3. 后续用户B登录,再次执行导入操作,此时线程池分配线程进行操作,但此时所有的线程都已与用户A绑定,因此获取到的Subject都是用户A的Subject,从Subject中获取session时此session已被销毁,因此报错
// 根据sessionId获取session,获取为空则报错
// package: org.apache.shiro.session.mgt.eis.AbstractSessionDAO
public Session readSession(Serializable sessionId) throws UnknownSessionException {
    Session s = doReadSession(sessionId);
    if (s == null) {
        throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
    }
    return s;
} 

解决方案

多线程时不要使用Shiro相关代码。将用户名作为参数传入,不再单独获取。

PS: 该解决方案不适用于所有情况,请根据实际情况按照上述排查步骤进行排查。

posted @ 2019-09-25 15:49  禁忌夜色153  阅读(9827)  评论(0编辑  收藏  举报