Shiro源码(二)-拦截请求原理(以anon为例进行分析)

  之前研究了,shiro 发挥作用的入口是在一个org.apache.shiro.spring.web.ShiroFilterFactoryBean.SpringShiroFilter。 这个对象内部维持了两个重要的对象: WebSecurityManager 和 FilterChainResolver。源码如下:

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

  所以猜测在对请求处理过程中会大用这两个对象,下面以这个对象为入口研究其处理过程。

0. 前期配置

1. Shiro 配置

  和上文一样,用如下配置:

package com.zd.bx.config.shiro;

import com.zd.bx.utils.file.PropertiesFileUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

@Configuration
public class ShiroConfig {

    private static final Map<String, String> FILTER_CHAIN_DEFINITION_MAP = new HashMap<>();

    static {
        initFilterChainDefinitionMap();
    }

    private static void initFilterChainDefinitionMap() {
        // 加载配置在permission.properties文件中的配置
        Properties properties = PropertiesFileUtils.getProperties("permission.properties");
        if (properties != null && CollectionUtils.isNotEmpty(properties.entrySet())) {
            Iterator<Entry<Object, Object>> iterator = properties.entrySet().iterator();
            while (iterator.hasNext()) {
                Entry<Object, Object> next = iterator.next();
                String key = next.getKey().toString();
                String value = next.getValue().toString();
                FILTER_CHAIN_DEFINITION_MAP.put(key, value);
            }
        }

        /**
         *  路径 -> 过滤器名称1[参数1,参数2,参数3...],过滤器名称2[参数1,参数2...]...
         * 自定义配置(前面是路径, 后面是具体的过滤器名称加参数,多个用逗号进行分割,过滤器参数也多个之间也是用逗号分割))
         * 有的过滤器不需要参数,比如anon, authc, shiro 在解析的时候接默认解析一个数组为 [name, null]
         */
        FILTER_CHAIN_DEFINITION_MAP.put("/test2", "anon"); // 测试地址
        FILTER_CHAIN_DEFINITION_MAP.put("/user/**", "roles[系统管理员,用户管理员],perms['user:manager:*']");
        FILTER_CHAIN_DEFINITION_MAP.put("/**", "authc"); // 所有资源都需要经过验证
    }

    // 将自己的验证方式加入容器
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }

    // 权限管理,配置主要是Realm的管理认证
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    // Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 登录页和成功跳转页面不需要设置(前后端不分离项目可以写)
        // factoryBean.setLoginUrl("/shiro/login.html");
        // factoryBean.setSuccessUrl("/shiro/index.html");

        // 自定义需要权限验证的过滤器。对于前后端分离的项目可以自定重写这个filter
//        Map<String, Filter> filterMaps = new HashMap<>();
//        filterMaps.put("authc", new ShiroAuthFilter());
//        shiroFilterFactoryBean.setFilters(filterMaps);

        // 定义处理规则
        shiroFilterFactoryBean.setFilterChainDefinitionMap(setFilterChainDefinitionMap());

        return shiroFilterFactoryBean;
    }

    private Map<String, String> setFilterChainDefinitionMap() {
        return FILTER_CHAIN_DEFINITION_MAP;
    }
}
View Code

2. Controller 测试类:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/test2")
    public String test2() {
        return "test2";
    }

    @GetMapping("/user/test")
    public String test1() {
        return "/user/test";
    }
}

 1. AnonymousFilter 访问原理查看

  这个过滤器是一个anon 过滤器,也就是可以匿名访问的请求。下面研究其原理。

1. 类图与源码如下:

类图:

 源码:

package org.apache.shiro.web.filter.authc;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.web.filter.PathMatchingFilter;

public class AnonymousFilter extends PathMatchingFilter {
    public AnonymousFilter() {
    }

    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
        return true;
    }
}

2. 研究其执行过程

根据继承院系以及filter 的执行原理,我们执行将断点打在org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter方法上,然后查看其执行原理。

1. org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter 源码如下:

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
        if (request.getAttribute(alreadyFilteredAttributeName) != null) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", this.getName());
            filterChain.doFilter(request, response);
        } else if (this.isEnabled(request, response) && !this.shouldNotFilter(request)) {
            log.trace("Filter '{}' not yet executed.  Executing now.", this.getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                this.doFilterInternal(request, response, filterChain);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        } else {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.", this.getName());
            filterChain.doFilter(request, response);
        }

    }

    protected String getAlreadyFilteredAttributeName() {
        String name = this.getName();
        if (name == null) {
            name = this.getClass().getName();
        }

        return name + ".FILTERED";
    }

    protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        return this.isEnabled();
    }

    protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
        return false;
    }

