• 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进行权限管理。测试过程中发现执行文件导入时最开始一切正常,但是导入几次之后再次执行导入就会报错,此时执行其他功能一切正常

    排查过程

    • [x] 1. 网上搜索,大部分都是说法如下:

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

    • [x] 2. 假如是上述原因,应该从登录开始就出问题,而不应该是极个别操作出现问题。而我这里只有多次导入之后出问题,并且此时其他功能还是正常的。因此排除这个原因,自己进行排查。
    • [x] 3. 因为只有导入出现这个问题,其他功能都一切正常;因此怀疑是导入的代码存在问题。
    • [x] 4. 排查导入代码发现,只有在执行保存语句的时候获取当前用户使用了Shiro相关代码,因此怀疑此处出现问题。
    this.operator = (String) SecurityUtils.getSubject().getPrincipal();
    
    • [x] 5. 在此处断点发现,手动新增数据和批量导入数据都执行该语句,但两次获取的Subject不一致。因此阅读源码进行排查。
    // 从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>>();
    
    • [x] 6. 根据上面对源码的跟踪,发现Subject是与ThreadLocal也就是线程绑定的。获取Subject时先获取当前线程绑定的Subject,若没有则重新创建并绑定到当前线程。而我导入的时候为了提高导入效率,使用了多线程。到此就发现问题的原因了

    问题原因

    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: 该解决方案不适用于所有情况,请根据实际情况按照上述排查步骤进行排查。

  • 相关阅读:
    uniapp爬坑之旅_开发一个自己的app_day27_完善数据库并给添加任务限制条件
    uniapp爬坑之旅_开发一个自己的app_day45_任务左滑不太好搞
    uniapp爬坑之旅_开发一个自己的app_day47_主页面基本完成
    uniapp爬坑之旅_开发一个自己的app_day28_完成任务条信息和数据库的同步
    uniapp爬坑之旅_开发一个自己的app_day31_增加主页面任务条滚动功能
    uniapp爬坑之旅_开发一个自己的app_day35
    uniapp爬坑之旅_开发一个自己的app_day46_实现任务左滑
    uniapp爬坑之旅_开发一个自己的app_day29_增加删除任务功能
    uniapp爬坑之旅_开发一个自己的app_day32_增加修改任务功能
    c++:class,名字空间等
  • 原文地址:https://www.cnblogs.com/jinjiyese153/p/11585157.html
Copyright © 2020-2023  润新知