authc过滤器 shiro_shiro原理之过滤器

本文详细介绍了Apache Shiro框架的过滤器工作原理,从ApplicationFilterChain的doFilter方法开始,剖析了请求在过滤器链中的执行过程。文中还探讨了Shiro如何注册和匹配过滤器,以及自定义过滤器的配置方法,帮助读者深入理解Shiro的过滤器机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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() {

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

我们可以看到 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 filterMap = shiroFilterFactoryBean.getFilters();

filterMap.put("hasToken", accessTokenFilter());

shiroFilterFactoryBean.setFilters(filterMap);

/**

* anon:匿名用户可访问

* authc:认证用户可访问ShiroFilterFactoryBean

* user:使用rememberMe可访问

* perms:对应权限可访问

* role:对应角色权限可访问

**/

// URL的过滤

Map 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 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 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 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 chains = this.getFilterChainDefinitionMap();

if (!CollectionUtils.isEmpty(chains)) {

Iterator var12 = chains.entrySet().iterator();

while(var12.hasNext()) {

Entry 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的子类,我们可以看看继承图:

SpringShiroFilter继承图

我们来看看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);

}

}

PathMatchingFilterChainResolver

返回的是一个ProxiedFilterChain的实例,该实例包含了PathMatchingFilterChainResolver所匹配出来的过滤器,如下图,匹配的是系统内置的名为anon过滤器,至于它所对应的过滤器是什么可以见上面默认过滤器的表。

getExecutionChain结果

接着,在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 filterMap = new LinkedHashMap<>();

filterMap.put("jwt",new CustomAuthenticationFilter());

bean.setFilters(filterMap);

bean.setSecurityManager(securityManager);

因为很可能像平常注入过滤器那样注入先后顺序可能会存在问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值