springboot整合shiro的文章到处都是。包括springboot的官网都有相应的例子。但是这块有个注意点,需要那些从springmvc迁到springboot的朋友注意下。这个问题困扰我了两三天,今天分享出来让后来人少踩坑。
spring整合shiro的时候我们会配置一个shiro.xml文件,将shiro的配置信息全部配置进去然后在web.xml里面配置一个过滤器代理就足够了。
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
但是到了springboot情况有所不同。之前我按照之前的方法原原本本的将shiro配置都写到了java文件中,并没有发现什么问题。可是真的使用的时候我发现了一个问题–过滤器链在无限次的自循环执行!
过滤器链后面连着servlet是一个完整的uri请求执行的路径。可是过滤器链自己无限次的循环执行,servlet根本执行不找,那么view也就不可能返回给前台页面,导致页面总是报连接异常。
找这个问题我也是走了很多的弯路。首先查找配置文件是不是配置错误了,找了几遍和网上写的都是一致的。然后一直在跟jetty的ServletHandler这个类。
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException
{
final Request baseRequest=Request.getBaseRequest(request);
// pass to next filter
if (_filterHolder!=null)
{
if (LOG.isDebugEnabled())
LOG.debug("call filter {}", _filterHolder);
Filter filter= _filterHolder.getFilter();
//if the request already does not support async, then the setting for the filter
//is irrelevant. However if the request supports async but this filter does not
//temporarily turn it off for the execution of the filter
if (baseRequest.isAsyncSupported() && !_filterHolder.isAsyncSupported())
{
try
{
baseRequest.setAsyncSupported(false,_filterHolder.toString());
filter.doFilter(request, response, _next);
}
finally
{
baseRequest.setAsyncSupported(true,null);
}
}
else
filter.doFilter(request, response, _next);
return;
}
// Call servlet
HttpServletRequest srequest = (HttpServletRequest)request;
if (_servletHolder == null)
notFound(baseRequest, srequest, (HttpServletResponse)response);
else
{
if (LOG.isDebugEnabled())
LOG.debug("call servlet " + _servletHolder);
_servletHolder.handle(baseRequest,request, response);
}
}
我很疑惑为什么这里面的_servletHolder为空!!(对底层不了解,为空也不知为啥。)
后来我想过滤器链无限循环就关注过滤器链。现在我配置了a,b,c,d四个过滤器,这四个过滤器应该被包装在spring的DelegatingFilterProxy过滤器代理e中,而DelegatingFilterProxy过滤器代理会被包装成FilterRegistrationBean类型的f。也就是说过滤器链应该是形如这样的
e(a,b,c,d包含其中)->f-Dispacher
而我发现项目中的过滤器是酱紫的:a->b->c->d->e->f->Dispacher
其中c过滤器(LogoutFilter)过滤了所有请求,使用了shiro的页面跳转规则
WebUtils类
public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative, boolean http10Compatible) throws IOException {
RedirectView view = new RedirectView(url, contextRelative, http10Compatible);
view.renderMergedOutputModel(queryParams, toHttp(request), toHttp(response));
}
我们配置的spring跳转规则自然会无法正常跳转。这是无语啊~~
后来想想,spring大费周章的将这些过滤器代理了,到了springboot又来一个FilterRegistrationBean包装spring的代理过滤器。而现在这些过滤器裸露在外,肯定是配置的问题。
于是按照以下配置修改了xml的配置
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="${shiro.login.url}"/>
<property name="unauthorizedUrl" value="${shiro.unauthorizedUrl}"/>
<property name="filters">
<util:map>
<entry key="authc">
<bean id="formAuthenticationFilter"
class="*.*.a">
</bean>
</entry>
<entry key="logout">
<bean id="logoutFilter" class="*.*.b">
</entry>
<entry key="sysUser">
<bean id="sysUserFilter" class="*.*.c">
</bean>
</entry>
<entry key="onlineSession">
<bean id="onlineSessionFilter"
class="*.*.d">
</bean>
</entry>
<entry key="syncOnlineSession">
<bean id="syncOnlineSessionFilter"
class="*.*.e">
<property name="onlineSessionDAO" />
</bean>
</entry>
<entry key="myfilter">
<bean id="myFilter" class="*.*.f"/>
</entry>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/static/** = anon
/js/** =anon
/css/** =anon
/favicon.ico =anon
/images/** = anon
/logout = logout
/user/login=authc
/** =sysUser,onlineSession,syncOnlineSession,perms,roles
</value>
</property>
</bean>
这样的配置后,过滤器链恢复了正常,这些自定义的过滤器包装在了代理过滤器中。
这个问题之所以是个问题,还在于对以下几个问题没搞清楚:
1、FilterRegistrationBean类的实现机制
2、shiro过滤器链的实现机制
下面我就从这两个方面说说我最近看到的。
#FilterRegistrationBean类的实现机制
说到这个类我们先来说说在没有整合springboot的时候我们在web.xml里面配置的类DelegatingFilterProxy。
在web.xml里面我们只要将真正的shiroFilter的id和这个类的过滤器名称配置一致就能够实现代理。
这个原理很简单,DelegatingFilterProxy这个类会根据这个id名称在spring容器中找到这个类。
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
然后调用shiroFilter的init方法,将我们配置的一堆过滤器和一堆映射转换成相应的字段信息
public void init() throws Exception {
WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());
setSecurityManager(env.getWebSecurityManager());
FilterChainResolver resolver = env.getFilterChainResolver();
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
}
到了springboot里面又有加了这个类FilterRegistrationBean,将代理过滤器又包装了起来。主要是因为springboot在启动spring容器的时候会调用以下方法将过滤器加载到servlet上下文。
FilterRegistration.Dynamic added = servletContext.addFilter(name, filter);
也就是说之前我们的delegate配置到了web.xml直接能够放到servlet上下文。到了springboot它不默认去读web.xml。那么到springboot中delegateFilter是通过这个FilterRegistrationBean类放置到servlet上下文的。
下次再加载什么类,是不是可以想想这个类。
2、shiro过滤器链的实现机制
shiro的过滤器链算是比较重要的部分。今天有必要大体了解下。
shiro中包括自定义过滤器和shiro自己的过滤器又很多很多。他们组织在一起的机制是什么呢?
shiro的filter是一个工厂类ShiroFilterFactoryBean生产出来的,生产出来的过滤器类型是SpringShiroFilter。
这个类型里面包含了两个参数:一个是安全管理器,一个是过滤器链解析器
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);
从图中可以看出SpringShiroFilter实现了servlet3.0的Filter接口,并且继承了OncePerRequestFilter类。所以它既是一个普通的类,有是包含了shiro特性的代理类。 无论是shiroFilter还是代理过滤器SpringShiroFilter都是继承自AbstractShiroFilter,过滤器执行方法如下:
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
chain的类型是代理过滤器链ProxiedFilterChain,这样shiro的内部过滤器链执行过程和其他的过滤器链执行过程很类似。
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);
}
}
有了代理类SpringShiroFilter,它代理的那些过滤器都有一个特点:继承自AdviceFilter 该类有以下部分组成: ##过滤器链继续执行前
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
return true;
}
继承类可以覆盖该方法在过滤器链执行前,做一些自己想要的操作。 ##过滤链执行
rotected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
chain.doFilter(request, response);
}
这个是过滤器链执行过程。 ##过滤器链执行完以后(正常)
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
}
过滤器链执行之后执行该方法,继承类可以覆盖该方法在过滤器链执行后,做一些想要的操作。 ##过滤链执行完成
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
}
过滤器链在执行的过程中可能抛出异常,这个时候过滤器之后的操作都将无法执行。但是afterCompletion方法无论过滤器链在正常完成或是异常完成的情况下都会被执行。该方法特别适合完成一些后期的清除工作。(类似finnally原理)。
有了执行功能的过滤器,有了这些过滤器的代理过滤器。那么还有一个问题就清楚过滤器了:过滤器执行。
#过滤器执行 代理过滤器实现了接口AbstractShiroFilter。它内部的过滤器是通过方法doFilterInternal实现的。
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);
//noinspection unchecked
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;
}
1、包装shiro的request类。
2、包装shiro的response类。
3、创建subject实例
4、调用subject实例的execute方法执行callable类的call()方法。
5、call()方法做了两件事情:更新session最新访问时间,执行shiro内部能够处理本次request请求的过滤器链(通过过滤器链解析器过滤出来的),并执行。
执行过程很巧妙,将任务包装成callable并将其放入subject的execute方法中。
public <V> V execute(Callable<V> callable) throws ExecutionException {
Callable<V> associated = associateWith(callable);
try {
return associated.call();
} catch (Throwable t) {
throw new ExecutionException(t);
}
}
该方法将callable继续包装成SubjectCallable实例。
public <V> Callable<V> associateWith(Callable<V> callable) {
return new SubjectCallable<V>(this, callable);
}
最后调用的是SubjectCallable的call方法。
public V call() throws Exception {
try {
threadState.bind();
return doCall(this.callable);
} finally {
threadState.restore();
}
}
call方法做了两件事情:
1、获取该线程的subject信息,并且将securityManager绑定到该线程中。
2、调用doCall方法执行过滤器链。
过滤器链是shiro的核心,但是不恰当的配置会导致过滤器链死循环执行。理解shiro的过滤器链实现原理,能够在我们排解相关错误做一个基础。
欢迎关注我的微信号:hyssop的后花园
转载于:https://my.oschina.net/zjItLife/blog/791660