前言
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");
|