鲁春利的工作笔记,好记性不如烂笔头
ShiroFilter
ShiroFilter,它是在整个 Shiro Web 应用中请求的门户,所有的请求都会被 ShiroFilter 拦截并进行相应的链式处理。ShiroFilter 往上有五层,最上层是 Filter(即 javax.servlet.Filter),它是 Servlet 规范中的 Filter 接口。
1、AbstractFilter
Shiro通过抽象类对Servlet的Filter接口进行了封装,并通过继承ServletContextSupport对ServletContext也进行了封装。
package org.apache.shiro.web.servlet; // ServletContextSupport对ServletContext进行了封装 public abstract class AbstractFilter extends ServletContextSupport implements Filter { protected FilterConfig filterConfig; public FilterConfig getFilterConfig() { return filterConfig; } // 初始化 FilterConfig 与 ServletContext public void setFilterConfig(FilterConfig filterConfig) { this.filterConfig = filterConfig; // 调用ServletContextSupport的方法来封装ServletContext setServletContext(filterConfig.getServletContext()); } // 从 FilterConfig 中获取初始参数 protected String getInitParam(String paramName) { FilterConfig config = getFilterConfig(); if (config != null) { return StringUtils.clean(config.getInitParameter(paramName)); } return null; } public final void init(FilterConfig filterConfig) throws ServletException { // 初始化 FilterConfig setFilterConfig(filterConfig); try { // 在子类中实现该模板方法 onFilterConfigSet(); } catch (Exception e) { // 异常信息 } } }
Shiro对ServletContext的封装
Shiro 为了封装 ServletContext 的而提供的一个类ServletContextSupport
package org.apache.shiro.web.servlet; // public class ServletContextSupport { private ServletContext servletContext = null; public ServletContext getServletContext() { return servletContext; } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } // 其他代码略 }
2、NameableFilter
提供了Filter Name的获取方式。
package org.apache.shiro.web.servlet; public abstract class NameableFilter extends AbstractFilter implements Nameable { /** * The name of this filter, unique within an application. */ private String name; // 若成员变量 name 为空,则从 FilterConfig 中获取 Filter Name protected String getName() { if (this.name == null) { FilterConfig config = getFilterConfig(); if (config != null) { this.name = config.getFilterName(); } } return this.name; } public void setName(String name) { this.name = name; } }
每个 Filter 必须有一个名字,可通过 setName 方法设置的,如果不设置就取该 Filter 默认的名字,也就是在 web.xml 中配置的 filter-name 了。
3、OncePerRequestFilter
NameableFilter是为了让每个 Filter 有一个名字,而且这个名字必须是唯一的。此外,在 shiro.ini 的 [urls] 片段的配置要求满足一定规则,例如:
[urls] /foo = ssl, authc
等号左边的是 URL,右边的是 Filter Chian,一个或多个 Filter,每个 Filter 用逗号进行分隔。
对于 /foo 这个 URL 而言,可先后通过 ssl 与 authc 这两个 Filter。如果我们同时配置了两个 ssl,这个 URL 会被 ssl 拦截两次吗?答案是否定的,因为 Shiro 为我们提供了一个“一次性Filter”的原则,也就是保证了每个请求只能被同一个 Filter 拦截一次,而且仅此一次。
package org.apache.shiro.web.servlet; public abstract class OncePerRequestFilter extends NameableFilter { /** * 已执行过的过滤器("already filtered")附加的后缀名 * * @see #getAlreadyFilteredAttributeName */ public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; /** * 是否开启过滤功能 * * @see #isEnabled() */ private boolean enabled = true; //most filters wish to execute when configured, so default to true public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 获取 Filter 已过滤的属性名 String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); // 判断是否已过滤 if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { // 若已过滤,则进入 FilterChain 中下一个 Filter filterChain.doFilter(request, response); // 若未过滤,则判断是否未开启过滤功能(其中 shouldNotFilter 方法将被废弃 } else if (!isEnabled(request, response) || shouldNotFilter(request) ) { // 若未开启,则进入 FilterChain 中下一个 Filter filterChain.doFilter(request, response); } else { // Do invoke this filter...(执行过滤) // 将已过滤属性设置为 true(只要保证 Request 中有这个属性即可) request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { // 在子类中执行具体的过滤操作 doFilterInternal(request, response, filterChain); } finally { // 当前 Filter 执行结束需移除 Request 中的已过滤属性 request.removeAttribute(alreadyFilteredAttributeName); } } } protected String getAlreadyFilteredAttributeName() { String name = getName(); if (name == null) { name = getClass().getName(); } return name + ALREADY_FILTERED_SUFFIX; } // 抽象方法,由子类实现 protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException; }
如何确保每个请求只会被同一个 Filter 拦截一次呢?Shiro 提供了一个超简单的解决方案:在 Requet 中放置一个后缀为 .FILTERED 的属性,在执行具体拦截操作(即 doFilterInternal 方法)之前放入该属性,执行完毕后移除该属性。
在 Shiro 的 Filter Chian 配置中,如果我们想禁用某个 Filter,如何实现呢?OncePerRequestFilter 也为我们提供了一个 enabled 的属性,方便我们可以在 shiro.ini 中随时禁用某个 Filter,例如:
[main] ssl.enabled = false [urls] /foo = ssl, authc
这样一来 ssl 这个 Filter 就被我们给禁用了,以后想开启 ssl 的话,完全不需要在 urls配置中一个个手工来添加,只需把 ssl.enabled 设置为 true,或注释掉该行,或直接删除该行即可。
可见,OncePerRequestFilter 给我们提供了一个模板方法doFilterInternal,在其子类中我们需要实现该方法的具体细节,那么谁来实现呢?
3.1、AbstractShiroFilter
package org.apache.shiro.web.servlet; /** * 确保可通过 SecurityUtils 获取 SecurityManager,并执行过滤器操作 */ public abstract class AbstractShiroFilter extends OncePerRequestFilter { // 是否可以通过 SecurityUtils 获取 SecurityManager private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled"; // Reference to the security manager used by this filter private WebSecurityManager securityManager; // Used to determine which chain should handle an incoming request/response private FilterChainResolver filterChainResolver; private boolean staticSecurityManagerEnabled; protected AbstractShiroFilter() { this.staticSecurityManagerEnabled = false; } public WebSecurityManager getSecurityManager() { return securityManager; } public void setSecurityManager(WebSecurityManager sm) { this.securityManager = sm; } public FilterChainResolver getFilterChainResolver() { return filterChainResolver; } public void setFilterChainResolver(FilterChainResolver filterChainResolver) { this.filterChainResolver = filterChainResolver; } // 在AbstractFilter.ini()中调用子类的实现 protected final void onFilterConfigSet() throws Exception { //added in 1.2 for SHIRO-287: // 从 web.xml 中读取 staticSecurityManagerEnabled 参数(默认为 false) applyStaticSecurityManagerEnabledConfig(); // 初始化(在子类中实现) init(); // 确保 SecurityManager 必须存在 ensureSecurityManager(); //added in 1.2 for SHIRO-287: // 若已开启 static 标志,则将当前的 SecurityManager 放入 SecurityUtils 中,以后可以随时获取 if (isStaticSecurityManagerEnabled()) { SecurityUtils.setSecurityManager(getSecurityManager()); } } public void init() throws Exception { // 空方法体 } // OncePerRequestFilter.doFilter 时需要执行的方法 protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)throws ServletException, IOException { Throwable t = null; try { // 通过ShiroHttpServletRequest对ServletRequest进行了封装 final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain); // 通过ShiroHttpServletResponse对ServletResponse进行了封装 final ServletResponse response = prepareServletResponse(request, servletResponse, chain); // 创建 Shiro 的 Subject 对象(WebSubject) final Subject subject = createSubject(request, response); // 使用异步的方式执行相关操作 //noinspection unchecked subject.execute(new Callable() { public Object call() throws Exception { // 更新 Session 的最后访问时间 updateSessionLastAccessTime(request, response); // 执行 Shiro 的 Filter Chain executeChain(request, response, chain); return null; } }); } catch (ExecutionException ex) { // 异常处理 } catch (Throwable throwable) { // 异常处理 } if (t != null) { // 异常处理 } } protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException { // 获取 Shiro 代理后的 FilterChain 对象,并进行链式处理 FilterChain chain = getExecutionChain(request, response, origChain); chain.doFilter(request, response); } protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) { FilterChain chain = origChain; // 在ShiroFilter的init方法中设置FilterChainResolver FilterChainResolver resolver = getFilterChainResolver(); if (resolver == null) { log.debug("No FilterChainResolver configured. Returning original FilterChain."); return origChain; } // 通过 FilterChainResolver(PathMatchingFilterChainResolver) 获取 ProxiedFilterChain FilterChain resolved = resolver.getChain(request, response, origChain); if (resolved != null) { log.trace("Resolved a configured FilterChain for the current request."); chain = resolved; } else { log.trace("No FilterChain configured for the current request. Using the default."); } return chain; } }
3.1.1、ShiroFilter
package org.apache.shiro.web.servlet; public class ShiroFilter extends AbstractShiroFilter { @Override public void init() throws Exception { // 从 ServletContext 中获取 WebEnvironment(该对象已通过 EnvironmentLoader 创建) // 实际实现为:通过ServletContext获取到web.xml文件中定义的shiroEnvironmentClass WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext()); // IniWebEnvironment.init会生成SecurityManager,在getWebSecurityManager时直接获取到 setSecurityManager(env.getWebSecurityManager()); // 通过IniFilterChainResolverFactory.createDefaultInstance获取PathMatchingFilterChainResolver FilterChainResolver resolver = env.getFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } } }
在 ShiroFilter 中只用做初始化的行为,就是从 WebEnvironment 中分别获取 WebSecurityManager与FilterChainResolver,其它的事情都由它的父类去实现了。
实际上ShiroFilter 还实现了一些其他的封装,例如:
通过 ShiroHttpServletRequest 来包装 Request
通过 ShiroHttpServletResponse 来包装 Response
通过 Session 来代理 HttpSession
提供 FilterChain 的代理机制
使用 ThreadContext 来保证线程安全