SpringBoot 与Shirofilter

 

Shiro 一段时间没用,好多东西忘了,今天自定义Filter 注册到Shiro中的时候发现了几个问题:

1.这个Filter会执行2次 

2.受Shiro管理的filter 无法使用Spring 的bean

所以干脆直接debug源码一探究竟:

先声明一点,我们在配置中通常都是通过ShiroFilterFactoryBean来配置的

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor


 并且我们能够发现他还实现了BeanPostProcessor接口,是个后置处理器,也就意味着他的加载是早与Bean的,同时也说明了Shiro的加载优先于Bean

 

  public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
    }
通过createInstance()生成具体的Shiro
 protected AbstractShiroFilter createInstance() throws Exception {

        log.debug("Creating Shiro Filter instance.");

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        //Expose the constructed FilterChainManager by first wrapping it in a
        // FilterChainResolver implementation. The AbstractShiroFilter implementations
        // do not know about FilterChainManagers - only resolvers:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
        //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
        //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
        //injection of the SecurityManager and FilterChainResolver:
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

1.先绑定SecurityManager,如果是web环境下则匹配是否是instanceof WebSecurityManager

 2.创建FilterChain ,Shiro的过滤器链,如下所示

protected FilterChainManager createFilterChainManager() {

        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map<String, Filter> defaultFilters = manager.getFilters();
        同时我们从代码中也能看出,Shiro率先配置Shiro自带的默认的Filter
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }
    
       然后配置我们在配置文件中自定义的Filter
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                添加,内部会进行是否有判断,然后设置
                manager.addFilter(name, filter, false);
            }
        }

        为这些filter配置动态URL,与我们具体原先的具体配置有关
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                manager.createChain(url, chainDefinition);
            }
        }

        return manager;
    }

制此shiro的过滤器链是创建成功了

下一步添加到FileterChainResolver中去,你配置了过滤器链总要有人调用处理吧

createInstance()至此结束,接下来的就是Spring 容器加载Bean的过程了,想了解的可以看下我的这篇文章,有点简陋,但是应该也看得懂

ok,我们需要重现bug了:浏览器访问filter拦截的url:

这是filter的执行流程前置:有点拗口,我编的因为

@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
        
		ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
        初始化绑定上下文,不多说了
		initContextHolders(request, attributes);

		try {
        这是核心方法
			filterChain.doFilter(request, response);
		}
		finally {
			resetContextHolders();
			if (logger.isDebugEnabled()) {
				logger.debug("Cleared thread-bound request context: " + request);
			}
			attributes.requestCompleted();
		}
	}

进入的第一个方法是ApplicationFilterChain的

@Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        字面翻译就行了,是否安全,返回的是系统的SecurityManager不是Shiro的,至于为何是null我
    未深入debug,执行的是else中的方法
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                 
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            执行的是这个方法
            internalDoFilter(request,response);
        }
    }

internalDoFilter方法,这才是内部的onion: 注意此处的filters ,我们可以查看属性

可以看出有AuthFilter 和shiroFilter,注意shiroFIlter我们在上面得知内部也配置了一个authFIlter(这是我自定义的一个filter),所以最终会执行两次,这跟SpringBoot的机制有关,SpringBoot会自动将实现了Filter接口的Bean收集到WebFilter中,这就导致了自定义Filter和ShiroFilter同级了,也就是说相当于注册了相同的bean2次 ,ok 那第一个问题解决了,去除@Bean 的注册即可,采用new的方式

 private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

      
      pos代表当前坐标,有一个filter的数组,这个意味着是否传递下去给下一个filter调用
        if (pos < n) {
          获取filterConfig的配置
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                获取filter
                Filter filter = filterConfig.getFilter();
                
                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    进入对应的filter处理中
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }

        
        //filter 结束了,ok servlet它lei了
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            if (request.isAsyncSupported() && !servletSupportsAsync) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }

