文档章节

shiro的代理过滤器

hyssop
 hyssop
发布于 2016/07/18 10:49
字数 2432
阅读 42
收藏 1
点赞 0
评论 0

在配置shiro的时候第一件事情就是在web.xml文件中配置一个由spring提供的类:org.springframework.web.filter.DelegatingFilterProxy 按照字面的翻译这应该是一个代理过滤器的策略。

<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>

    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

这个类其实是将上下文中名称为shiroFilter的类做成一个代理过滤器。该类将从spring上下文中找到自己要代理的过滤器类,并负责初始化和销毁该过滤器。并在每次发起的拦截请求是先走该类的方法invokeDelegate()。

##初始化

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
		Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
		if (isTargetFilterLifecycle()) {
			delegate.init(getFilterConfig());
		}
		return delegate;
	}

初始化过程就是将配置文件中的参数注入到ShiroFilterFactoryBean中的SpringShiroFilter中。

##请求调用 当我们在浏览器中打印了一个能够被shiro拦截的uri的时候首先会进入到DelegatingFilterProxy的doFilter方法中。该方法首先判断被代理的拦截器是否被初始化,如果没有则实行懒加载策略初始化shiro拦截器。否则则开始调用shiro拦截器去执行拦截。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// Lazily initialize the delegate if necessary.
		Filter delegateToUse = this.delegate;
		if (delegateToUse == null) {//懒加载过程
			synchronized (this.delegateMonitor) {
				if (this.delegate == null) {
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
					}
					this.delegate = initDelegate(wac);
				}
				delegateToUse = this.delegate;
			}
		}

	
		invokeDelegate(delegateToUse, request, response, filterChain);
	}
	protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
	// 调用真正shirofilter
		delegate.doFilter(request, response, filterChain);
	}

delegate实例最终类型是SpringShiroFilter。该类集成在AbstractShiroFilter。

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

AbstractShiroFilter类继承自OncePerRequestFilter

public abstract class AbstractShiroFilter extends OncePerRequestFilter{}

所以我们首先来看看OncePerRequestFilter。

   public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {//不拦截已经被拦截处理的请求
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } else //不过滤被配置不过滤的请求
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
            filterChain.doFilter(request, response);
        } else {
            //将来自该过滤请求(如shiroFitler)设置成已经被过滤
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
            //执行shiro的过滤逻辑。
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

如果到达的请求没有被OncePerRequestFilter过滤掉,则会走shiro的拦截请求。值得一起的是,FilterChain chain其实是spring的servlet上下文。这个是为了包装request、response的时候将servlet上下文也放置进来。

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

        Throwable t = null;

        try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);//包装request
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);//包装response

            final Subject subject = createSubject(request, response);//创建subject对象

            //执行回调函数。
            //1、更新session的最后访问时间
            //2、执行拦截器链。
            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);
        }
    }

shiro的拦截器首先将request和response进行了包装。并创建subject执行回调函数。该函数主要执行了两个部分:1、更新session的最后访问时间 2、执行拦截器链。重点看下执行拦截器链逻辑。

 protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);//获得拦截器链
        chain.doFilter(request, response);//执行拦截器链
    }

拦截器逻辑主要分成了两个部分:1、获得拦截器链 2、执行拦截器链。 1、获得拦截器链

protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;
//1.1获得拦截器链解析器
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }
//1.2获得最终的拦截器执行链。
        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;
    }

1.1、获得拦截器链解析器最终获得了PathMatchingFilterChainResolver的实例。 1.2、获得最终的拦截器执行链是执行PathMatchingFilterChainResolver中的getChain方法。

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
//获得过滤链管理器。该管理器是在初始化SpringShiroFilter时候注入进来的。
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }
//获得请求uri
        String requestURI = getPathWithinApplication(request);
//uri和拦截器链做匹配。
        for (String pathPattern : filterChainManager.getChainNames()) {
//如果匹配成功
            if (pathMatches(pathPattern, requestURI)) {
            //1.3返回一个代理过滤器
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }
    ```
1.3是将当前的servlet上下文和请求uri一起包装成为一个过滤器代理。
```java
FilterChainManager类
 public FilterChain proxy(FilterChain original, String chainName) {
        NamedFilterList configured = getChain(chainName);
        if (configured == null) {
            String msg = "There is no configured chain under the name/key [" + chainName + "].";
            throw new IllegalArgumentException(msg);
        }
        return configured.proxy(original);
    }
   public class SimpleNamedFilterList implements NamedFilterList {
     public FilterChain proxy(FilterChain orig) {
        return new ProxiedFilterChain(orig, this);
    }
   }

2、执行拦截器链 刚才看到获得拦截器的过程其实就是生成了一个类型为ProxiedFilterChain的实例。那么执行自然就是该类的doFilter方法。

 public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
 //如果过滤器链为空,则执行servlet上下文的过滤器。
        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);
        }
    }

