Spring Boot 2 + SiteMesh + Shiro 入坑记

最近入手一个项目,用的是Spring Boot 1.5 + SiteMesh + Shiro,想赶时髦升级成Spring Boot 2,于是就掉坑里了。

正常情况从login网页登录后,页面转到index。但是升级完后却报了一个常见但又很难解决的问题:

org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton.  This is an invalid application configuration.
    at org.apache.shiro.SecurityUtils.getSecurityManager(SecurityUtils.java:123)
    at org.apache.shiro.subject.Subject$Builder.<init>(Subject.java:626)
    at org.apache.shiro.SecurityUtils.getSubject(SecurityUtils.java:56)

一、原因分析

首先可以肯定的是Shiro的filter已经注册到filter chain里了,因为登录时输入用户名和密码有参与校验了,但是这个异常说明shiro filter没有起作用,这是为什么呢?

俗话说, 没有对比就没有伤害。但是这种棘手的问题却最好能通过对比来解决。

既然1.5版本的可以正常使用,那么就打开两个工程,把断点都打在SecurityUtils.getSubject(),看看都有什么内容。

1. 先看Spring Boot 1.5的,因为堆栈太长,只截了一部分图,但仍然很长。从图中可以看到ShiroFilter在起作用。

2. 再看看Spring Boot 2的,这个就短很多,没有看到Shiro Filter。

两张图对比下来,发现中间有一次forward。在1.5里,forward之后所有的filter又都重新执行了一遍,比如有两个SiteMeshFilter。而2里forward之后就只有一个WsFilter在执行。这中间有什么猫腻?

虽然在forward之后,Shiro filter消失了,但是在forward之前,filter chain里是有Shiro filter的,但是顺序排在SiteMeshFilter之后。

重点来了,咳咳。

SiteMeshFilter在处理时,调用了context.decorate(decoratorPath, content),这导致了ApplicationDispatcher.forward操作。

    @Override
    protected boolean postProcess(String contentType, CharBuffer buffer,
                                  HttpServletRequest request, HttpServletResponse response,
                                  ResponseMetaData metaData)
            throws IOException, ServletException {
        WebAppContext context = createContext(contentType, request, response, metaData);
        Content content = contentProcessor.build(buffer, context);
        if (content == null) {
            return false;
        }

        String[] decoratorPaths = decoratorSelector.selectDecoratorPaths(content, context);
        for (String decoratorPath : decoratorPaths) {
            content = context.decorate(decoratorPath, content);
        }

        if (content == null) {
            return false;
        }
        try {
            content.getData().writeValueTo(response.getWriter());
        } catch (IllegalStateException ise) {  // If getOutputStream() has already been called
            content.getData().writeValueTo(new PrintStream(response.getOutputStream()));
        }
        return true;
    }
 

ApplicationDispatcher.forward操作里,又重新构建filter chain:

在这里面有一个matchDispatcher的函数,正是这个函数,导致Spring Boot 1.5和2的filter chain是不同的。1.5里所有的filter又都重新加载了,2里只有一个WsFilter被重新加载。而Forward之前的filter通通不见了。最惨的是Shiro Filter,刚好排在SiteMeshFilter之后,于是在Forward之前和之后都没有执行。

为什么forward之后filter都消失了呢?看看matchDispatcher的函数内部:

原来在Spring Boot 2里,大部分filter都不支持forward。

究其原因,是因为在filter registration的时候,filter的dispatcher type被赋予不同的值,代码位置在:

org.springframework.boot.web.servlet.AbstractFilterRegistrationBean.configure()

1. 这是Spring Boot 1.5的:

2. 这是Spring Boot 2的:

可以看到1.5里Forward,Include和Request dispatcher type都支持,而2里只支持Request dispatcher type。

二、解决方案

解决思路是让Shiro Filter能执行,把SecurityManager绑在ThreadContext里。

解决办法有两个,一是调整Filter的顺序,把Shiro Filter调到SiteMeshFilter的前面。二是让Shiro Filter也支持Forward。

方案一里,Shiro Filter是通过ShiroFilterFactoryBean来配置的,看不到调整Filter顺序的地方。反正我是没找到,诸位看官如果有办法的话,欢迎留言。

这里我用方案二,就是在ShiroConfig类里添加一个FilterRegistrationBean。

   @Bean
   public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy(){
       FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<>();
       DelegatingFilterProxy proxy = new DelegatingFilterProxy();
       proxy.setTargetFilterLifecycle(true);
       proxy.setTargetBeanName("shiroFilter");
       filterRegistrationBean.setFilter(proxy);
       filterRegistrationBean.setEnabled(true);
       filterRegistrationBean.addUrlPatterns("/*");
       //filterRegistrationBean.setAsyncSupported(true);
       
       EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST,
               DispatcherType.FORWARD);
       filterRegistrationBean.setDispatcherTypes(types);
       
       return filterRegistrationBean;
   }
 

在Forward之后,Shiro Filter也可以被重新加载,于是问题得到解决。

猜你喜欢

转载自blog.csdn.net/elashu/article/details/85089891