然后呢它会到AbstractShiroFilter chain中

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {
        
         提前声明异常,之所以前置声明是因为要根据不同的Error采取不同措施
        Throwable t = null;

        try {
            准备阶段
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            final Subject subject = createSubject(request, response);
            

            subject.execute(new Callable() {
                public Object call() throws Exception {
                    字面翻译即可
                    updateSessionLastAccessTime(request, response);
                    这是核心方法
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        } catch (Throwable throwable) {
            t = throwable;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
            String msg = "Filtered request failed.";
            throw new ServletException(msg, t);
        }
    }

subject.execute核心方法如下:

 protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }

//获取过滤器链
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;
        获取过滤器处理器
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }
        
        这个方法解释在下面
        FilterChain resolved = resolver.getChain(request, response, origChain);

        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;
    }





public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {

        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }
        获取访问的url路径
        String requestURI = getPathWithinApplication(request);


        循环遍历匹配是否符合要拦截的url
        for (String pathPattern : filterChainManager.getChainNames()) {

  
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                            "Utilizing corresponding filter chain...");
                }
                
                return filterChainManager.proxy(originalChain, pathPattern);

                内部有个挺好的idea:,这个方法不是这个重入函数里的
                  public FilterChain proxy(FilterChain orig) {
                        return new ProxiedFilterChain(orig, this);
                    }
                调用的同时生成一个新的代理Chain,从而继续代理
            }
        }

        return null;
    }
        
    


接下来就是具体的doFilter方法:这是属于ProxyFilterChain对象的方法,在这里Shiro将内部配置的bean一个一个都循环遍历下去,当然前提是要符合拦截要求

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {
            //we've reached the end of the wrapped chain, so invoke the original one:
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }

OK, 这就是差不多是过滤器链的流程了

 

 

至于第二个bug的解决方式有以下几种:

 第一种方法:.耦合度较高的一种:通过构造函数来注入: 如 A(IServiceB b)  然后在ShiroFactoryBean中添加属性参数 (IServiceB b)即可

第二种方法:.通过添加一个util来处理:

@Component
public class ApplicationContextUtil implements ApplicationContextAware
{
	private static ApplicationContext applicationContext;
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
	{
		if (null != applicationContext)
		{
			ApplicationContextUtil.applicationContext = applicationContext;
		} else
		{
			throw new RuntimeException("ApplicationContext配置为空");
		}
	}
	public static ApplicationContext getApplicationContext()
	{
		return ApplicationContextUtil.applicationContext;
	}
}

然后在doFilter方法中通过 :

	if(null==loadBalancerClient)
		{
			this.loadBalancerClient = ApplicationContextUtil.getApplicationContext().getBean(LoadBalancerClient.class);
		}
		if(null==tmallAdminConfigProperty)
		{
			this.tmallAdminConfigProperty = ApplicationContextUtil.getApplicationContext()
					.getBean(TmallAdminConfigProperty.class);
		}

进行调用,或许有人会说在init中初始化,这是不行的哦,因为init方法并不会执行,当然或许有人想通过@PostConsturct初始化,但是注意这个加载完毕的时候那个bean还没初始化呢,不过可以这个也实现ApplicationContext接口,这是可以的,但是做不到通用

第三种方法:可以算是最快捷方便的吧,将自定义的Filter注册为Bean,是的,注册为Bean,然后借助SpringBoot提供的FilterRegistrationBean<T> 这个类:禁止这个filter ,只让Shiro管理即可:代码如下:

	@Bean
	public AuthFilter authFilter()
	{
		return new AuthFilter();
	}
	@Bean
	public FilterRegistrationBean<Filter>filterReg()
	{
		FilterRegistrationBean<Filter>filterRegistrationBean=new FilterRegistrationBean<>();
		filterRegistrationBean.setFilter(authFilter());
		filterRegistrationBean.setEnabled(false);
		return filterRegistrationBean;
	}

并且这个方式可以使得这个Filter正常的通过@Autowired 注入Bean了

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值