前言
Shiro 已经实现的过滤器有以下几种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public enum DefaultFilter {     anon(AnonymousFilter.class),     authc(FormAuthenticationFilter.class), // 认证     authcBasic(BasicHttpAuthenticationFilter.class), // 认证     logout(LogoutFilter.class),     noSessionCreation(NoSessionCreationFilter.class),     perms(PermissionsAuthorizationFilter.class), // 授权     port(PortFilter.class), // 授权     rest(HttpMethodPermissionFilter.class), // 授权     roles(RolesAuthorizationFilter.class), // 授权     ssl(SslFilter.class), // 授权     user(UserFilter.class); 	 }
  | 
 
这里的枚举名如”anon”是这个过滤器的名字,配置过滤器链时会用到它。上面标注了“认证”的过滤器继承自AuthenticationFilter,“授权”过滤器继承自AuthorizationFilter。这两个过滤器都继承AccessControlFilter,AccessControlFilter 之上又有一系列继承(从父类到子类):
Filter(javax.servlet接口) -> AbstractFilter -> NameableFilter -> OncePerRequestFilter -> AdviceFilter -> PathMatchingFilter -> AccessControlFilter。
以下展示源码的版本是 Shiro 1.3.2。
AbstractFilter
抽象类,实现了 Filter 接口的 init 方法。扩展点包括添加FilterConfig为成员(这个类里包含 filterName 等属性)。部分源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
   | public abstract class AbstractFilter extends ServletContextSupport implements Filter {
      protected FilterConfig filterConfig;
      public FilterConfig getFilterConfig() {         return filterConfig;     }
      public void setFilterConfig(FilterConfig filterConfig) {         this.filterConfig = filterConfig;         setServletContext(filterConfig.getServletContext());     } 	 	     public final void init(FilterConfig filterConfig) throws ServletException {         setFilterConfig(filterConfig);          try {             onFilterConfigSet();          } catch (Exception e) {             if (e instanceof ServletException) {                 throw (ServletException) e;             } else {                 if (log.isErrorEnabled()) {                     log.error("Unable to start Filter: [" + e.getMessage() + "].", e);                 }                 throw new ServletException(e);             }         }     }
      protected void onFilterConfigSet() throws Exception {     } 	 	     public void destroy() {      } 	 	 }
  | 
 
NameableFilter
抽象类,未实现 Filter 接口的任何方法,扩展了一个属性name,用于表示过滤器的名字,这个名字可以外部调用 set 方法设置,如果没有设置,则来源是FilterConfig。部分源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | public abstract class NameableFilter extends AbstractFilter implements Nameable {
      private String 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;     }
  	 }
  | 
 
OncePerRequestFilter
抽象类,实现了 Filter 接口的 doFilter 方法,方法内有控制同一个过滤器只执行一次,且提供了过滤器的开关(默认开启)。在这个方法内调用doFilterInternal方法(抽象方法,由子类实现)真正执行过滤器的逻辑。
AdviceFilter
抽象类,实现了doFilterInternal方法。实现方式是,在调用filterChain.doFilter()之前调用preHandle方法,之后调用postHandler,执行完整条过滤器后调用afterComplete方法。preHandle、postHandler、afterComplete 是它提供给子类的钩子,可由子类实现。部分源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
   | public abstract class AdviceFilter extends OncePerRequestFilter {
      protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {         return true;     }       @SuppressWarnings({"UnusedDeclaration"})     protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {     }
      @SuppressWarnings({"UnusedDeclaration"})     public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {     } 	     public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)             throws ServletException, IOException {         Exception exception = null;         try {             boolean continueChain = preHandle(request, response);             if (log.isTraceEnabled()) {                 log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");             }
              if (continueChain) {                  executeChain(request, response, chain);              }
              postHandle(request, response);             if (log.isTraceEnabled()) {                 log.trace("Successfully invoked postHandle method");             }         } catch (Exception e) {             exception = e;         } finally { 			             cleanup(request, response, exception);         }     } 	 	 }
  | 
 
