鲁春利的工作笔记,好记性不如烂笔头



ShiroFilter

Apache Shiro学习笔记(六)Shiro Filter介绍_第1张图片


ShiroFilter,它是在整个 Shiro Web 应用中请求的门户,所有的请求都会被 ShiroFilter 拦截并进行相应的链式处理。ShiroFilter 往上有五层,最上层是 Filter(即 javax.servlet.Filter),它是 Servlet 规范中的 Filter 接口。


1、AbstractFilter

    Shiro通过抽象类对Servlet的Filter接口进行了封装,并通过继承ServletContextSupport对ServletContext也进行了封装。

package org.apache.shiro.web.servlet;
// ServletContextSupport对ServletContext进行了封装
public abstract class AbstractFilter extends ServletContextSupport implements Filter {

    protected FilterConfig filterConfig;

    public FilterConfig getFilterConfig() {
        return filterConfig;
    }
    
    // 初始化 FilterConfig 与 ServletContext
    public void setFilterConfig(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
        // 调用ServletContextSupport的方法来封装ServletContext
        setServletContext(filterConfig.getServletContext());
    }
    
    // 从 FilterConfig 中获取初始参数
    protected String getInitParam(String paramName) {
        FilterConfig config = getFilterConfig();
        if (config != null) {
            return StringUtils.clean(config.getInitParameter(paramName));
        }
        return null;
    }

    public final void init(FilterConfig filterConfig) throws ServletException {
        // 初始化 FilterConfig
        setFilterConfig(filterConfig);
        try {
            // 在子类中实现该模板方法
            onFilterConfigSet();
        } catch (Exception e) {
            // 异常信息
        }
    }
}

Shiro对ServletContext的封装

Shiro 为了封装 ServletContext 的而提供的一个类ServletContextSupport

package org.apache.shiro.web.servlet;
// 
public class ServletContextSupport {
    private ServletContext servletContext = null;

    public ServletContext getServletContext() {
        return servletContext;
    }

    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }
    
    // 其他代码略
}


2、NameableFilter

提供了Filter Name的获取方式。

package org.apache.shiro.web.servlet;

public abstract class NameableFilter extends AbstractFilter implements Nameable {
    /**
     * The name of this filter, unique within an application.
     */
    private String name;
    
    // 若成员变量 name 为空,则从 FilterConfig 中获取 Filter Name
    protected String getName() {
        if (this.name == null) {
            FilterConfig config = getFilterConfig();
            if (config != null) {
                this.name = config.getFilterName();
            }
        }
        return this.name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

    每个 Filter 必须有一个名字,可通过 setName 方法设置的,如果不设置就取该 Filter 默认的名字,也就是在 web.xml 中配置的 filter-name 了。


3、OncePerRequestFilter

    NameableFilter是为了让每个 Filter 有一个名字,而且这个名字必须是唯一的。此外,在 shiro.ini 的 [urls] 片段的配置要求满足一定规则,例如:

[urls]
/foo = ssl, authc

    等号左边的是 URL,右边的是 Filter Chian,一个或多个 Filter,每个 Filter 用逗号进行分隔。

    对于 /foo 这个 URL 而言,可先后通过 ssl 与 authc 这两个 Filter。如果我们同时配置了两个 ssl,这个 URL 会被 ssl 拦截两次吗?答案是否定的,因为 Shiro 为我们提供了一个“一次性Filter”的原则,也就是保证了每个请求只能被同一个 Filter 拦截一次,而且仅此一次。

package org.apache.shiro.web.servlet;

public abstract class OncePerRequestFilter extends NameableFilter {

     /**
     * 已执行过的过滤器("already filtered")附加的后缀名
     *
     * @see #getAlreadyFilteredAttributeName
     */
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

    /**
     * 是否开启过滤功能
     *
     * @see #isEnabled()
     */
    private boolean enabled = true; //most filters wish to execute when configured, so default to true

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 获取 Filter 已过滤的属性名
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        // 判断是否已过滤
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            // 若已过滤,则进入 FilterChain 中下一个 Filter
            filterChain.doFilter(request, response);
            
        // 若未过滤,则判断是否未开启过滤功能(其中 shouldNotFilter 方法将被废弃
        } else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
            // 若未开启,则进入 FilterChain 中下一个 Filter
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...(执行过滤)
            // 将已过滤属性设置为 true(只要保证 Request 中有这个属性即可)
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                // 在子类中执行具体的过滤操作
                doFilterInternal(request, response, filterChain);
            } finally {
                // 当前 Filter 执行结束需移除 Request 中的已过滤属性
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }
    
    protected String getAlreadyFilteredAttributeName() {
        String name = getName();
        if (name == null) {
            name = getClass().getName();
        }
        return name + ALREADY_FILTERED_SUFFIX;
    }
    // 抽象方法,由子类实现
    protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException;
}

    如何确保每个请求只会被同一个 Filter 拦截一次呢?Shiro 提供了一个超简单的解决方案:在 Requet 中放置一个后缀为 .FILTERED 的属性,在执行具体拦截操作(即 doFilterInternal 方法)之前放入该属性,执行完毕后移除该属性。

    在 Shiro 的 Filter Chian 配置中,如果我们想禁用某个 Filter,如何实现呢?OncePerRequestFilter 也为我们提供了一个 enabled 的属性,方便我们可以在 shiro.ini 中随时禁用某个 Filter,例如:

[main]
ssl.enabled = false

[urls]
/foo = ssl, authc

    这样一来 ssl 这个 Filter 就被我们给禁用了,以后想开启 ssl 的话,完全不需要在 urls配置中一个个手工来添加,只需把 ssl.enabled 设置为 true,或注释掉该行,或直接删除该行即可。

    可见,OncePerRequestFilter 给我们提供了一个模板方法doFilterInternal,在其子类中我们需要实现该方法的具体细节,那么谁来实现呢?


3.1、AbstractShiroFilter

package org.apache.shiro.web.servlet;
/**
 * 确保可通过 SecurityUtils 获取 SecurityManager,并执行过滤器操作
 */
public abstract class AbstractShiroFilter extends OncePerRequestFilter {
    // 是否可以通过 SecurityUtils 获取 SecurityManager
    private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";
    
    // Reference to the security manager used by this filter
    private WebSecurityManager securityManager;

    // Used to determine which chain should handle an incoming request/response
    private FilterChainResolver filterChainResolver;
    
    private boolean staticSecurityManagerEnabled;

    protected AbstractShiroFilter() {
        this.staticSecurityManagerEnabled = false;
    }

    public WebSecurityManager getSecurityManager() {
        return securityManager;
    }

    public void setSecurityManager(WebSecurityManager sm) {
        this.securityManager = sm;
    }

    public FilterChainResolver getFilterChainResolver() {
        return filterChainResolver;
    }

    public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
        this.filterChainResolver = filterChainResolver;
    }
    
    // 在AbstractFilter.ini()中调用子类的实现
    protected final void onFilterConfigSet() throws Exception {
        //added in 1.2 for SHIRO-287:
        // 从 web.xml 中读取 staticSecurityManagerEnabled 参数(默认为 false)
        applyStaticSecurityManagerEnabledConfig();
        // 初始化(在子类中实现)
        init();
        // 确保 SecurityManager 必须存在
        ensureSecurityManager();
        //added in 1.2 for SHIRO-287:
        // 若已开启 static 标志,则将当前的 SecurityManager 放入 SecurityUtils 中,以后可以随时获取
        if (isStaticSecurityManagerEnabled()) {
            SecurityUtils.setSecurityManager(getSecurityManager());
        }
    }
    
    public void init() throws Exception {
        // 空方法体
    }
    
    // OncePerRequestFilter.doFilter 时需要执行的方法
    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, 
                                    final     FilterChain chain)throws ServletException, IOException {

        Throwable t = null;

        try {
            // 通过ShiroHttpServletRequest对ServletRequest进行了封装
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            // 通过ShiroHttpServletResponse对ServletResponse进行了封装
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
            // 创建 Shiro 的 Subject 对象(WebSubject)
            final Subject subject = createSubject(request, response);

            // 使用异步的方式执行相关操作
            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    // 更新 Session 的最后访问时间
                    updateSessionLastAccessTime(request, response);
                    // 执行 Shiro 的 Filter Chain
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            // 异常处理
        } catch (Throwable throwable) {
            // 异常处理
        }

        if (t != null) {
            // 异常处理
        }
    }
    
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        // 获取 Shiro 代理后的 FilterChain 对象,并进行链式处理
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }
    
    protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        // 在ShiroFilter的init方法中设置FilterChainResolver
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }
        // 通过 FilterChainResolver(PathMatchingFilterChainResolver) 获取 ProxiedFilterChain
        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;
    }
}


3.1.1、ShiroFilter

package org.apache.shiro.web.servlet;

public class ShiroFilter extends AbstractShiroFilter {
    @Override
    public void init() throws Exception {
        // 从 ServletContext 中获取 WebEnvironment(该对象已通过 EnvironmentLoader 创建)
        // 实际实现为:通过ServletContext获取到web.xml文件中定义的shiroEnvironmentClass
        WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());

        // IniWebEnvironment.init会生成SecurityManager,在getWebSecurityManager时直接获取到
        setSecurityManager(env.getWebSecurityManager());
        
        // 通过IniFilterChainResolverFactory.createDefaultInstance获取PathMatchingFilterChainResolver
        FilterChainResolver resolver = env.getFilterChainResolver();
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
    }
}

在 ShiroFilter 中只用做初始化的行为,就是从 WebEnvironment 中分别获取 WebSecurityManager与FilterChainResolver,其它的事情都由它的父类去实现了。
实际上ShiroFilter 还实现了一些其他的封装,例如:
    通过 ShiroHttpServletRequest 来包装 Request
    通过 ShiroHttpServletResponse 来包装 Response
    通过 Session 来代理 HttpSession
    提供 FilterChain 的代理机制
    使用 ThreadContext 来保证线程安全