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

来源:互联网 发布:软件系统设计方案 编辑:程序博客网 时间:2024/04/30 15:54

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

    • 安全认证框架Shiro 二- shiro过滤器工作原理
  • 第一前言
  • 第二ShiroFilterFactoryBean入口
  • 第三请求到来解析过程
  • 第四过滤器执行原理
  • 第五总结

第一:前言

由于工作原因,写上篇文章安全认证框架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方式提交的话,如果没登陆会去执行登陆操作。

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

原创粉丝点击