这里逻辑如下:

 调用 getAlreadyFilteredAttributeName() 获取一个属性名称。这个名称是作为key存在request 内部。接下来是三个分支的判断。

1》如果 request 域中有上面属性名称的值,证明已经处理过。直接让链条继续执行

2》根据isEnabled() 判断是否开启(默认是true),!shouldNotFilter() 判断该过滤器是否需要过滤该请求 (默认为false, 取反则为true)。满足这两条就走该过滤器逻辑。

  首先向request 域中放入上面属性名称,标记处理过

  然后调用this.doFilterInternal(request, response, filterChain); 让链条继续执行

  finally 代码块移除掉request 域中的信息。

3》如果上面都不满足,则直接过滤器继续执行。也就是交给下一个过滤器处理。

2. this.doFilterInternal(request, response, filterChain); 调用到org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException {
        Throwable t = null;

        try {
            final ServletRequest request = this.prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = this.prepareServletResponse(request, servletResponse, chain);
            Subject subject = this.createSubject(request, response);
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
                    AbstractShiroFilter.this.executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException var8) {
            t = var8.getCause();
        } catch (Throwable var9) {
            t = var9;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException)t;
            } else if (t instanceof IOException) {
                throw (IOException)t;
            } else {
                String msg = "Filtered request failed.";
                throw new ServletException(msg, t);
            }
        }
    }

1》 包装request 和 response 对象

    protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
        return new ShiroHttpServletRequest(orig, this.getServletContext(), this.isHttpSessions());
    }

    protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletRequest toUse = request;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest http = (HttpServletRequest)request;
            toUse = this.wrapServletRequest(http);
        }

        return toUse;
    }

    protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
        return new ShiroHttpServletResponse(orig, this.getServletContext(), request);
    }

    protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletResponse toUse = response;
        if (!this.isHttpSessions() && request instanceof ShiroHttpServletRequest && response instanceof HttpServletResponse) {
            toUse = this.wrapServletResponse((HttpServletResponse)response, (ShiroHttpServletRequest)request);
        }

        return toUse;
    }

2》创建Subject 对象

org.apache.shiro.web.servlet.AbstractShiroFilter#createSubject:

    protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
        return (new Builder(this.getSecurityManager(), request, response)).buildWebSubject();
    }

第一步创建builder:

org.apache.shiro.web.subject.WebSubject.Builder#Builder如下:

        public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
            super(securityManager);
            if (request == null) {
                throw new IllegalArgumentException("ServletRequest argument cannot be null.");
            } else if (response == null) {
                throw new IllegalArgumentException("ServletResponse argument cannot be null.");
            } else {
                this.setRequest(request);
                this.setResponse(response);
            }
        }

        protected WebSubject.Builder setRequest(ServletRequest request) {
            if (request != null) {
                ((WebSubjectContext)this.getSubjectContext()).setServletRequest(request);
            }

            return this;
        }

        protected WebSubject.Builder setResponse(ServletResponse response) {
            if (response != null) {
                ((WebSubjectContext)this.getSubjectContext()).setServletResponse(response);
            }

            return this;
        }

org.apache.shiro.subject.Subject.Builder#Builder(org.apache.shiro.mgt.SecurityManager):两个重要属性 securityManager 和 subjectContext 对象。

        public Builder(SecurityManager securityManager) {
            if (securityManager == null) {
                throw new NullPointerException("SecurityManager method argument cannot be null.");
            }
            this.securityManager = securityManager;
            this.subjectContext = newSubjectContextInstance();
            if (this.subjectContext == null) {
                throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                        "cannot be null.");
            }
            this.subjectContext.setSecurityManager(securityManager);
        }

第二步:build 创建subject org.apache.shiro.web.subject.WebSubject.Builder#buildWebSubject

        public WebSubject buildWebSubject() {
            Subject subject = super.buildSubject();
            if (!(subject instanceof WebSubject)) {
                String msg = "Subject implementation returned from the SecurityManager was not a " + WebSubject.class.getName() + " implementation.  Please ensure a Web-enabled SecurityManager has been configured and made available to this builder.";
                throw new IllegalStateException(msg);
            } else {
                return (WebSubject)subject;
            }
        }

