Shiro原理-过滤器
前言
这几天一直在研究Shiro到底是如何工作的,即一个请求过来了,它是如何做到知道这个请求应该用什么方式来鉴权的?应该调用哪个过滤器?自己定义的过滤器该如何才能生效?
带着这样的疑问,我做了一些测试与研究,并记录于此文。
实现原理
Shiro对于请求的鉴权的实现也是通过过滤器(或者说是拦截器)来实现的,但是Spring项目中有拦截链机制,会有多个拦截器生效,包括系统内置的以及Shiro注入的,所以需要搞懂他的过滤的实现机制就需要去弄明白这些过滤器是如何过滤的。
那就开始吧
ApplicationFilterChain 简介
Tomcat
的类ApplicationFilterChain
是一个Java Servlet API
规范javax.servlet.FilterChain
的实现,用于管理某个请求request
的一组过滤器Filter
的执行。当针对一个request
所定义的一组过滤器Filter
处理完该请求后,最后一个doFilter()
调用才会执行目标Servlet
的方法service()
,然后响应对象response
会按照相反的顺序依次被这些Filter
处理,最终到达客户端。
在ApplicationFilterChain
的doFilter
方法下打上断点
// org.apache.catalina.core.ApplicationFilterChain.java
// 执行过滤器链中的下一个过滤器Filter。如果链中所有过滤器都执行过,则调用servlet的service()方法。
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// 这个if-else分支主要是根据Globals.IS_SECURITY_ENABLED是true还是false决定
// 如何调用目标逻辑,但两种情况下,目标逻辑最终都是 internalDoFilter(req,res)
if (Globals.IS_SECURITY_ENABLED) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws ServletException, IOException {
// 调用internalDoFilter
ApplicationFilterChain.this.internalDoFilter(req, res);
return null;
}
});
} catch (PrivilegedActionException var7) {
Exception e = var7.getException();
if (e instanceof ServletException) {
throw (ServletException)e;
}
if (e instanceof IOException) {
throw (IOException)e;
}
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
}
throw new ServletException(e.getMessage(), e);
}
} else {
// 调用internalDoFilter
this.internalDoFilter(request, response);
}
}
我们可以看到 filters中包含了5个过滤器:
CharacterEncodingFilter:spring内置过滤器,用来指定请求或者响应的编码格式。
FormContentFilter:该过滤器针对
DELETE
,PUT
和PATCH
这三种HTTP method
分析其FORM
表单参数,将其暴露为Servlet
请求参数。RequestContextFilter:该过滤器将当前请求暴露到当前线程。
SpringShiroFilter:shiro内置过滤器,包装 Request 和 Response,使它们由原来的 HttpServlet 系列包装为 ShiroHttpServletRequest等。
Tomcat WebSocket Filter:webSocket 相关过滤器。
这些注入的过滤器会通过
internalDoFilter
来执行过滤工作,如下:
// org.apache.catalina.core.ApplicationFilterChain.java
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.pos < this.n) {
// 如果过滤链中还有过滤器需要过滤
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try {
// 找到目标的Filter
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
// 执行目标 Filter 对象的 doFilter方法,
// 注意,这里当前ApplicationFilterChain对象被传递到了目标
// Filter对象的doFilter方法,而目标Filter对象的doFilter在执行完自己
// 被指定的逻辑之后会反过来调用这个ApplicationFilterChain对象的
// doFilter方法,只是pos向前推进了一个过滤器。这个ApplicationFilterChain
// 和Filter之间反复调用彼此doFilter方法的过程一直持续直到当前链发现所有的
// Filter都已经被执行
if (Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response, this};
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (ServletException | RuntimeException | IOException var15) {
throw var15;
} catch (Throwable var16) {
Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
} else {
// 调用servlet的service()方法
// We fell off the end of the chain -- call the servlet instance
// 这里是过滤器链中所有的过滤器都已经被执行的情况,现在需要调用servlet实例本身了。
// !!! 注意 : 虽然这里开始调用servlet实例了,但是从当前方法执行堆栈可以看出,过滤器链
// 和链中过滤器的doFilter方法的执行帧还在堆栈中并未退出,他们会在servlet实例的逻辑
// 执行完后,分别执行完自己剩余的的逻辑才会逐一结束。
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !this.servletSupportsAsync) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response};
SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal);
} else {
this.servlet.service(request, response);
}
} catch (ServletException | RuntimeException | IOException var17) {
throw var17;
} catch (Throwable var18) {
Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set((Object)null);
lastServicedResponse.set((Object)null);
}
}
}
}
Shiro Filter 注册
Shiro 中对请求的配置需要在Shiro的配置文件中配置的,可以在这里添加自定义的过滤器以及配置相关的URL过滤信息,而ShiroFilter是在ShiroFilterFactoryBean中创建的,所以我们首先需要配置注入好ShiroFilterFactoryBean。
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
// 自定义过滤器
Map<String, Filter> filterMap = shiroFilterFactoryBean.getFilters();
filterMap.put("hasToken", accessTokenFilter());
shiroFilterFactoryBean.setFilters(filterMap);
/**
* anon:匿名用户可访问
* authc:认证用户可访问ShiroFilterFactoryBean
* user:使用rememberMe可访问
* perms:对应权限可访问
* role:对应角色权限可访问
**/
// URL的过滤
Map<String, String> filterChainMap = new LinkedHashMap<>();
// 登录接口开放
filterChainMap.put("/auth/login", "anon");
// 获取用户信息需要认证用户
filterChainMap.put("/user/**", "authc");
...
bean.setFilterChainDefinitionMap(filterChainMap);
return bean;
}
Filter注入
默认过滤器
public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
authcBearer(BearerHttpAuthenticationFilter.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);
...
}
Filter Name | Class |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
// DefaultFilterChainManager
// 加载默认过滤器
protected void addDefaultFilters(boolean init) {
DefaultFilter[] var2 = DefaultFilter.values();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
DefaultFilter defaultFilter = var2[var4];
this.addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
}
}
// org.apache.shiro.spring.web.ShiroFilterFactoryBean
// 创建实体的方法中调用了加载过滤器链的方法createFilterChainManager
protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
SecurityManager securityManager = this.getSecurityManager();
String msg;
if (securityManager == null) {
msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
} else if (!(securityManager instanceof WebSecurityManager)) {
msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
} else {
FilterChainManager manager = this.createFilterChainManager();
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
return new ShiroFilterFactoryBean.SpringShiroFilter((WebSecurityManager)securityManager, chainResolver);
}
}
// 匿名内部类 SpringShiroFilter
private static final class SpringShiroFilter extends AbstractShiroFilter {
protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
if (webSecurityManager == null) {
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
} else {
this.setSecurityManager(webSecurityManager);
if (resolver != null) {
this.setFilterChainResolver(resolver);
}
}
}
}
// 创建过滤器链
protected FilterChainManager createFilterChainManager() {
// 创建DefaultFilterChainManager
DefaultFilterChainManager manager = new DefaultFilterChainManager();
// 先获取默认的过滤器
Map<String, Filter> defaultFilters = manager.getFilters();
Iterator var3 = defaultFilters.values().iterator();
// 对每个默认的过滤器执行applyGlobalPropertiesIfNecessary方法
// applyGlobalPropertiesIfNecessary的作用:
// - 设置customAuthenticationFilter中的loginUrl,SuccessUrl和unauthorizedUrl。
while(var3.hasNext()) {
Filter filter = (Filter)var3.next();
this.applyGlobalPropertiesIfNecessary(filter);
}
Map<String, Filter> filters = this.getFilters();
String name;
Filter filter;
// 加载自定义的过滤器,进行设置并添加到过滤器管理器DefaultFilterChainManager中
// 如果不为空的话也要执行applyGlobalPropertiesIfNecessary方法
if (!CollectionUtils.isEmpty(filters)) {
for(Iterator var10 = filters.entrySet().iterator(); var10.hasNext(); manager.addFilter(name, filter, false)) {
Entry<String, Filter> entry = (Entry)var10.next();
name = (String)entry.getKey();
filter = (Filter)entry.getValue();
this.applyGlobalPropertiesIfNecessary(filter);
if (filter instanceof Nameable) {
((Nameable)filter).setName(name);
}
}
}
// 加载URL的过滤,并调用createChain方法构造过滤链
Map<String, String> chains = this.getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
Iterator var12 = chains.entrySet().iterator();
while(var12.hasNext()) {
Entry<String, String> entry = (Entry)var12.next();
String url = (String)entry.getKey();
String chainDefinition = (String)entry.getValue();
manager.createChain(url, chainDefinition);
}
}
return manager;
}
// 构造过滤链,通过URL过滤规则
public void createChain(String chainName, String chainDefinition) {
if (!StringUtils.hasText(chainName)) {
throw new NullPointerException("chainName cannot be null or empty.");
} else if (!StringUtils.hasText(chainDefinition)) {
throw new NullPointerException("chainDefinition cannot be null or empty.");
} else {
if (log.isDebugEnabled()) {
log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
}
// 如果指定了多个过滤器
String[] filterTokens = this.splitChainDefinition(chainDefinition);
String[] var4 = filterTokens;
int var5 = filterTokens.length;
// 将所有过滤器加到过滤链中去
for(int var6 = 0; var6 < var5; ++var6) {
String token = var4[var6];
String[] nameConfigPair = this.toNameConfigPair(token);
this.addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
}
}
}
// 添加到过滤链中去
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
if (!StringUtils.hasText(chainName)) {
throw new IllegalArgumentException("chainName cannot be null or empty.");
} else {
// 根据过滤器的名字找到过滤器
Filter filter = this.getFilter(filterName);
if (filter == null) {
// 如果不存在就抛出异常
throw new IllegalArgumentException("There is no filter with name '" + filterName + "' to apply to chain [" + chainName + "] in the pool of available Filters. Ensure a filter with that name/path has first been registered with the addFilter method(s).");
} else {
this.applyChainConfig(chainName, filter, chainSpecificFilterConfig);
NamedFilterList chain = this.ensureChain(chainName);
chain.add(filter);
}
}
}
Shiro Filter 匹配
刚刚我们分析Spring Shiro的Shiro注入的时候,我们可以看到
createInstance
方法返回的是AbstractShiroFilter
的子类SpringShiroFilter
,而AbstractShiroFilter
也是OncePerRequestFilter
的子类,我们可以看看继承图:
我们来看看AbstractShiroFilter
的部分源码:
// org.apache.shiro.web.servlet.AbstractShiroFilter.java
// OncePerRequestFilter 执行 doFilter 方法时调用了该方法
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绑定到线程的subject中
subject.execute(new Callable() {
public Object call() throws Exception {
// 更新session相关信息
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);
}
}
}
// 执行过滤链,
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException {
// 获取需要执行的过滤链
FilterChain chain = this.getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
// 获取需要执行的过滤链
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
// 获取解析器,这里获取到的是 PathMatchingFilterChainResolver
FilterChainResolver resolver = this.getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
} else {
// 获取过滤链 就是在这里通过请求的url选择了相应的过滤链的
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;
}
}
// org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
// 获取Manager
FilterChainManager filterChainManager = this.getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
} else {
// 接下来就是先获取请求的url,然后去匹配过滤链,然后再返回。
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);
}
}
返回的是一个ProxiedFilterChain
的实例,该实例包含了PathMatchingFilterChainResolver
所匹配出来的过滤器,如下图,匹配的是系统内置的名为anon
过滤器,至于它所对应的过滤器是什么可以见上面默认过滤器的表。
接着,在AbstractShiroFilter
中的executeChain
就会执行它的doFilter
方法。
// org.apache.shiro.web.servlet.ProxiedFilterChain
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// 执行所代理的过滤器的doFilter方法。
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);
}
}
总结
刚开始学习看源码,IDEA的调试工具也不是很会用,好在确实IDEA很强大,不然这么多过滤链跳来跳去是真的难懂。经过这次的阅读以及网上的博客的研究,Shiro的过滤链如果有自定义的过滤链的话,一定不能像平常的拦截器那样注入,必须要在注入ShiroFilterFactoryBean
时使用如下方式注入才能生效。
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("jwt",new CustomAuthenticationFilter());
bean.setFilters(filterMap);
bean.setSecurityManager(securityManager);
因为很可能像平常注入过滤器那样注入先后顺序可能会存在问题。