若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)

本文章转载于公众号:王清江唷,仅用于学习和讨论,如有侵权请联系

QQ交流群:298405437

本人QQ:4206359  具体视频地址:8 跑后端_哔哩哔哩_bilibili

1、直接看阮一峰的JWT

阮老师讲得很好了,网址如下:

http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

2.ry是怎么践行JWT的呢?

问题一:不登录的时候有token吗?

答:没有,所以只能在login页面,凡是想跳转其他界面,都被重定向到登录,硬生生让你登录。前端阻拦的代码如下:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第1张图片

问题二:token什么时候生成的?

答:登录的时候生成的,具体代码讲述:

在login控制器的service层代码中,有login方法:

 public String login(String username, String password, String code, String uuid)
{
       boolean captchaOnOff = configService.selectCaptchaOnOff();
       // 验证码开关
       if (captchaOnOff)
       {
           validateCaptcha(username, code, uuid);
       }
       // 用户验证
       Authentication authentication = null;
       try
       {
           // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
           authentication = authenticationManager
                   .authenticate(new UsernamePasswordAuthenticationToken(username, password));
       }
       catch (Exception e)
       {
           if (e instanceof BadCredentialsException)
           {
               AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
               throw new UserPasswordNotMatchException();
           }
           else
           {
               AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
               throw new ServiceException(e.getMessage());
           }
       }
       AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
       LoginUser loginUser = (LoginUser) authentication.getPrincipal();
       recordLoginInfo(loginUser.getUserId());
       // 生成token
       return tokenService.createToken(loginUser);

 }

最后一句话,他说生成token,于是他执行:

    public String createToken(LoginUser loginUser){        String token = IdUtils.fastUUID();        loginUser.setToken(token);        setUserAgent(loginUser);        refreshToken(loginUser);
        Map claims = new HashMap<>();        claims.put(Constants.LOGIN_USER_KEY, token);        return createToken(claims);    }

代码片段:可切换语言,无法单独设置文字格式

然后它又是最后一个话在生成token,好家伙,玩我是吧?点进去我们才看到真的生成方法:

    private String createToken(Map claims){        String token = Jwts.builder()                .setClaims(claims)                .signWith(SignatureAlgorithm.HS512, secret).compact();        return token;    }

代码片段:可切换语言,无法单独设置文字格式

现在token生成好了,大家要注意,refreshToken方法已经把当前成功登录的人的信息存到了redis中,前缀是login_tokens: + 当前的tokenId,tokenId是一个uuid。

所以再看login方法,不仅仅生成了token,还把登录人的信息存到了Redis中。方便下次用户带着token来的时候,后端可以拿到tokenId来redis中找用户的tokenId是否存在。

问题三,用户登录后,发请求是怎么自动带上token的?

登录成功的时候,前端存了一份token在cookie中,登录代码如下:

    // 登录    Login({ commit }, userInfo) {      const username = userInfo.username.trim()      const password = userInfo.password      const code = userInfo.code      const uuid = userInfo.uuid      return new Promise((resolve, reject) => {        login(username, password, code, uuid).then(res => {          setToken(res.token)          commit('SET_TOKEN', res.token)          resolve()        }).catch(error => {          reject(error)        })      })    },

代码片段:可切换语言,无法单独设置文字格式

引入眼帘有一个setToken,里面就将token放cookie,代码如下:

export function setToken(token) {  return Cookies.set(TokenKey, token)}

代码片段:可切换语言,无法单独设置文字格式

接下来如果要发请求,request.js会自动带上,如下代码:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第2张图片

可以看到,它会去config.headers看看到底要不要,如果我们在请求头指定了不要,那么发请求就不会带上token,否则就会被带上token。

问题四:我再次请求的时候带上了token,后端在哪问我带没带token呢?

其实后端有一个拦截器,总是在悄悄检查。

如下代码:

package com.ruoyi.framework.security.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;