最终调用到SecurityManager 方法org.apache.shiro.mgt.DefaultSecurityManager#createSubject(org.apache.shiro.subject.SubjectContext)

    public Subject createSubject(SubjectContext subjectContext) {
        //create a copy so we don't modify the argument's backing map:
        SubjectContext context = copy(subjectContext);

        //ensure that the context has a SecurityManager instance, and if not, add one:
        context = ensureSecurityManager(context);

        //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
        //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
        //process is often environment specific - better to shield the SF from these details:
        context = resolveSession(context);

        //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
        //if possible before handing off to the SubjectFactory:
        context = resolvePrincipals(context);

        Subject subject = doCreateSubject(context);

        //save this subject for future reference if necessary:
        //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
        //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
        //Added in 1.2:
        save(subject);

        return subject;
    }

(1) resolveSession(context); 方法会解析session,然后调用 context.setSession(session); 设置到SubjectContext 对象属性中。最终会调用到:org.apache.shiro.web.session.mgt.ServletContainerSessionManager#getSession 获取并包装session

    public Session getSession(SessionKey key) throws SessionException {
        if (!WebUtils.isHttp(key)) {
            String msg = "SessionKey must be an HTTP compatible implementation.";
            throw new IllegalArgumentException(msg);
        } else {
            HttpServletRequest request = WebUtils.getHttpRequest(key);
            Session session = null;
            HttpSession httpSession = request.getSession(false);
            if (httpSession != null) {
                session = this.createSession(httpSession, request.getRemoteHost());
            }

            return session;
        }
    }

    protected Session createSession(HttpSession httpSession, String host) {
        return new HttpServletSession(httpSession, host);
    }

  org.apache.shiro.web.session.HttpServletSession 实际是Shiro 包装的一个session类,源码如下:

package org.apache.shiro.web.session;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import javax.servlet.http.HttpSession;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpSession;

public class HttpServletSession implements Session {
    private static final String HOST_SESSION_KEY = HttpServletSession.class.getName() + ".HOST_SESSION_KEY";
    private static final String TOUCH_OBJECT_SESSION_KEY = HttpServletSession.class.getName() + ".TOUCH_OBJECT_SESSION_KEY";
    private HttpSession httpSession = null;

    public HttpServletSession(HttpSession httpSession, String host) {
        String msg;
        if (httpSession == null) {
            msg = "HttpSession constructor argument cannot be null.";
            throw new IllegalArgumentException(msg);
        } else if (httpSession instanceof ShiroHttpSession) {
            msg = "HttpSession constructor argument cannot be an instance of ShiroHttpSession.  This is enforced to prevent circular dependencies and infinite loops.";
            throw new IllegalArgumentException(msg);
        } else {
            this.httpSession = httpSession;
            if (StringUtils.hasText(host)) {
                this.setHost(host);
            }

        }
    }

    public Serializable getId() {
        return this.httpSession.getId();
    }

    public Date getStartTimestamp() {
        return new Date(this.httpSession.getCreationTime());
    }

    public Date getLastAccessTime() {
        return new Date(this.httpSession.getLastAccessedTime());
    }

    public long getTimeout() throws InvalidSessionException {
        try {
            return (long)this.httpSession.getMaxInactiveInterval() * 1000L;
        } catch (Exception var2) {
            throw new InvalidSessionException(var2);
        }
    }

