ShiroFilterFactoryBean源码分析

shiro是一个安全框架,集验证、授权、会话管理以及加密功能,并且可以Spring集成,本文主要是针对在web环境下shiro和spring集成的基础下分析ShiroFilterFactoryBean。

废话不多说了,先上配置图

首先在web.xml配置shiroFilter, DelegatingFilterProxy类的作用是把shiroFilter的操作委托给spring

<filter>
     <filter-name>shiroFilter</filter-name>
     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
     <init-param>
     <!-- true为bean的生命周期由ServletContainer管理,false为SpringApplicationContext管理 -->
       <param-name>targetFilterLifecycle</param-name>
       <param-value>true</param-value>
     </init-param>
   </filter>
   <filter-mapping>
     <filter-name>shiroFilter</filter-name>
     <url-pattern>/*</url-pattern>
   </filter-mapping>

 

在spring中配置shiroFilter bean

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <property name="unauthorizedUrl" value="/special/unauthorized" />
        <property name="filters">
<!-- 自定义Filter --> <util:map> <entry key="authc" value-ref="formAuthenticationFilter" /> </util:map> </property> <property name="filterChainDefinitions"> <value> /resources/** = anon /login = ssl,authc /** = user </value> </property> </bean>

 

这样,shiro和spring的集成就基本配置好了,securityManager的配置现在暂时不分析,现在我们重点来分析下源码。

从配置文件中我们可以看到,我们使用了ShiroFilterFactoryBean创建shiroFilter,来看看ShiroFilterFactoryBean:

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {

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

    private SecurityManager securityManager;

    private Map<String, Filter> filters;

    private Map<String, String> filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition

    private String loginUrl;
    private String successUrl;
    private String unauthorizedUrl;

    private AbstractShiroFilter instance;

 

可以看到ShiroFilterFactoryBean的属性和我们shiroFilter bean的配置文件属性一一对应,spring在初始化shiroFilter的时候会把配置文件中的值通过setter方法set到实例中。

ShiroFilterFactoryBean实现了FactoryBean,FactoryBean是一个特殊Bean,当需要得到shiroFilter这个bean时,会调用getObject()来获取实例。

public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
return instance; }

 

到这里我们应该知道shiroFilter这个bean就是AbstractShiroFilter的一个实例。我们来看看是如何创建这个shiroFilter实例的:

protected AbstractShiroFilter createInstance() throws Exception {

        log.debug("Creating Shiro Filter instance.");

        SecurityManager securityManager = getSecurityManager();
//配置文件必须配置securityManager
if (securityManager == null) { String msg = "SecurityManager property must be set."; throw new BeanInitializationException(msg); }
//securityManager必须是WebSecurityManager的实例,也就是DefaultWebSecurityManager了
if (!(securityManager instanceof WebSecurityManager)) { String msg = "The security manager does not implement the WebSecurityManager interface."; throw new BeanInitializationException(msg); }
//创建FilterChainManager,用来管理Filter和FilterChain FilterChainManager manager
= createFilterChainManager();
//创建FilterChainResolver,用来配合FIlterChainManager工作 PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager);

//通过两个组件:securityManager chainResolver来实例化shiroFilter return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver); }

 

从上面的源码我们可以看到,shiroFilter = new SpringShiroFilter(securityManager, chainResolver),  securityManager我们在spring的配置文件已经配置好了,它是shiro的“心脏”,类似于SpringMVC中的DispatcherServlet,

它是一个顶级组件,管理验证、授权、会话管理、加密等组件,所有和shiro交互都经过securityManager,相当于一个大管家。FilterChainManager从名字可以猜测到应该是用来管理Filter和FilterChain的,我们看看它是如何创建的:

protected FilterChainManager createFilterChainManager() {
        
//创建manager实例,实例化时会把shiro缺省的Filter(如 authc, anon, ssl, user等)缓存到DefaultFilterChainManager中的map属性中 DefaultFilterChainManager manager
= new DefaultFilterChainManager();
//获取缺省的Filter Map
<String, Filter> defaultFilters = manager.getFilters();

//把loginUrl, successUrl, unanthorizedUrl三个属性设置到相应的缺省Filter中 for (Filter filter : defaultFilters.values()) { applyGlobalPropertiesIfNecessary(filter); }
//这个filters是我们在配置文件中<property name="filters"/>节点中配置的自定义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); }

//这里可以看到,DefaultFilterChainManager的属性map中包含了缺省的Filter以及用户定义Filter,统一由manager管理 manager.addFilter(name, filter, false); } }
//这个map是用来缓存配置文件的<filterChainDefinitions>节点的数据的,如 /login = anon, /user/** = user 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();
//创建FilterChain,下面分析下这里的源码 manager.createChain(url, chainDefinition); } }
return manager; }

 

可以看到是用DefaultFilterChainManager来创建manager实例的,我们先来看看DefaultFilterChainManager的属性,我们可以看到,DefaultFilterChainManager有两个重要的map,一个是filters,

就是我们上面分析的用来缓存缺省filter以及自定义filter的,另外的一个map filterChains是用来缓存NamedFilterList的,那么NamedFilterList是什么呢,通过源码可以看到它其实就是相当于一个List<Filter>!

我们在配置<filterChainDefinitions>节点时 url=filter是一对(一)多的,例如 /login=user, roles[admin], perms[user:edit],那么很明显这个filterChinas是用来缓存<filterChainDefinitions>节点的数据的。

public class DefaultFilterChainManager implements FilterChainManager {

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

    private FilterConfig filterConfig;

//上面分析过了,主要用来缓存缺省的filter和配置文件中<property name="filters">节点的filter
private Map<String, Filter> filters; //pool of filters available for creating chains
//这里我们猜测应该是用来缓存FilterChain的,也就是上面分析到的 manager.createChain(url, chainDefinition) private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain

通过源码我们可以看到确实filterChains是用来缓存<filterChainDefinitions>节点数据的,在缓存过程中只是多了对 anon,user[edit:1]这些字符串进行解析,大家具体可以看看源码。

到这里我们manager实例就基本分析完毕了,主要还是在于两个map,这个两个map分别用来缓存<bean id="shiroFilter" >节点下的数据,还有缺省的Filter,如anno,ssl,user等缺省Filter。

 

那么我们回到创建shiroFilter实例的源码

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); }
//上面已经分析过manager的创建过程
FilterChainManager manager
= createFilterChainManager();

//主要用来解析request的请求路径的,默认的ANT风格的路径,下面来分析这个类 PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager); return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver); }

 

PathMatchingFilterChainResolver

PathMatchingFilterChainResolver有两个属性,FilterChainManager和PatternMatcher(AntPathMatcher),前者我们分析过了,后者是一个路径匹配器,是用来匹配请求路径的。

public class PathMatchingFilterChainResolver implements FilterChainResolver {

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

    private FilterChainManager filterChainManager;

    private PatternMatcher pathMatcher;

    public PathMatchingFilterChainResolver() {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = new DefaultFilterChainManager();
    }

    public PathMatchingFilterChainResolver(FilterConfig filterConfig) {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = new DefaultFilterChainManager(filterConfig);
    }

    public PatternMatcher getPathMatcher() {
        return pathMatcher;
    }

  
    public void setPathMatcher(PatternMatcher pathMatcher) {
        this.pathMatcher = pathMatcher;
    }

    public FilterChainManager getFilterChainManager() {
        return filterChainManager;
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public void setFilterChainManager(FilterChainManager filterChainManager) {
        this.filterChainManager = filterChainManager;
    }

//主要的方法,originalChain为web.xml配置的Filter
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { FilterChainManager filterChainManager = getFilterChainManager();
//manager 的 filterChains is empty ?
if (!filterChainManager.hasChains()) { return null; } String requestURI = getPathWithinApplication(request); //the 'chain names' in this implementation are actually path patterns defined by the user. We just use them //as the chain name for the FilterChainManager's requirements

// filterchains.keySet() 也就是 url[] for (String pathPattern : filterChainManager.getChainNames()) { // If the path does match, then pass on to the subclass implementation for specific checks: if (pathMatches(pathPattern, requestURI)) { if (log.isTraceEnabled()) { log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "]. " + "Utilizing corresponding filter chain..."); }
//如果当前的请求url路径能匹配上,就返回一个代理FilterChain,这个代理FilterChain主要包含了originalChain(FilterChain)以及List<Filter>两个属性,后者为shiro配置的Filter
return filterChainManager.proxy(originalChain, pathPattern); } } return null; } protected boolean pathMatches(String pattern, String path) { PatternMatcher pathMatcher = getPathMatcher(); return pathMatcher.matches(pattern, path); } protected String getPathWithinApplication(ServletRequest request) { return WebUtils.getPathWithinApplication(WebUtils.toHttp(request)); } }

FilterChainResolver只定义了一个方法 getChain(), PathMatchingFilterChainResolver实现了这个方法,当接收到请求时,PatternMatcher结合DefaultFilterChainManager的两个map的数据来匹配请求路径,没有匹配到路径的时候返回null,

匹配成功后返回一个ProxiedFilterChain实例,ProxiedFilterChain实现FilterChain接口,里面维护了一个FilterChain以及一个LIst<Filter>,FilterChain也就是参数originalChain,它是web.xml配置的Filter形成的过滤链,List<Filter>是请求路径配到的Shiro配置的Filter列表。

从源码可以看出如果请求路径能匹配到多个shiro的url时,只会匹配到第一url,因为filters和filterChains都是有序的LinkedHashMap类型。

到此 shiroFilter = new SpringShiroFilter(securityManager, chainResolver)也就创建完毕了。

 

现在分析下shiroFilter是如何工作的,shiroFilter也是一个Filter,Filter的工作入口点是doFilter(...)方法,我们先来看看类继承关系图:

 

OncePerRequestFilter改写了doFilter(...)方法:

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(...),这是个模板方法,在AbstractShiroFilter实现了

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 {
//有请求的话更新一下会话的LastAccessTime 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); } }

 

来看看executeChain(...)

protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }

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

现在看来思路应该很清晰了。之前代码说到当请求没有匹配到shiro的Filter时返回null, 从上面代码我们可以看到当返回null时就执行originalChain也就是web.xml定义的FilterChain,如果匹配到了就返回一个代理FilterChain(ProxiedFilterChain)。

 

 

现在回到配置文件总结一下:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <property name="unauthorizedUrl" value="/special/unauthorized" />
        <property name="filters">
          <!-- 自定义Filter  -->
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /resources/** = anon
                /login = ssl,authc
                /** = user
            </value>
        </property>
    </bean>

配置文件中的三个节点loginUrl, successUrl, unauthorizedUrl主要是用来设置Shiro相应的缺省Filter(下面会附上缺省Filter列表),filters节点是配置我们自定义的Filter,缺省Filter以及自定义Filter都会缓存在DefaultFilterChainManager的

Map<String, Filter> filters属性中。<filterChainDefinitions>节点配置的是url和Filter的关系,一个url可以配置多个Filter,形成FilterChain,该节点的数据都会缓存在DefaultFilterChainManager的 Map<String, NamedFilterList> filterChains 

属性中,这两个map也是DefaultFilterChainManager的两个主要属性。

PathMatchingFilterChainResolver的作用是解析请求路径,返回FilterChain。它有两个重要属性:FilterChainManager和PatternMatcher,前者是上述说的DefaultFilterChainManager的实例,包含了两个map,缓存了Filter和FilterChain数据,

PatternMatcher是用来匹配请求路径的,采用的AntPathMatcher,匹配ANT风格路径。当请求路径路径匹配失败时,返回NULL,返回NULL时并非是不过滤请求了,而是用Web.xml配置的FilterChain来doFilter(...),如果匹配路径成功了,那么将

返回一个ProxiedFilterChain,这个类实现了FilterChain接口,它维护了两个重要属性,一个是web.xml配置的原始过滤链FilterChain,另一个是请求路径匹配成功的Shiro过滤器列表List<Filter>。这样这个请求就会调用相应的doFilter(...)方法

进行过滤。

至此,shiroFilter也就创建完成, shiroFilter = new SpringShiroFilter(securityManager, filterChainResolver)。

 

附上shiro缺省Filter

public enum DefaultFilter {

    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);

    private final Class<? extends Filter> filterClass;

    private DefaultFilter(Class<? extends Filter> filterClass) {
        this.filterClass = filterClass;
    }

    public Filter newInstance() {
        return (Filter) ClassUtils.newInstance(this.filterClass);
    }

    public Class<? extends Filter> getFilterClass() {
        return this.filterClass;
    }

    public static Map<String, Filter> createInstanceMap(FilterConfig config) {
        Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length);
        for (DefaultFilter defaultFilter : values()) {
            Filter filter = defaultFilter.newInstance();
            if (config != null) {
                try {
                    filter.init(config);
                } catch (ServletException e) {
                    String msg = "Unable to correctly init default filter instance of type " +
                            filter.getClass().getName();
                    throw new IllegalStateException(msg, e);
                }
            }
            filters.put(defaultFilter.name(), filter);
        }
        return filters;
    }
}

 

 

 

至今转行做Java已经半年有多,水平很差,如果有错误希望各位不吝赐教。

年龄较大,发现很多不常用的知识学习后稍纵即忘,写博客以记录之,并且与君共勉。

posted @ 2017-12-07 23:43  SuperFish  阅读(1191)  评论(1)    收藏  举报