一、前言
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Shiro的本质是filter(过滤器),filter在Java Web项目中是由容器负责管理的,本篇就从容器的角度看Shiro的Filter(过滤器)是如何被容器管理的,本篇主要涉及Tomcat相关逻辑,讲解Filter的初始化流程;
二、抛砖引玉
在看源码之前,先抛出两个个问题:
- Tomcat初始化后把Filter缓存到哪里?
- Tomcat,Spring,Shiro这三者的关系?
- 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;
}
}
// ....
}
用一张图表示:
所以,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真正用于注册Servlet和Filter的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);
}
}
四、总结
回答最开始的两个问题:
- Tomcat初始化后把Filter缓存到哪里?
——放到tomcat上下文对象的成员变量中。 - Tomcat,Spring,Shiro这三者的关系
——Tomcat负责顶层的接口定义,并且提供初始化Filter的上下文对象供Spring使用
——Spring通过ServletContextInitializer接口执行容器上下文的初始化动作
——Shiro通过工厂bean的配置,封装一个Filter对象给ServletContextInitializer接口 - Shiro filter的执行逻辑?
——Shiro filter有自己单独的调用链,同时还持有原始调用链,在执行完自己的filter后切换到原始调用链