    public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
        try {
            int timeout = maxIdleTimeInMillis / 1000L.intValue();
            this.httpSession.setMaxInactiveInterval(timeout);
        } catch (Exception var4) {
            throw new InvalidSessionException(var4);
        }
    }

    protected void setHost(String host) {
        this.setAttribute(HOST_SESSION_KEY, host);
    }

    public String getHost() {
        return (String)this.getAttribute(HOST_SESSION_KEY);
    }

    public void touch() throws InvalidSessionException {
        try {
            this.httpSession.setAttribute(TOUCH_OBJECT_SESSION_KEY, TOUCH_OBJECT_SESSION_KEY);
            this.httpSession.removeAttribute(TOUCH_OBJECT_SESSION_KEY);
        } catch (Exception var2) {
            throw new InvalidSessionException(var2);
        }
    }

    public void stop() throws InvalidSessionException {
        try {
            this.httpSession.invalidate();
        } catch (Exception var2) {
            throw new InvalidSessionException(var2);
        }
    }

    public Collection<Object> getAttributeKeys() throws InvalidSessionException {
        try {
            Enumeration namesEnum = this.httpSession.getAttributeNames();
            Collection<Object> keys = null;
            if (namesEnum != null) {
                keys = new ArrayList();

                while(namesEnum.hasMoreElements()) {
                    keys.add(namesEnum.nextElement());
                }
            }

            return keys;
        } catch (Exception var3) {
            throw new InvalidSessionException(var3);
        }
    }

    private static String assertString(Object key) {
        if (!(key instanceof String)) {
            String msg = "HttpSession based implementations of the Shiro Session interface requires attribute keys to be String objects.  The HttpSession class does not support anything other than String keys.";
            throw new IllegalArgumentException(msg);
        } else {
            return (String)key;
        }
    }

    public Object getAttribute(Object key) throws InvalidSessionException {
        try {
            return this.httpSession.getAttribute(assertString(key));
        } catch (Exception var3) {
            throw new InvalidSessionException(var3);
        }
    }

    public void setAttribute(Object key, Object value) throws InvalidSessionException {
        try {
            this.httpSession.setAttribute(assertString(key), value);
        } catch (Exception var4) {
            throw new InvalidSessionException(var4);
        }
    }

    public Object removeAttribute(Object key) throws InvalidSessionException {
        try {
            String sKey = assertString(key);
            Object removed = this.httpSession.getAttribute(sKey);
            this.httpSession.removeAttribute(sKey);
            return removed;
        } catch (Exception var4) {
            throw new InvalidSessionException(var4);
        }
    }
}
View Code

(2) resolvePrincipals  会解析身份信息和记住我相关信息。

(3) org.apache.shiro.mgt.DefaultSecurityManager#doCreateSubject 创建subject, 会调用到:org.apache.shiro.web.mgt.DefaultWebSubjectFactory#createSubject

    public Subject createSubject(SubjectContext context) {
        boolean isNotBasedOnWebSubject = context.getSubject() != null && !(context.getSubject() instanceof WebSubject);
        if (context instanceof WebSubjectContext && !isNotBasedOnWebSubject) {
            WebSubjectContext wsc = (WebSubjectContext)context;
            SecurityManager securityManager = wsc.resolveSecurityManager();
            Session session = wsc.resolveSession();
            boolean sessionEnabled = wsc.isSessionCreationEnabled();
            PrincipalCollection principals = wsc.resolvePrincipals();
            boolean authenticated = wsc.resolveAuthenticated();
            String host = wsc.resolveHost();
            ServletRequest request = wsc.resolveServletRequest();
            ServletResponse response = wsc.resolveServletResponse();
            return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager);
        } else {
            return super.createSubject(context);
        }
    }

  这里可以看到是获取到SubjectContext 相关属性,然后创建Subject, 相当于参数都设置到Subject。

  有一个重要的:解析是否认证wsc.resolveAuthenticated() 会调用到:org.apache.shiro.subject.support.DefaultSubjectContext#resolveAuthenticated。 可以看到实际也是从自己内部的MAP中拿属性。后面研究认证的时候查看其逻辑。

    public boolean resolveAuthenticated() {
        Boolean authc = getTypedValue(AUTHENTICATED, Boolean.class);
        if (authc == null) {
            //see if there is an AuthenticationInfo object.  If so, the very presence of one indicates a successful
            //authentication attempt:
            AuthenticationInfo info = getAuthenticationInfo();
            authc = info != null;
        }
        if (!authc) {
            //fall back to a session check:
            Session session = resolveSession();
            if (session != null) {
                Boolean sessionAuthc = (Boolean) session.getAttribute(AUTHENTICATED_SESSION_KEY);
                authc = sessionAuthc != null && sessionAuthc;
            }
        }

        return authc;
    }

(4) 最后返回去的Subject 信息如下:

 3》调用org.apache.shiro.subject.support.DelegatingSubject#execute(java.util.concurrent.Callable<V>)

    public <V> V execute(Callable<V> callable) throws ExecutionException {
        Callable<V> associated = associateWith(callable);
        try {
            return associated.call();
        } catch (Throwable t) {
            throw new ExecutionException(t);
        }
    }

    public <V> Callable<V> associateWith(Callable<V> callable) {
        return new SubjectCallable<V>(this, callable);
    }

可以看到是对Callable 对象包装之后继续调用其call 方法。实际也就是跑下面两行代码:

                    AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
                    AbstractShiroFilter.this.executeChain(request, response, chain);

