springboot test shiro UnavailableSecurityManagerException

3 篇文章 0 订阅
2 篇文章 0 订阅

springboot test shiro UnavailableSecurityManagerException

  • 在做单元测试时遇到这个问题,场景是需要获得当前登录用户的id,正好可以使用shiro框架的SecurityUtils来获取,但是,测试一运行就报这个错误,然后就来分析一下

    public abstract class SecurityUtils {
        ...
        public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
                SecurityManager securityManager = ThreadContext.getSecurityManager();
                if (securityManager == null) {
                    securityManager = SecurityUtils.securityManager;
                }
                if (securityManager == null) {
                    String msg = "No SecurityManager accessible to the calling code, either bound to the " +
                            ThreadContext.class.getName() + " or as a vm static singleton.  This is an invalid application " +
                            "configuration.";
                    throw new UnavailableSecurityManagerException(msg);
                }
                return securityManager;
            }
        ...
    }
    
  • 上面就是那段报错的代码,可以看到当ThreadContext.getSecurityManager()没有获取到securityManager时,并且SecurityUtils.securityManager为空时(SecurityUtils.securityManager这个值没找到有哪个地方会对它进行赋值,所以一般情况,它是空的),就会报错。

  • 主要原因还是在ThreadContext上,这个ThreadContext是个什么东西呢?

    /**
     * A ThreadContext provides a means of binding and unbinding objects to the
     * current thread based on key/value pairs.
     * <p/>
     * <p>An internal {@link java.util.HashMap} is used to maintain the key/value pairs
     * for each thread.</p>
     * <p/>
     * <p>If the desired behavior is to ensure that bound data is not shared across
     * threads in a pooled or reusable threaded environment, the application (or more likely a framework) must
     * bind and remove any necessary values at the beginning and end of stack
     * execution, respectively (i.e. individually explicitly or all via the <tt>clear</tt> method).</p>
     *
     * @see #remove()
     * @since 0.1
     */
    public abstract class ThreadContext {
        private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
    }
    
    • 可以知道它是为每个线程维护键值对数据的工具,翻译过来叫线程上下文,名字也贴合它的功能,实现是利用了ThreadLocal,这个类之前也分析过,在此不作赘述。
  • 了解了ThreadContext,就来看看什么时候put SecurityManager实例的。

    • 一层层往上找,发现

      • org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal

        • org.apache.shiro.subject.support.DelegatingSubject#execute
          • org.apache.shiro.subject.support.SubjectCallable#call
            • org.apache.shiro.subject.support.SubjectThreadState#bind
              • org.apache.shiro.util.ThreadContext#bind
      • 发现这个bind动作是shiro的过滤器触发的;而且每个请求触发一次,这个可以从org.apache.shiro.web.servlet.OncePerRequestFilter看出。

      • 所以,到这里可以得出一个结论,每个请求bind数据到ThreadContext,这样,在这个线程执行的任何地方,都可以获取到bind的数据,但是,为什么测试不行呢?猜想一下,那就是这个线程结束了,自然就获取不到bind的数据了。

      • 接着来调试下单元测试,看了看调用栈发现不是之前那个bind了数据的线程,从调用栈就可以看出来,根本就没有bind的踪迹,说明猜想对了,与此同时,去shiro的官网找了下,发现它提供了shiro和单元测试使用的demo,看来它这个问题一直都有的。

  • 解决办法有很多,只要设置一个SecurityManager实例就可以了,比如注入一个实例到SecurityUtils.securityManager,测试就可以运行了。

  • 总结

    • 这次问题查找,熟悉了调用栈的使用,然后对一个请求的过滤器处理过程,以及责任链模式有了新的印象
    • 还需要再深入的话,就需要看看junit的实现原理了,还有spring集成测试是怎么实现的
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值