《Shiro三部曲1》——前世今生

一、前言

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

Shiro的本质是filter(过滤器),filter在Java Web项目中是由容器负责管理的,本篇就从容器的角度看Shiro的Filter(过滤器)是如何被容器管理的,本篇主要涉及Tomcat相关逻辑,讲解Filter的初始化流程;

二、抛砖引玉

在看源码之前,先抛出两个个问题:

  1. Tomcat初始化后把Filter缓存到哪里?
  2. Tomcat,Spring,Shiro这三者的关系?
  3. Shiro filter的执行逻辑?

应该说,知道这三个问题,就基本上能够把shiro的顶层原理说清楚了!

三、源码解析

3.1 Tomcat缓存

说起缓存,对Tomcat来说,就是其上下文对象:StandardContext,该类中有关filter的成员变量有三个,也就是说,项目启动以后,Filter其实就是缓存在这三个成员变量中的;

    /**
     * The set of filter configurations (and associated filter instances) we
     * have initialized, keyed by filter name.
     */
    private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();


    /**
     * The set of filter definitions for this application, keyed by
     * filter name.
     */
    private Map<String, FilterDef> filterDefs = new HashMap<>();


    /**
     * The set of filter mappings for this application, in the order
     * they were defined in the deployment descriptor with additional mappings
     * added via the {@link ServletContext} possibly both before and after those
     * defined in the deployment descriptor.
     */
    private final ContextFilterMaps filterMaps = new ContextFilterMaps();

3.2 容器初始化流程

Tomcat上下文ApplicationContext extends ServletContext

    @Override
    public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {
        return addFilter(filterName, null, filter);
    }
    private FilterRegistration.Dynamic addFilter(String filterName,
        String filterClass, Filter filter) throws IllegalStateException {
        // ...... 这里context就是:StandardContext,ApplicationContext持有StandardContext对象引用
        context.addFilterDef(filterDef);
        // ......
    }

ServletContext

    /**
     * Add a filter definition to this Context.
     *
     * @param filterDef The filter definition to be added
     */
    @Override
    public void addFilterDef(FilterDef filterDef) {
        synchronized (filterDefs) {
            filterDefs.put(filterDef.getFilterName(), filterDef);
        }
        fireContainerEvent("addFilterDef", filterDef);
    }

如果你跟过代码,会发现这一步是Spring调用容器addFilter方法,而且第一个到达的filter是:characterEncodingFilter;
AbstractFilterRegistrationBean.java

	@Override
	protected Dynamic addRegistration(String description, ServletContext servletContext) {
		Filter filter = getFilter();
		return servletContext.addFilter(getOrDeduceName(filter), filter);
	}

characterEncodingFilter是Spring下的过滤器,怎么会调到tomcat中呢?
这是因为tomcat容器初始化逻辑中,调用了Spring的初始化方法,同时把容器上下文对象传给了Spring,Spring在初始化逻辑中使用容器对象方法又把filter初始化进了Tomcat容器的filter缓存,看源码:StandardContext

tomcat调用Spring的地方

    @Override
    protected synchronized void startInternal() throws LifecycleException {
    	// ....
	    // 调用ServletContainerInitializers
	    // 这里的ServletContainerInitializers 是由其他框架继承,然后被tomcat调用,spring boot中实现这个接口的类是:TomcatStarter
        for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(), getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }
    	// ....
	}

用一张图表示:
《Shiro三部曲》——1.混沌初开
所以,Tomcat,Spring,Shiro这三者交互的原理大概就清楚了:

Tomcat(Servlet)提供统一接口ServletContextInitializer,Spring实现该接口
Spring通过容器上下文回调容器方法,初始化Filter
Shiro本质上是一个含有Filter的spring bean,Spring回调容器的时候,顺便也就把filter带过去了

Spring回调逻辑:AbstractFilterRegistrationBean

	@Override
	protected Dynamic addRegistration(String description, ServletContext servletContext) {
		Filter filter = getFilter();
		return servletContext.addFilter(getOrDeduceName(filter), filter);
	}

3.3 Spring 注册器Bean

Tomcat Servlet提供Servlet容器初始化接口ServletContainerInitializer接口,用于Tomca调用下游框架(Spring)使用,在Tomcat逻辑中,所有实现这个接口类都会被调用,其作用就是用来初始化容器上下文,Spring实现这个接口的类是:TomcatStarter;

Spring如何执行初始化呢?这里有一个接口很关键:Servlet上下文初始化接口(ServletContextInitializer),这是Spring自己的接口,所有实现ServletContextInitializer的类都可以看作是用于上下文初始化的。

Spring提供一个单独的管理类:ServletContextInitializerBeans,看名字就知道,这个类不是Spring容器内的bean,它其实是一个集合类,包含真正执行初始化动作的ServletContextInitializer接口实现类。

且看其构造方法:

public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
	// 重点成员变量initializers,初始化bean的集合
	private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;

	private final List<Class<? extends ServletContextInitializer>> initializerTypes;
	
	@SafeVarargs
	@SuppressWarnings("varargs")
	public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
			Class<? extends ServletContextInitializer>... initializerTypes) {
		this.initializers = new LinkedMultiValueMap<>();
		this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
				: Collections.singletonList(ServletContextInitializer.class);
		addServletContextInitializerBeans(beanFactory);
		// 重点关注下面这个方法
		addAdaptableBeans(beanFactory);
		List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
				.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
				.collect(Collectors.toList());
		this.sortedList = Collections.unmodifiableList(sortedInitializers);
		logMappings(this.initializers);
	}
}

addAdaptableBeans方法作用是把所有实现ServletContextInitializer接口的类都集合起来,Spring真正用于注册ServletFilter的Bean是:RegistrationBean,RegistrationBean就是ServletContextInitializer的子类;

	@SuppressWarnings("unchecked")
	protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
		MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
		addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
		addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
		for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
			addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
					new ServletListenerRegistrationBeanAdapter());
		}
	}

以Filter为例,注册器的继承关系如下图:
注册器继承关系
addAsRegistrationBean方法核心作用就是把相应的初始化bean(Servlet,Filter初始化bean)找出来;

	private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
			Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
		// **重点关注getOrderedBeansOfType方法**,找bean的关键
		List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
		for (Entry<String, B> entry : entries) {
			String beanName = entry.getKey();
			B bean = entry.getValue();
			if (this.seen.add(bean)) {
				// **创建注册器**
				RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
				int order = getOrder(bean);
				registration.setOrder(order);
				this.initializers.add(type, registration); // 把注册器放入initializers
			}
		}
	}

3.4 注册器原理

重点来了

public class FilterRegistrationBean<T extends Filter> extends AbstractFilterRegistrationBean<T> {
	private T filter;
}

FilterRegistrationBean类有个filter成员变量,核心就是这个filter是如何找到并赋值的,从这里开始终于涉及到Shiro了。
用过Shiro的同学,一定知道Shiro的配置Bean是ShiroFilterFactoryBean

@Configuration
public class ShiroConfig {
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //注入核心安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //注入拦截器
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("myFilter", new MyFilter());
        shiroFilterFactoryBean.setFilters(filters);

        //配置拦截链
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/user/update", "authc");

        //设置登录的请求
        shiroFilterFactoryBean.setLoginUrl("/login");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
}

可以看到这是一个工厂bean,工厂bean的作用就是在使用的时候不返回工厂bean自身,而是由工厂bean创建出一个新的对象并返回,那么ShiroFilterFactoryBean会创建什么对象呢?还是得从源码找答案:

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
	// 内部类
    private static final class SpringShiroFilter extends AbstractShiroFilter {
    }
}

在源码中,可以看一个内部类:SpringShiroFilter,工厂bean:ShiroFilterFactoryBean最终要返回的就是这个内部类的对象,SpringShiroFilter继承自Filter接口,因此,可以说ShiroFilterFactoryBean返回的其实就是一个过滤器Filter;

但是,一个ShiroFilterFactoryBean下面可以配置多个filter,那么是如何返回的呢?

    private static final class SpringShiroFilter extends AbstractShiroFilter {
        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            super();
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            }
            setSecurityManager(webSecurityManager);
            if (resolver != null) {
                setFilterChainResolver(resolver);
            }
        }
    }

其实,ShiroFilterFactoryBean返回的这个filter并不是我们在配置中写的那些filter,而是一个总Filter,是Shiro扩展后的Filter,其内部包含了filter的调用链,具体的filter在写配置bean的时候,就已经new出来了。

        //注入拦截器
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("myFilter", new MyFilter());
        shiroFilterFactoryBean.setFilters(filters);

3.5 Shiro filters的执行逻辑

Shiro对外(Tomcat)提供的一个是一个统一的Filter,但是对内Shiro是封装了一堆的filters,有默认的,也有用户自定义的,通过内部的调用链进行调用,Shiro自己的调用链是:ProxiedFilterChain,传统Filter的调用链是ApplicationFilterChain;
AbstractShiroFilter类:

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

        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);
        }
    }

ProxiedFilterChain.doFilter

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    	// 判断shiro的filters是否有匹配的filter,或者是否执行完自己的filters
        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.");
            }
            // 如果没有,则转到原始ApplicationFilterChain调用链下进行调用
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            // 如果匹配到filters,则调用shiro内部的filter
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }

四、总结

回答最开始的两个问题:

  1. Tomcat初始化后把Filter缓存到哪里?
    ——放到tomcat上下文对象的成员变量中。
  2. Tomcat,Spring,Shiro这三者的关系
    ——Tomcat负责顶层的接口定义,并且提供初始化Filter的上下文对象供Spring使用
    ——Spring通过ServletContextInitializer接口执行容器上下文的初始化动作
    ——Shiro通过工厂bean的配置,封装一个Filter对象给ServletContextInitializer接口
  3. Shiro filter的执行逻辑?
    ——Shiro filter有自己单独的调用链,同时还持有原始调用链,在执行完自己的filter后切换到原始调用链
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值