包装成SubjectCallable, 其call 方法如下:

    public V call() throws Exception {
        try {
            threadState.bind();
            return doCall(this.callable);
        } finally {
            threadState.restore();
        }
    }

  可以看出是为了在调用call 之前跑threadState.bind(); 方法进行线程上下文环境的绑定。调用org.apache.shiro.subject.support.SubjectThreadState#bind:

    public void bind() {
        SecurityManager securityManager = this.securityManager;
        if ( securityManager == null ) {
            //try just in case the constructor didn't find one at the time:
            securityManager = ThreadContext.getSecurityManager();
        }
        this.originalResources = ThreadContext.getResources();
        ThreadContext.remove();

        ThreadContext.bind(this.subject);
        if (securityManager != null) {
            ThreadContext.bind(securityManager);
        }
    }

    可以看出是向ThreadLocal 绑定securityManager 和 Subject 对象

4》开始执行call 里面的方法

(1) 更新session 的最后访问时间

(2) org.apache.shiro.web.servlet.AbstractShiroFilter#executeChain 执行过滤方法

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

这里面的逻辑分为两部分.

第一部分是: 创建一个filterchain。org.apache.shiro.web.servlet.AbstractShiroFilter#getExecutionChain

    protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;
        FilterChainResolver resolver = this.getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        } else {
            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;
        }
    }
  • 获取 FilterChainResolver, 获取到的对象是org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver
  • 调用org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain 获取FilterChain
    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = this.getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        } else {
            String requestURI = this.getPathWithinApplication(request);
            if (requestURI != null && !"/".equals(requestURI) && requestURI.endsWith("/")) {
                requestURI = requestURI.substring(0, requestURI.length() - 1);
            }

            Iterator var6 = filterChainManager.getChainNames().iterator();

            String pathPattern;
            do {
                if (!var6.hasNext()) {
                    return null;
                }

                pathPattern = (String)var6.next();
                if (pathPattern != null && !"/".equals(pathPattern) && pathPattern.endsWith("/")) {
                    pathPattern = pathPattern.substring(0, pathPattern.length() - 1);
                }
            } while(!this.pathMatches(pathPattern, requestURI));

            if (log.isTraceEnabled()) {
                log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + Encode.forHtml(requestURI) + "].  Utilizing corresponding filter chain...");
            }

            return filterChainManager.proxy(originalChain, pathPattern);
        }
    }

filterChainManager.getChainNames() 获取到我们设置的路径,如下:

 然后用获取到的路径和当前请求的路径进行匹配,匹配到之后结束循环。这里也可以看出是找到一个就结束循环。匹配规则是按正则匹配。org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#pathMatches

    protected boolean pathMatches(String pattern, String path) {
        PatternMatcher pathMatcher = this.getPathMatcher();
        return pathMatcher.matches(pattern, path);
    }

这里结束循环找到的是/test2。

  • 调用org.apache.shiro.web.filter.mgt.DefaultFilterChainManager#proxy 创建代理FilterChain
    public FilterChain proxy(FilterChain original, String chainName) {
        NamedFilterList configured = this.getChain(chainName);
        if (configured == null) {
            String msg = "There is no configured chain under the name/key [" + chainName + "].";
            throw new IllegalArgumentException(msg);
        } else {
            return configured.proxy(original);
        }
    }

根据URI获取到对应的过滤器链条, 也就是NamedFilterList。 然后调用org.apache.shiro.web.filter.mgt.SimpleNamedFilterList#proxy生成代理类

    public FilterChain proxy(FilterChain orig) {
        return new ProxiedFilterChain(orig, this);
    }

org.apache.shiro.web.servlet.ProxiedFilterChain 源码如下:

package org.apache.shiro.web.servlet;

import java.io.IOException;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProxiedFilterChain implements FilterChain {
    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.");
        } else {
            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) {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }

            ((Filter)this.filters.get(this.index++)).doFilter(request, response, this);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }

            this.orig.doFilter(request, response);
        }

    }
}

   可以看到核心逻辑是生成一个shiro 自己封装的FilterChain, 这个FilterChain 包含该URI需要在shiro 内部走的filter(NamedFilterList)。 走完之后走我们交给servletContext的FilterChain , 也就是让原来servlet 环境中的filter 继续执行。

第二步: 调用chain.doFilter(request, response); 继续责任链。 实际就是调用上面shiro 代理过的FilterChain, 也就是会调用到上面org.apache.shiro.web.servlet.ProxiedFilterChain#doFilter 先走shiro 内部的filter, 然后走ServletContext 环境中的Filter, 开始下面 5》 进行执行匿名允许访问的filter; 走完之后继续 this.orig.doFilter(request, response); 执行权交给原来过滤器链。

