Apache Shiro学习笔记(六)Shiro Filter介绍
鲁春利的工作笔记,好记性不如烂笔头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 的 片段的配置要求满足一定规则,例如:
/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,例如:
ssl.enabled = false
/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 来保证线程安全
页:
[1]