PathMatchingFilter
抽象类,实现了直接父类 AdviceFilter 定义的三个钩子方法中的preHandle方法。在这个方法里先匹配 requestURI,匹配方法是把 requestURI 和成员Map<String, Object> appliedPaths中保存的URI进行 match。如果 match 失败,preHandle 就直接返回 true,表示去执行下一个过滤器,当前这个过滤器除了匹配URI之外什么都没做。如果 match 成功,再调用onPreHandle方法,此时 onPreHandle 的返回值就是 preHandle 的返回值,可以决定是继续执行后续的过滤器还是中断。onPreHandle 也是一个钩子方法,默认实现直接返回 true,可由子类重写。
很容易想到,在 onPreHandle 内定义的就是当前过滤器对自己匹配的URI的处理工作,如判断用户是否有权访问匹配的URI,有权访问则返回 true,可继续执行后面的过滤器,无权访问则中断。
下面介绍成员Map<String, Object> appliedPaths的含义。map key 为当前过滤器适用的URI,value 为URI对应的“配置”。比如按下面过滤器链的配置:
1 2 3 4 5 6 7
   | Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/login", "anon");  filterChainDefinitionMap.put("/login/submit", "anon"); filterChainDefinitionMap.put("/*.ico", "anon"); filterChainDefinitionMap.put("/**", "authc"); factory.setFilterChainDefinitionMap(filterChainDefinitionMap);
   | 
 
这将会创建3个 Filter 对象,分别叫 logout、anon、authc(具体的实现类见前言中的DefaultFilter)。每个过滤器的成员 appliedPaths 中的 map key 就是和它们匹配的URI,value 为 null。如”anon”过滤器的 appliedPaths 含3个元素:<”/login”, null>, <”/login/submit”, null>, <”/*.ico”, null>
如果在过滤器名字之后还加了“配置”,像这样:
1
   | filterChainDefinitionMap.put("/**", "roles[admin, user]");
  | 
 
那么”roles”过滤器的 appliedPaths 包含:<”/**”, [“admin”,”user”]>
appliedPaths 是如何添加的?见博客《SpringShiroFilter原理》中的“创建原理”一节。
AccessControlFilter
抽象类,实现了直接父类 PathMatchingFilter 的onPreHandle方法,实现代码是:
1 2 3
   | public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { 	return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }
  | 
 
AccessControlFilter 新定义了两个方法:isAccessAllowed和onAccessDenied,但未实现。
onPreHandle 方法的含义是,先执行isAccessAllowed方法判断是否可继续(可访问),若可继续(isAccessAllowed=true),则 onPreHandle=true。若不可继续,则调用onAccessDenied处理不可访问的情况,处理完可返回 true or false。onAccessDenied=true 说明可执行后面的过滤器,onAccessDenied=false 则中断过滤器的执行直接返回响应(如 AdviceFilter 中所述)。
除此以外,AccessControlFilter 还增加了对 loginUrl 的识别和处理,以及获取Subject对象的方法,定义它的子类时可使用这些方法,例如:
1 2 3 4 5 6 7 8 9
   |  protected Subject getSubject(ServletRequest request, ServletResponse response) { 	return SecurityUtils.getSubject(); }
 
  protected boolean isLoginRequest(ServletRequest request, ServletResponse response) { 	return pathsMatch(getLoginUrl(), request); }
 
  | 
 
AuthenticationFilter 和 AuthenticatingFilter
2个都属于认证过滤器,都是抽象类。继承链(从子类到父类)是:AuthenticatingFilter -> AuthenticationFilter -> AccessControlFilter。这两个类都只实现了 isAccessAllowed 方法(如下源码),未实现 onAccessDenied 方法。
1 2 3 4 5 6 7 8 9 10 11
   |  protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { 	Subject subject = getSubject(request, response); 	return subject.isAuthenticated(); }
 
  protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { 	return super.isAccessAllowed(request, response, mappedValue) || 			(!isLoginRequest(request, response) && isPermissive(mappedValue)); }
 
  | 
 
考虑到这是一个用于认证的过滤器,就很好理解上面 AuthenticatingFilter.isAccessAllowed() 方法的含义:若用户已登录,或 requestURI 不是 loginUrl 且 requestURI 带有”permissive”配置,则当前过滤器结束,继续执行后续过滤器。否则进入 onAccessDenied 方法内执行认证。
AuthenticatingFilter 与父类相比,扩展了更多和登录有关的方法。
自定义的认证过滤器一般继承于它们,需要实现 onAccessDenied 方法,方法内实现用户登录校验等逻辑。
AuthorizationFilter
授权过滤器,抽象类,继承自 AccessControlFilter,实现了 onAccessDenied 方法(如下源码),代表不可访问后的处理,未实现 isAccessAllowed 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   |  protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
       Subject subject = getSubject(request, response);            if (subject.getPrincipal() == null) {          saveRequestAndRedirectToLogin(request, response);      } else {
           String unauthorizedUrl = getUnauthorizedUrl();
           if (StringUtils.hasText(unauthorizedUrl)) {              WebUtils.issueRedirect(request, response, unauthorizedUrl);          } else {              WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);          }      }      return false;  }
  | 
 
自定义的授权过滤器一般继承于它,需要实现 isAccessAllowed 方法,方法内判断 requestURI 是否可访问。
LogoutFilter
继承自 AdviceFilter,只实现了 preHandle 方法。如下:
1 2 3 4 5 6 7 8 9 10 11 12
   | @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {     Subject subject = getSubject(request, response);     String redirectUrl = getRedirectUrl(request, response, subject);      try {         subject.logout();      } catch (SessionException ise) {         log.debug("Encountered session exception during logout.  This can generally safely be ignored.", ise);     }     issueRedirect(request, response, redirectUrl);      return false;  }
   | 
 
给 URL=”/logout” 配置这个 Filter,就能在用户点击“退出登录”时进入这个 Filter,完成退出以及退出后跳转页面的操作,不需要自己写用户退出时的代码。
另外,退出后跳转页面默认URL=”/“,如果要自定义,则需要这样配置:
1 2 3 4 5 6 7 8 9 10 11
   | LogoutFilter logoutFilter = new LogoutFilter(); logoutFilter.setRedirectUrl("/login"); 
  Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>(); filtersMap.put("logout", logoutFilter);  factory.setFilters(filtersMap); 
 
  Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/logout", "logout");
 
   |