/**
* token过滤器 验证token有效性
*
* @author ruoyi
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
   @Autowired
   private TokenService tokenService;
   @Override
   protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
           throws ServletException, IOException
{
       LoginUser loginUser = tokenService.getLoginUser(request);
       if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
       {
           tokenService.verifyToken(loginUser);
           UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
           authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
           SecurityContextHolder.getContext().setAuthentication(authenticationToken);
       }
       chain.doFilter(request, response);
   }
 }
import com.ruoyi.framework.web.service.TokenService;

代码片段:可切换语言,无法单独设置文字格式

getLoginUser方法总是在试图拿到token。然后验证token是否正确,token有没有过期,然后把用户该有的权限重新设置在上下文中。

该拦截器是在哪里设置的呢?

    @Override
   protected void configure(HttpSecurity httpSecurity) throws Exception
{
       // 注解标记允许匿名访问的url
       ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
       permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
       httpSecurity
               // CSRF禁用,因为不使用session
               .csrf().disable()
               // 认证失败处理类
               .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
               // 基于token,所以不需要session
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
               // 过滤请求
               .authorizeRequests()
               // 对于登录login 注册register 验证码captchaImage 允许匿名访问
               .antMatchers("/login", "/register", "/captchaImage").anonymous()
               // 静态资源,可匿名访问
               .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
               .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
               // 除上面外的所有请求全部需要鉴权认证
               .anyRequest().authenticated()
               .and()
               .headers().frameOptions().disable();
       // 添加Logout filter
       httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
       // 添加JWT filter
       httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
       // 添加CORS filter
       httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
       httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);

 }

代码片段:可切换语言,无法单独设置文字格式

3、ry认证出现问题如何处理的?

认证配置类中最重要的HttpSecurity配置如下:

    @Override
   protected void configure(HttpSecurity httpSecurity) throws Exception
{
       // 注解标记允许匿名访问的url
       ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
       permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
       httpSecurity
               // CSRF禁用,因为不使用session
               .csrf().disable()
               // 认证失败处理类
               .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
               // 基于token,所以不需要session
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
               // 过滤请求
               .authorizeRequests()
               // 对于登录login 注册register 验证码captchaImage 允许匿名访问
               .antMatchers("/login", "/register", "/captchaImage").anonymous()
               // 静态资源,可匿名访问
               .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
               .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
               // 除上面外的所有请求全部需要鉴权认证
               .anyRequest().authenticated()
               .and()
               .headers().frameOptions().disable();
       // 添加Logout filter
       httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
       // 添加JWT filter
       httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
       // 添加CORS filter
       httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
       httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);

}

在SecurityConfig类中明确指定了认证失败后handle:

.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)

也就是如下的方式来处理:

package com.ruoyi.framework.security.handle;
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.ServletUtils;

/**
* 认证失败处理类 返回未授权
*
* @author ruoyi
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
   private static final long serialVersionUID = -8970718410437077606L;
   @Override
   public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
           throws IOException
{
       int code = HttpStatus.UNAUTHORIZED;
       String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
       ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
   }
}
import com.ruoyi.common.utils.StringUtils;

可以看到如果认证失败就简单返回一句话【请求访问:{},认证失败,无法访问系统资源】。

通过postman或apifox等发请求的软件,随便给后端发一个请求,很容易就能得到一个这样的返回:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第3张图片

3.1新问题:postman如何调接口?

现在有一个这样的问题,遇到认证失败,但是自己想用postman、apifox、apipost等软件就想要调通API,怎么办?

现在介绍一种方法,因为超级管理员是无视一切权限的,只要用上超级管理员的token,一切权限都能轻轻松松越过去。下面演示apipost如何使用超级管理员的token。

首先拿到token【视频说】,然后放到如下位置:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第4张图片

然后就能调通了,如下图:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第5张图片

大家可能会问,为啥404,因为abc这个接口本身就没有写,所以404,但凡调用一个写好的接口,就能调通,如下:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第6张图片

然而,如果用上了超级管理员token,一切查询方面的结果都是和超级管理员有关的,有时候可能并不是自己想要的,这就需要给授权,后面再说。

3.2 Swagger好像也调不通

swagger也不通,如下:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第7张图片

也是因为没有权限,如下方式加上token就能调通:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第8张图片

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第9张图片

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第10张图片

4、深入认证源码过滤器链

4.1手动修改过滤器链和认识addFilterAfter方法

4.1.1 删除多余的一个解决跨域过滤器

我们知道,SpringSecurity的机制无非就是过滤器链,一个一个执行过滤器,当前ry的过滤器链如下:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第11张图片

这里我先看cors(处理跨域)的过滤器有两个,但是处理跨域的过滤器只会执行一次(因为继承了OncePerRequestFilter),所以没有加入两个解决跨域过滤器的必要。所以我删除了一个,我怎么删除的呢?我直接在ry添加两个过滤器的地方注释了一个:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第12张图片

从上面的红色框可以看出,我就是删除了在JwtAuthenticationTokenFilter过滤器前面的一个cors过滤器。

addFilterBefore(参数一,参数二)方法的功能是添加一个过滤器,并且添加到参数二过滤器之前。

当我删除了一个跨域过滤器之后,新的过滤器链如下:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第13张图片

4.1.2新方法:addFilterAfter

//        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
 httpSecurity.addFilterAfter(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

刚刚说到addFilterBefore是在添加到指定过滤器之前,而addFilterAfter相反,添加到指定过滤器之后。

通过addFilterAfter和addFilterBefore,我们可以控制additionalFilters过滤器链每一个过滤器的顺序。

看下面的图:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第14张图片

眼尖的同学已经发现,其实上面几行,我已经用过了addFilterAfter方法,然而,这里虽然把addFilterBefore改成了addFilterAfter,但是其实结果还是一样的。

因为没有启用表单认证所以UsernamePasswordAuthenticationFilter被移除了。UsernamePasswordAuthenticationFilter自然也不起作用了,相当于我们刚刚的addFilterAfter其实就是取代了UsernamePasswordAuthenticationFilter的位置,由authenticationTokenFilter(JwtAuthenticationTokenFilter)实例来完成认证。

4.2安全过滤器链条的每个过滤器作用

Tips:SpringSecurity成功认证的标志是上下文存储着经过认证的用户信息,像下面这样:

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);

当前过滤器链:

若依的使用(JWT,ry是怎么践行JWT的呢?,ry认证出现问题如何处理的?,深入认证源码过滤器链)_第15张图片

共12个过滤器。

分别是:

1、WebAsyncManagerIntegrationFilter,用于将SecurityContext传递到异步线程中(异步线程就可以获取安全上下文)。

2、SecurityContextPersistenceFilter,它主要是使用SecurityContextRepository在session中保存或更新一个SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续filter建立所需的上下文。

3、HeaderWriterFilter,它用于向请求的Header中添加相应的信息。

4.3 CorsFilter,它用于处理跨域,ry有自定义此过滤器,如下:

文件ResourcesConfig.java​​​​​​​

    /**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter()
{
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);    

上面的配置可以让前端请求基本上是随便跨域了。ry把此过滤器设置了两次,一次在LogoutFilter.class之前,一次在JwtAuthenticationTokenFilter.class之前,但是被我删除了JwtAuthenticationTokenFilter.class前面的,但是执行的效果还是一样的。

5、LogoutFilter,用于处理注销主体。

LogoutFilter会去执行两个handler,分别是SecurityContextLogoutHandler和LogoutSuccessEventPublishingLogoutHandler。SecurityContextLogoutHandler用于清除上下文认证信息。

做的一些关键代码如下:​​​​​​​

    if (this.clearAuthentication) {      SecurityContext context = SecurityContextHolder.getContext();      context.setAuthentication(null);    }    SecurityContextHolder.clearContext();

LogoutSuccessEventPublishingLogoutHandler啥都没干,如果authentication已经是Null的情况下,正好上一个handler已经将authentication清理了,所以可以理解LogoutSuccessEventPublishingLogoutHandler啥都没做。如果前一个handler执行出现问题,本handler也可以理解为一种补偿机制,发布一个事件说authentication已经登出了,相关代码如下:​​​​​​​

 @Override
 public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
   if (this.eventPublisher == null) {
     return;
   }
   if (authentication == null) {
     return;
   }
   this.eventPublisher.publishEvent(new LogoutSuccessEvent(authentication));

}

ry对成功注销做了自定

实现:​​​​​​​

/**
* 自定义退出处理类 返回成功
*
* @author ruoyi
*/
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
   @Autowired
   private TokenService tokenService;
   /**
    * 退出处理
    *
    * @return
    */
   @Override
   public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
           throws IOException, ServletException
{
       LoginUser loginUser = tokenService.getLoginUser(request);
       if (StringUtils.isNotNull(loginUser))
       {
           String userName = loginUser.getUsername();
           // 删除用户缓存记录
           tokenService.delLoginUser(loginUser.getToken());
           // 记录用户退出日志
           AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
       }
       ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));

 }

