之前研究了,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; } }
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); } } }
(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); } }
- 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, 也可以查看其责任链模式的调用规则。其实也是用下标增长进行调用。