• 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, 也可以查看其责任链模式的调用规则。其实也是用下标增长进行调用。

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    mycat 1.6.6.1 distinct报错问题
    linux下Tomcat+OpenSSL配置单向&双向认证(自制证书)
    Too many open files错误与解决方法
    Tomcat类加载机制触发的Too many open files问题分析(转)
    spring boot 自签发https证书
    redis集群如何解决重启不了的问题
    centos7 docker 安装 zookeeper 3.4.13 集群
    centos7用docker安装kafka
    心怀感恩
    不使用if switch 各种大于 小于 判断2个数的大小
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/15456018.html
Copyright © 2020-2023  润新知