安全认证框架Shiro (二)- shiro过滤器工作原理

安全认证框架Shiro (二)- shiro过滤器工作原理

第一:前言

由于工作原因,写上篇文章安全认证框架Shiro (一)- ini配置文件过了好久,这里补上Shiro的后续学习经历。

第二:ShiroFilterFactoryBean入口

ShiroFilterFactoryBean实现FactoryBean,说明它是ShiroFilter的工厂类。它是怎么初始化让Shiro能很好的工作的呢,该类的入口方法是createInstance(),该方法实现了几个功能
1.创建了一个过滤器管理类FilterChainManager,该类主要管理shiro里的过滤器,里面有2个重要的属性
1.1 filters:管理全部过滤器,包括默认的关于身份验证和权限验证的过滤器,这些过滤器分为两组,一组是认证过滤器,有anon,authcBasic,auchc,user,一组是授权过滤器,有perms,roles,ssl,rest,port。同时也包含在xml里filters配置的自定义过滤器。在其它地方使用时都是从过滤器管理类里filters里拿的。且过滤器是单例的,整个Shiro框架只维护每种类型过滤器的单例。
1.2 filterChains:过滤链。它是我们重点关注的东西,是一个Map对象,其中key就是我们请求的url,value是一个NamedFilterList对象,里面存放的是与url对应的一系列过滤器。这后面会详细讲解。

2.将过滤器管理类设置到PathMatchingFilterChainResolver类里,该类负责路径和过滤器链的解析与匹配。根据url找到过滤器链。

我们以如下的xml配置为例讲解,如下:

<!-- Shiro的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.jsp"/>
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
                <entry key="logout" value-ref="logoutFilter" />
            </util:map>
        </property>
        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap" />
    </bean>

    <bean id="filterChainDefinitionMap"
        class="com.haedrig.shiro.spring.ChainDefinitionSectionMetaSource">
        <!-- 默认的连接配置 -->
        <property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /login = authc
                /logout = logout
                /authenticated = authc
                /views/**=anon
                /** = authc, perms
            </value>
        </property>
    </bean>

createInstance()源码如下:

 protected AbstractShiroFilter createInstance() throws Exception {
        log.debug("Creating Shiro Filter instance.");

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }
        //创建默认的过滤器管理器
        FilterChainManager manager = createFilterChainManager();

        //Expose the constructed FilterChainManager by first wrapping it in a
        // FilterChainResolver implementation. The AbstractShiroFilter implementations
        // do not know about FilterChainManagers - only resolvers:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        //将过滤器管理器设置到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);
    }

该方法创建过滤器管理器,同时设置到PathMatchingFilterChainResolver对象里,当有请求过来里,shiro会通过PathMatchingFilterChainResolver解析得到请求的url对应的过滤器链。

createFilterChainManager()方法:

 protected FilterChainManager createFilterChainManager() {

        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        //获得shiro默认的过滤器,同时将xml里配置的loginUrl,successUrl,unauthorizedUrl设置到不同过滤器里
        Map<String, Filter> defaultFilters = manager.getFilters();
        //apply global settings if necessary:
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        //将自定义过滤器添加到filters里让管理器管理,注意,filters是Map,所以key同名的会被覆盖。
        //Apply the acquired and/or configured filters:
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                //'init' argument is false, since Spring-configured filters should be initialized
                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
                manager.addFilter(name, filter, false);
            }
        }

        //build up the chains:
        //获得xml里filterChainDefinitions配置
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                //根据url创建url对应的过滤链**重点**重点**重点,每个配置过的url都对应一个过滤链
                manager.createChain(url, chainDefinition);
            }
        }

        return manager;
    }

讲解:
1.applyGlobalPropertiesIfNecessary(filter);方法是将配置的loginUrl,successUrl,unauthorizedUrl设置到不同过器里。其中loginUrl赋值到所以继承自AccessControlFilter的过滤器里,successUrl赋值到所以继承自AuthenticationFilter的过滤器里,unauthorizedUrl赋值到所以继承自AuthorizationFilter的过滤器里。
当然其实过滤器可以自己设置loginUrl,successUrl,unauthorizedUrl,自定义赋值的也覆盖全局指定的。

2.读取filters配置的自定义过滤器,将它们纳入到过滤器管理器里。

3.最后读取filterChainDefinitions配置,根据配置设置每个url对应的过滤链,filterChains保存这些配置,它是一个Map集合,key就是url,value就是过滤器组成的NamedFilterList集合。其实当请求过来时,解析出请求路径,会从filterChains里找到url对应的过滤链,按过滤器的策略一个一个执行下去。
同时会调用PathMatchingFilter的processPathConfig()方法做些赋值操作。下面会专门讲将从PathMatchingFilter开始工作的过程。

4.一步一步分析下来过滤器管理器里的过滤链filterChains如下:

{
    /login.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@6ed97422,
    /login=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@592dbd8,
    /logout=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@1141badb,
    /authenticated=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@2d1b876e,
    /views/**=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@31106774,
    /**=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@14cd7d10,
    /index.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@712384a,
    /admin.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@7643ef31
}

可以看到每个url对应一个SimpleNamedFilterList对象,SimpleNamedFilterList是个List子类对象,保存的是过滤器集。

第三:请求到来解析过程

我们知道DelegatingFilterProxy过滤器的代理类来实现拦截的,任何请求都会先经过shiro先过滤,直到成功才会执行javaweb本身的过滤器。
一个请求过来时,先到达AbstractShiroFilter.executeChain()方法,去根据request解析出来的url找到对应的过滤链,然后执行过滤器链。
executeChain()方法如下:

 protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
            //得到过滤器链
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }

进入getExecutionChain()方法:

 protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        //resolver即是前面说的PathMatchingFilterChainResolver对象。里面保存有过滤器管理器实例
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }

        //进入PathMatchingFilterChainResolver对象里,根据解析出来的requestURI找到对应的过滤器链并返回
        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;
    }

最终返回的过滤器链是:
这里写图片描述
而backingList里数据如下:
这里写图片描述
我们们在xml里配置的是一样的。

/login = authc

再往下一步一步走,中间过程省略看图
这里写图片描述
直到FormAuthenticationFilter.doFilterInternal()方法

public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        Exception exception = null;

        try {

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

这行代码boolean continueChain = preHandle(request, response);其实是真正直到的单个过滤器里了,要想知道这里面在干什么,需要往下继续看。

第四:过滤器执行原理

先从PathMatchingFilter类讲解开始,匹配路径的过滤器,因为所以的过滤器都会继承PathMatchingFilter类,它的作用是路径匹配。过滤器单独维护自己需要过滤的url。
整体看一下类的属性:
这里写图片描述
分别介绍属性详情:
appliedPaths:是个map类,存放需要过滤的URL的。
比如如下的配置:
这里写图片描述
会将这红框中的属性添加到appliedPaths集合里。
结果如下:
这里写图片描述
pathMatcher:匹配器,就是当请求过来时,匹配哪个url对应哪个过滤器的。
processPathConfig:解析xml里配置的url对应的过滤器,分别加到appliedPaths要应用的map集合里,由于每个过滤器都继承PathMatchingFilter,故每个过滤器都会经过这步操作。
比如如下图,刚启动web服务时过滤器管理器会解析xml里filterChainDefinitions配置的过滤链,在根据配置给每个url创建过滤器链时,会调用不同过滤器的processPathConfig方法让过滤器自己把url添加到appliedPaths集合里,因为过滤器是单例的,过滤器管理器的filters里也只维护过滤器的单个实例:
这里写图片描述

preHandle方法:请求过来时该方法匹配url路径是否是该过滤器要处理的。遍历appliedPaths里所有的url直到完全匹配成功或遍历完为止。如果匹配成功则表明该url请求是需要该过滤器处理。然后就会进入onPreHandle方法。
onPreHandle方法:该方法在路径匹配成功时决定对url是否需要身份验证。默认是返回true,意思是不需要验证的。但子类需要根据业务逻辑自己重写该方法。
看看对该方法的实现类有哪些。
这里写图片描述
其中重点看看AccessControlFilter和AnonymousFilter。一个是需要身份验证的,一个是匿名访问的。
AnonymousFilter就比较简单了,任何对象访问都一直返回true,表明任何用AnonymousFilter过滤的请求都不需要验证。因为它一直返回true。
这里写图片描述

AccessControlFilter是需要身份验证的过滤器。当请求在过滤器里匹配成功后。后续验证处理在这里。如图:
这里写图片描述

如果isAccessAllowed()验证成功则返回true,否则交由onAccessDenied()方法处理,最后将onAccessDenied()方法处理的结果返回。
isAccessAllowed()方法是决定了当前请求的subject是否允许访问。如果以前做过验证则返回true,否则返回false。
onAccessDenied()方法是在被拒绝访问时处理。AccessControlFilter类有很多子类重载了该方法。以FormAuthenticationFilter类的onAccessDenied()方法为例。
这里写图片描述
如果是登陆请求,则执行登陆操作,否则保存请求链接跳转到登陆请求界面。
executeLogin()方法就比较简单了。创建 token,获得Subject对象,然后执行login()最后到realm里查询数据库做比较,这里先不讲。
saveRequestAndRedirectToLogin()方法会执行WebUtils.saveRequest()将请求保存到session里
这里写图片描述

redirectToLogin()即是服务器跳转到登陆界面。
流程如下:
这里写图片描述


2017-11-16补


有必要再补充PathMatchingFilter里属性appliedPaths和过滤器链的关系:
如下图:
这里写图片描述
每个过滤器自己在appliedPaths集合维护自己需要处理的url集合,而url对应的过滤器链可能串连多个,它只要求对应的过滤器有自己的url即可,不管过滤器是否还要处理其它url.

第五:总结

入口在ShiroFilterFactoryBean,每个过滤器都会根据类型不同拥有loginUrl,successUrl,unauthorizedUrl中的一个或多个配置,过滤器管理器负责为配置的每个url创建过滤器链,对于没配置的url则对应到“/**”路径的过滤器链上。当请求过来时,ShiroFilterFactoryBean负责接收请求并让过滤器管理器通过一定的策略找到url对应的过滤器链执行过滤器链,这里有个特殊情况,登陆请求,如果是登陆请求且是POST方式提交的话,如果没登陆会去执行登陆操作。

未完这里,未完待续…..

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值