这里面涵盖了一个过滤器的设计模式。当我们给某个uri设计多个拦截器的时候,这几个拦截器是怎么连续执行的呢?换句话说上面的index是如何一个一个累加直到this.filters.size() == this.index条件成立跳出循环的呢?我们来看下shiro里面的拦截器链实现。 首先观察一下shiro配置文章的这个段

   /static/** = anon
                /js/**  =anon
                /css/** =anon
                /favicon.ico =anon
                /images/** = anon
                /logout = logout
                /user/login=authc
                /** =sysUser,onlineSession,syncOnlineSession,perms,roles

我们不难发现这里面的拦截器都扩展自接口AdviceFilter或接口AdviceFilter的实现。给接口作用就是在执行拦截器前后添加一些逻辑。

public abstract class AdviceFilter extends OncePerRequestFilter {
   protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        return true;
    }
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
    }
    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
    }
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
        chain.doFilter(request, response);
    }
    //执行逻辑
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        Exception exception = null;
        try {
    //执行前的逻辑,如果未返回true则不执行过滤器逻辑。
            boolean continueChain = preHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
            }
//执行过滤器逻辑
            if (continueChain) {
                executeChain(request, response, chain);
            }
//执行后逻辑。
            postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }
        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }
    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
            throws ServletException, IOException {
    }
}

AdviceFilter扩展自OncePerRequestFilter类。

  public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            //执行过滤器链下一个过滤器
            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
                       //执行过滤器链下一个过滤器
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
            //执行本过滤器。
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

这两个类的共同特点就是执行doFilterInternal方法是执行本过滤器的过滤逻辑。执行filterChain是执行过利器链里面下一个过滤器的执行逻辑。filterChain的类型都是ProxiedFilterChain。

public class ProxiedFilterChain implements FilterChain {

    //TODO - complete JavaDoc

    private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class);

    private FilterChain orig;
    private List<Filter> filters;
    private int index = 0;

    public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
        if (orig == null) {
            throw new NullPointerException("original FilterChain cannot be null.");
        }
        this.orig = orig;
        this.filters = filters;
        this.index = 0;
    }

    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.");
            }
            //如果过滤器为空,或者过滤器链已经执行完,则orig里面如果有过滤器逻辑则执行。
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            //获得第index个过滤器并执行。
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }

过滤器模式简单地说就是:到了某个过滤器他会执行自己的逻辑(doFilterInternal方法),执行完自己的逻辑之后,他还会到刚刚传过来的过滤器链的doFilter方法返回到过滤器链下一个过滤器继续执行。 这里也有个疑惑,就是过滤器链执行完毕之后应该怎么返回到springmvc的DispatcherServlet里面的service()方法继续spring的逻辑呢? 答案就在这行: this.orig.doFilter(request, response); 我们在配置到web.xml文件中的过滤器和servlet的执行顺序首先按照配置的先后顺序执行过滤器,当过滤器执行完毕的时候会调用 FilterChain的doFilter()方法返回到servlet环境,然后再原路返回。那么为什么orig.doFilter能够返回到servlet呢,查看源码filterChian既不是servlet的代理也不是servlet的包装器。那什么时机让orig成为了servlet返回路径的呢? dubug到这里我们发现了如图的类:ServletHandler。原来这个ServletHandler是jetty用于管理Filter、FilterMapping、Servlet、ServletMapping的容器。(我用的是jetty,本段环境为jetty环境)。以及用于实现一次请求所对应的Filter链和Servlet执行流程的类。对Servlet的框架实现中,它也被认为是Handler链的末端,因而在它的doHandle()方法中没有调用nextHandle()方法。至于具体实现就等分析jetty源码的时候阐述了。

© 著作权归作者所有

共有 人打赏支持
hyssop
粉丝 18
博文 84
码字总数 103453
作品 0
昌平
程序员
springboot的shiro配置之过滤器

springboot整合shiro的文章到处都是。包括springboot的官网都有相应的例子。但是这块有个注意点,需要那些从springmvc迁到springboot的朋友注意下。这个问题困扰我了两三天,今天分享出来让后...

hyssop ⋅ 2016/11/21 ⋅ 0

自己的研究shiro框架的感悟1

首先在配置shiro框架时,由spring托管shiro。由spring框架中的DelegatingFilterProxy类来代理shiro框架中的ShiroFilterFactoryBean类,这样spring和shiro就有建立了关系。 web.xml文件中配置...

HelloRookie ⋅ 2016/10/16 ⋅ 0

SHIRO 相关类

Spring shiro filter 的初始化 org.apache.shiro.spring.web.ShiroFilterFactoryBean#getObject > createInstance 引出: 1. 接口 filterChainManager 管理所有的fileter和过滤器链 默认实现......

triankg ⋅ 2014/12/23 ⋅ 0

springboot和shiro整合

虽然网上一大堆这方面的文章,可是自己去整合还是会遇到这样那样的问题。今天写出来等待和我一样遇到相同问题的人看见。 首先明白一点springboot之后,web.xml配置基本上就是用不上了。那么,...

hyssop ⋅ 2016/10/21 ⋅ 0

一步一步教你用shiro——1引入shiro框架

一步一步教你用shiro——1引入shiro框架 一步一步教你用shiro——2配置并自定义realm 一步一步教你用shiro——3配置并自定义sessionManager 一步一步教你用shiro——4配置并自定义sessionDa...

肥肥小浣熊 ⋅ 04/30 ⋅ 0

Shiro 学习应用

和 Spring Security 一样,Shiro 也属于权限安全框架。和 Spring Security 相比,Shiro 更简单,学习曲线更低。关于 Shiro 的一系列特征及优点,很多文章已有列举,这里不再逐一赘述。这里记...

sp42 ⋅ 2014/05/05 ⋅ 0

DelegatingFilterProxy详解

全文内容转载至spring DelegatingFilterProxy,targetFilterLifecycle的作用 ,我重新整理一下文章的结构 今天在看SHIRO的时候,看到佟刚老师用了DelegatingFilterProxy类,并使用了targetF...

特拉仔 ⋅ 01/10 ⋅ 0

Apache Shiro 使用手册(五)Shiro 配置说明

Apache Shiro的配置主要分为四部分: 对象和属性的定义与配置 URL的过滤器配置 静态用户配置 静态角色配置 其中,由于用户、角色一般由后台进行操作的动态数据,因此Shiro配置一般仅包含前两...

heroShane ⋅ 2014/02/10 ⋅ 0

shiro+springMVC整合文档及Demo

1.web.xml <!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 --> <!-- 这里filter-name必须对应定义的<bean id="shiroFilter"/> --> <!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过......

罗曼蒂克瑟尔 ⋅ 2015/09/29 ⋅ 28

Shiro登录成功之后跳到指定URL

通常我们使用shiro,登录之后就会跳到我们上一次访问的URL,如果我们是直接访问登录页面的话,shiro就会根据我们配置的successUrl去重定向,如果我们没有配置successUrl的话,那么shiro重定向...

嘿嘿!! ⋅ 2016/10/20 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

istio 文档

https://istio.io/docs/concepts/ https://istio.io/docs/concepts/traffic-management/handling-failures/ https://istio.io/docs/concepts/traffic-management/rules-configuration/......

xiaomin0322 ⋅ 17分钟前 ⋅ 0

编程语言的作用及与操作系统和硬件的关系

一、编程语言的作用及与操作系统和硬件的关系 作用:编程语言是计算机语言,是一种程序员与计算机之间沟通的介质,通过编程语言可以使得计算机能够根据人的指令一步一步去工作,完成某种特定...

slagga ⋅ 28分钟前 ⋅ 0

runtime实现按钮点击事件

也不能说是实现吧,,,就是有点类似于RAC里边的写法,不用给btn添加另外的点击事件,就那个add...select...这样子很不友好,来看下代码: [self.btn handleControlEvent:UIControlEventTou...

RainOrz ⋅ 28分钟前 ⋅ 0

Windows系统运维转linux系统运维的经历

开篇之前,首先介绍一下我的背景把:我是一个三线城市的甲方运维。最近,在《Linux就该这么学》书籍的影响下和朋友小A(Linux运维已经三年了,工资也比我的高很多)的影响下,决定转行。最近...

linux-tao ⋅ 29分钟前 ⋅ 0

zip压缩工具,tar打包工具

zip压缩工具 zip打包工具跟前面说到的gzip,bz2,xz 工具最大的不一样是zip可以压缩目录。如果没有安装,需要使用yum install -y zip 来安装。安装完之后就可以直接使用了,跟之前提到的压缩...

李超小牛子 ⋅ 37分钟前 ⋅ 0

使用npm发布自己的npm组件包

一、注册npm账号 官网:https://www.npmjs.com/signup 注册之后需要进行邮箱验证,否则后面进行组件包发布时候会提示403错误,让进行邮箱核准。 二、本地新建一个文件夹,cd进入后使用npm i...

灰白发 ⋅ 39分钟前 ⋅ 0

010. 深入JVM学习—垃圾收集策略概览

1. 新生代可用GC策略 1. 串行GC(Serial Copying) 算法:复制(Copying)清理算法; 操作步骤: 扫描年轻代中所有存活的对象; 使用Minor GC进行垃圾回收,同时将存活对象保存到“S0”或“S...

影狼 ⋅ 39分钟前 ⋅ 0

JVM性能调优实践——JVM篇

在遇到实际性能问题时,除了关注系统性能指标。还要结合应用程序的系统的日志、堆栈信息、GClog、threaddump等数据进行问题分析和定位。关于性能指标分析可以参考前一篇JVM性能调优实践——性...

Java小铺 ⋅ 40分钟前 ⋅ 0

误关了gitlab sign-in 功能的恢复记录

本想关sign-up的,误点了sign-in 退出后登录界面提示: No authentication methods configured 一脸懵逼.. 百度后众多方案说修改application_settings 的 signin_enabled字段; 实际上新版本字段...

铂金蛋蛋 ⋅ 41分钟前 ⋅ 0

登录后,后续请求接口没有带登录cookie可能原因

1.XMLHttpRequest.withCredentials没设置好,参考https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials...

LM_Mike ⋅ 41分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部