​​​​​​​​​​​​​​ }

无非就是在退出的时候做一下收尾工作,清缓存,写登出日志,最后再返回一个退出成功的信息。

4.4 JwtAuthenticationTokenFilter,用于认证用户。

当用户登陆成功之后,发请求会带上token,JwtAuthenticationTokenFilter是专门用于解析token并把认证成功的用户信息设置到上下文中,用于后续的鉴权(认证成功的LoginUser里面有字段里面带着用户的权限的)。相关代码如下:​​​​​​​

/** * token过滤器 验证token有效性 *  * @author ruoyi */@Componentpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter{    @Autowired    private TokenService tokenService;
    @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}
}

7、RequestCacheAwareFilter,通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest。

8、SecurityContextHolderAwareRequestFilter,针对ServletRequest进行了一次包装,使得request具有更加丰富的API。

通过SecurityContextHolderAwareRequestWrapper把ServletRequest包装起来,多了一些方法如getUserPrincipal,getAuthentication方便后续的确认是否目前已经认证。

因为在过滤器链条上面request和response一直在链条上传输,这也是包装request的原因。

9、AnonymousAuthenticationFilter,当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。SpringSecurity为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

10、SessionManagementFilter,SecurityContextRepository限制同一用户开启多个会话的数量,但是前后端分离往往会禁用session,改用Redis存储用户会话状态。

