本篇文章聚焦 shiro 的 filter 相关逻辑,核心逻辑SecurityManager、Subject、Authenticator后面再做详细分析。
在 web 应用中,shiro 发挥作用的切入点是根据 severlet 规范实现过滤器,通过过滤器对 web 请求进行拦截,然后做认证和权限检查。
那么 shiro 中都有哪些拦截器,在一个认证流程当中 filter 的执行顺序是怎么样的,是如何跟 web 系统集成的呢。
本文中的流程描述主要基于 OAuth2认证规范。
Filter 的注册
tomact 的 filter 信息是在ApplicationFilterChain中维护的,其结构是一个ApplicationFilterConfig数组,下面是其在源码中的定义:
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
想要知道shiro 的 filter 是如何注册进来的,可以在代码ApplicationFilterChain类中 filter 属性是如何往里面添加成员的。
FilterRegistrationBean对象分析
FilterRegistrationBean是在 spring 环境下,用户自定义 filter 注册的时候会用到的类。这个类的主要功能是包含了和 filter 相关的配置信息,包括filter,name,urlPatterns等,它之所以能在 tomact 启动的时候被加载到,是因为实现了ServletContextInitializer接口,注意这个接口不同于WebApplicationInitializer,并不会在 servlet 启动的时候被加载,而是受 spring 容器管理,根据 spring 的需要来调用对于的实现类。
Filter 注册到 Tomcat (embed)
在 spring 启动后会将配置为 bean 的 filter 注册到 spring 容器中。如下是 shiro filter 的注册到 spring 容器的方法。
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean shiroFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
registration.addInitParameter("targetFilterLifecycle", "true");
registration.setEnabled(true);
registration.setOrder(Integer.MAX_VALUE - 1);
registration.addUrlPatterns("/*");
return registration;
}
}
其中 名为shiroFilter的 bean 是在 shiro factoryBean的配置中完成的,如下是 shiro FactoryBean 的配置:
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/webjars/**", "anon");
......
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
ShiroFilterFactoryBean会将 shiro 的filter 相关配置保存生成一个名为shiroFilter的 bean,这个 bean 让后会被DelegatingFilterProxy代理,注册成为一个FilterRegistrationBean实例,最后这些 bean 在 tomact 启动的时候,在 onStartup方法中,一步步存放到 tomcat 的 filterMap 中,然后后续的 web 请求都会经过这些 filter。
Tomcat filter 执行流程
Shiro filter 关键对象介绍
先看一个类图。下图中展示了 shiro 中的主要过滤器,下面对这些 filter 的主要功能做介绍。
AdviceFilter:使用了类似 AOP 的编程思想,在执行 shiro 的 filterChain 之前和之后分别执行子类的preHandle和postHandle方法;PathMatchingFilter:继承自AdviceFilter,做路径匹配的过滤器,最核心的是preHandle方法(AdviceFilter.preHandle会调用到这里),对于匹配上的请求,会调用isFilterChainContinued做下一步处理逻辑,最后会调用抽象方法onPreHandle;AccessControlFilter:继承自PathMatchingFilter,上一步调用到onPreHandle后,会执行isAccessAllowed和onAccessDenied来判断请求是能容许通过,这两个方法都是抽象方法,具体实现逻辑由子类完成,用户也可以在自定义 filter 中覆写这两个方法来实现自己的需要的逻辑;- ``AuthenticationFilter`:这个接口功能比较简单,主要作用通过isAccessAllowed方法判断当前请求的用户是否已登录.
AuthenticatingFilter:最重要的功能是executeLogin和createToken,其中executeLogin的动作最终调用Subject.login完成,这里是 shiro 的主要功能(Subject,SecurityManager,Session)类集中交互的地方。OAuth2Filter:用户自定义filter,继承自AuthenticatingFilter,这里面 用户可以自定义createToken(是在 cookie 中存放 token 还是在 header 中直接存 token),onAccessDenied(未登录用户是直接返回401,还是进一步判断是否有 token 然后做下一步处理)等方法来完成自己的登录逻辑。SpringShiroFilter:继承自AbstractShiroFilter,该类是ShiroFilterFactoryBean在getObject方法实例化,也就是会在 spring 初始化的时候实例化,是 将shiro 的核心功能(WebSecurityManager,FilterChainResolver)集成到 filter中的纽带。
Shiro Filter 执行序列图
shiro 的过滤器执行的流程主要分为三个阶段,
- 第一阶段是 tomcat filter 通用流程;
- 第二阶段是 Shiro 的 filter 流程,转化的纽带是
DelegatingFilterProxy和SpringShiroFilter这两个跟 spring 相关的filter; - 第三阶段是用户自定义 filter 流程。主要根据用户实际需求晚上自定义逻辑。当然用户自定义 filter 只是覆写了几个方法,主要的流程还是在 shiro 的核心对象之间执行。