近期Shiro修复了一个漏洞,1.10.0之前的版本在请求forward时不进行拦截鉴权
如下测试代码,方法1不需要权限,方法2配置了authc,方法1转发方法2,则可以绕过方法2的鉴权
Controller
ShiroConfig
目录
ShiroFilter在两个版本间的代码区别
1.7版本
1.10版本
SpringBoot对与外部Filter的集成过程
FilterRegistrationBean执行流程
总结
1.7在forward请求时会跳过,1.10默认情况下forward请求也会走过滤
org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter shiro-web.jar
在该请求处理过第一次后,为请求添加了属性shiroFilter.FILTERED=true, 在第二次forward请求进来时会进到第一个if跳过本Filter的执行
org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter shiro-web.jar
增加了一个属性filterOncePerRequest来判断是否需要每个请求都进行过滤,如果为true则不过滤forward请求,如果为false每个请求都会走一遍鉴权
filterOncePerRequest属性在1.10之后新增,默认是false,也就是过滤forward
虽然shiro本身支持了forward过滤,但是在springBoot下使用shiro1.10新特性,不只是改个版本号
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
--》org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
--》org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getSelfInitializer
--》org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
在这里获取各种初始化类,调用onStartup方法进行web服务器的初始化
getServletContextInitializerBeans方法返回是一个集合类,查找容器中所有的Filter,Servlet,ServletContextInitializer等等相关的初始化bean
addServletContextInitializerBeans(beanFactory);
查询容器中所有ServletContextInitializer实现类放入this.initializer集合中,实现类列表如下, 可以看到Filter相关的实现类是FilterRegistrationBean,先忽略看圈内的第二个方法
addAdaptableBeans(beanFactory);
获取容器中Servlet/Filter类型的bean,将其作为RegistrationBean放入this.initializer集合,对于Filter来说,就是查询容器所有Filter类型的bean,封装为FilterRegistrationBean放入this.initializer集合
所以由上边两拨代码可知,可以有以下几种方式引入ShiroFilter(应该还有其他方式@WebFilter/DelegatingFilterProxy等 ,这里不涉及,咱忽略)
1、ShiroFilterFactoryBean
如上配置之后,ShiroFilter作为bean被加载到spring容器,再由addAdaptableBeans(beanFactory);方法将其封装为FilterRegistrationBean,调用onStartup方法加载到Tomcat中
2、FilterRegistrationBean
直接向spring容器注入FilterRegistrationBean
顶层父类继承自ServletContextInitializer,自然从onStartup方法看起
先将filter放入servlet容器,再进行配置
将filter加入Tomcat的servletContext
配置Filter
我们重点看下边代码开头的DispatcherType配置,如果我们没有自己在FilterRegistrationBean配置的话,默认情况下ShiroFilter是只会处理REQUEST类型的请求,因为ShiroFilter继承的不是org.springframework.web.filter.OncePerRequestFilter
@Override
protected void configure(FilterRegistration.Dynamic registration) {
super.configure(registration);
// 这里DispatcherType表示请求类型
// 首先设置Filter可以处理的请求类型
EnumSet dispatcherTypes = this.dispatcherTypes;
// 如果没有自己设置DispatcherType
if (dispatcherTypes == null) {
T filter = getFilter();
// 如果filter类型是org.springframework.web.filter.OncePerRequestFilter,则可以处理全部请求类型
if (ClassUtils.isPresent("org.springframework.web.filter.OncePerRequestFilter",
filter.getClass().getClassLoader()) && filter instanceof OncePerRequestFilter) {
dispatcherTypes = EnumSet.allOf(DispatcherType.class);
}
else {
// 否则只能处理REQUEST类型的请求
dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
}
}
Set servletNames = new LinkedHashSet<>();
for (ServletRegistrationBean> servletRegistrationBean : this.servletRegistrationBeans) {
servletNames.add(servletRegistrationBean.getServletName());
}
servletNames.addAll(this.servletNames);
if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
}
else {
if (!servletNames.isEmpty()) {
registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(servletNames));
}
if (!this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(this.urlPatterns));
}
}
}
注:ShiroFilter继承的也叫OncePerRequestFilter,同名,一个是spring的,一个是shiro的
在文章头的测试用例中forward转发开始后会进入如下方法,将当前请求路径改成转发路径和请求类型改为FORWARD
org.apache.catalina.core.ApplicationDispatcher#doForward
最终进入如下方法重新进行Filter过滤和Servlet处理
org.apache.catalina.core.ApplicationDispatcher#invoke
在内部创建Filter链时,会先查询当前请求的Dispatcher_type,当前forward请求为FORWARD,然后在filter中查询能处理该请求的Filter组成链
SpringBoot集成ShiroFilter时,默认情况下ShiroFilter不拦截Forward或者Include请求,如果在SpringBoot下使用要对Forward请求拦截鉴权,不仅需要将shiro-spring版本升级到1.10.0,而且需要手动配置FilterRegistrationBean的DispatcherType