11、ExceptionTranslationFilter,用于处理异常,它的过滤器方法直接放行,但是catch了许多异常例如认证过程的异常,如下:​​​​​​​

  private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
     throws IOException, ServletException {
   try {
     chain.doFilter(request, response);
   }
   catch (IOException ex) {
     throw ex;
   }
   catch (Exception ex) {
     // Try to extract a SpringSecurityException from the stacktrace
     Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
     RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
         .getFirstThrowableOfType(AuthenticationException.class, causeChain);
     if (securityException == null) {
       securityException = (AccessDeniedException) this.throwableAnalyzer
           .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
     }
     if (securityException == null) {
       rethrow(ex);
     }
     if (response.isCommitted()) {
       throw new ServletException("Unable to handle the Spring Security Exception "
           + "because the response is already committed.", ex);
     }
     handleSpringSecurityException(request, response, chain, securityException);
   }

}

12、FilterSecurityInterceptor,获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限,然而ry自创了一套鉴权方式,所以原生FilterSecurityInterceptor这里不做展开。

最后:

这些过滤器由一个代理管理,从上依次往下执行。下面我来来看一下代理(FilterChainProxy.VirtualFilterChain)的关键性代码:​​​​​​​

   @Override
   public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
     if (this.currentPosition == this.size) {
       ...省略非关键代码
       // Deactivate path stripping as we exit the security filter chain
       this.firewalledRequest.reset();
       this.originalChain.doFilter(request, response);
       return;
     }
     this.currentPosition++;
     Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
     ...省略非关键代码
     nextFilter.doFilter(request, response, this);

}

从上面的代码可以看得出,有一个名为additionalFilters的ArrayList存储了12个过滤器,一个一个依次执行,执行完毕之后继续执行originalChain里面的过滤器。

你可能感兴趣的:(javaweb,java,开发语言)