5》 进入org.apache.shiro.web.filter.authc.AnonymousFilter, 也就是开始shiro 内部的责任链模式的调用

  • 先到达org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter, alreadyFilteredAttributeName 是 anon.FILTERED
    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
        if (request.getAttribute(alreadyFilteredAttributeName) != null) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", this.getName());
            filterChain.doFilter(request, response);
        } else if (this.isEnabled(request, response) && !this.shouldNotFilter(request)) {
            log.trace("Filter '{}' not yet executed.  Executing now.", this.getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                this.doFilterInternal(request, response, filterChain);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        } else {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.", this.getName());
            filterChain.doFilter(request, response);
        }

    }
  • 调用到org.apache.shiro.web.servlet.AdviceFilter#doFilterInternal
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        Exception exception = null;

        try {
            boolean continueChain = this.preHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
            }

            if (continueChain) {
                this.executeChain(request, response, chain);
            }

            this.postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }
        } catch (Exception var9) {
            exception = var9;
        } finally {
            this.cleanup(request, response, exception);
        }

    }
  1. this.preHandle(request, response); 调用到:org.apache.shiro.web.filter.PathMatchingFilter#preHandle。 (可以看到这里实际是验证过滤器链是否需要继续)
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        if (this.appliedPaths != null && !this.appliedPaths.isEmpty()) {
            Iterator var3 = this.appliedPaths.keySet().iterator();

            String path;
            do {
                if (!var3.hasNext()) {
                    return true;
                }

                path = (String)var3.next();
            } while(!this.pathsMatch(path, request));

            log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
            Object config = this.appliedPaths.get(path);
            return this.isFilterChainContinued(request, response, path, config);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
            }

            return true;
        }
    }

    private boolean isFilterChainContinued(ServletRequest request, ServletResponse response, String path, Object pathConfig) throws Exception {
        if (this.isEnabled(request, response, path, pathConfig)) {
            if (log.isTraceEnabled()) {
                log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}].  Delegating to subclass implementation for 'onPreHandle' check.", new Object[]{this.getName(), path, pathConfig});
            }

            return this.onPreHandle(request, response, pathConfig);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}].  The next element in the FilterChain will be called immediately.", new Object[]{this.getName(), path, pathConfig});
            }

            return true;
        }
    }

    这里实际就是对URI进行验证是否匹配,然后获取到路径上对应的配置调用isFilterChainContinued 方法验证是否满足配置。

        this.onPreHandle(request, response, pathConfig); 调用到: org.apache.shiro.web.filter.authc.AnonymousFilter#onPreHandle 永远返回true

package org.apache.shiro.web.filter.authc;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.web.filter.PathMatchingFilter;

public class AnonymousFilter extends PathMatchingFilter {
    public AnonymousFilter() {
    }

    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
        return true;
    }
}

   2. continueChain 为true, 继续调用org.apache.shiro.web.servlet.AdviceFilter#executeChain; 为false 则不进行链条的继续调用。

    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
        chain.doFilter(request, response);
    }

      可以看到是让shiro 代理FilterChain 继续执行。也就是下一个filter 继续执行, 执行完执行原来FilterChain 的链条。最后进入业务代码。

  3. 执行完成调用org.apache.shiro.web.servlet.AdviceFilter#postHandle

总结:

1. Shiro 内置filter 继承关系如下:

 2. 从OncePerRequestFilter 向后分两条线。 AbstractShiroFilter 是会注册到ServletContext 中的过滤器。 其doFilter 逻辑就是根据请求路径获取到该请求对应的shiro 过滤器, 也就是AdviceFilter 下面衍生的过滤器的实现类,然后生成一个ProxiedFilterChain(内部包含找到的Shiro中的filter和原来的FilterChain)。然后调用proxiedFilterChain.doFilter 走AdviceFilter  内部逻辑。

3. AdviceFilter 就是负责处理anon、authc 等请求的。里头也是采用责任连模式加模板模式的设计进行处理。

补充:servlet中的filterChain类是org.apache.catalina.core.ApplicationFilterChain#doFilter, 也可以查看其责任链模式的调用规则。其实也是用下标增长进行调用。

【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
原文地址:https://www.cnblogs.com/qlqwjy/p/15456018.html