SpringBoot2.x过滤器OncePerRequestFilter(Spring内置Filter)

JAVA && Spring && SpringBoot2.x — 学习目录

SpringBoot2.x(Spring)含有内置的Filter。即OncePerRequestFilter顾名思义:仅执行一次的Filter。图1是OncePerRequestFilter的子类:

 
图1-SpringBoot2.x的OncePerRequestFilter子类关系图.png

在Spring中,Filter默认继承OncePerRequestFilter类。来过滤请求。

1. OncePerRequestFilter存在的意义

OncePerRequestFilter是在一次外部请求中只过滤一次。对于服务器内部之间的forward等请求,不会再次执行过滤方法。

在SpringBoot2.x环境下,服务器内部转发(forward)一个请求,代码如图2所示:

 
图二-转发请求.png
  1. 自定义实现Filter接口的类,在过滤请求时打印日志。结果如图三所示:
 
图三-打印Filter请求.png

根据上图所示,实际上请求在服务器内部转发时,并未进行过滤。可以看到上,实现Filter接口,也会在一次请求中只过滤一次。

实际上,OncePerRequestFilter是为了兼容不同的web 容器,也就是说其实不是所有的容器都过滤一次。Servlet版本不同,执行的过程也不同。例如:在Servlet2.3中,Filter会过滤一切请求,包括服务器内部使用forward和<%@ include file=/login.jsp%>的情况,但是在servlet2.4中,Filter默认只会过滤外部请求。
对于异步请求(即为避免线程阻塞,需要委托另一个线程处理),也只过滤一次请求。
【小家Spring】——关于OncePerRequestFilter

源码:org.springframework.web.filter.OncePerRequestFilter#doFilter中,通过更改request中的Filter状态,防止内部请求时多次调用Filter,核心代码如图4所示。

 
图4-核心源码.png

采用的是模板方法模式,子类实现org.springframework.web.filter.OncePerRequestFilter#doFilterInternal方法。对请求进行过滤。

在Spring环境中若想使用Filter,建议继承OncePerRequestFilter,而非原生的Servlet Filter接口。

2. OncePerRequestFilter方法

OncePerRequestFilter是采用的模板方法模式,子类需要实现父类定义的钩子方法(算法逻辑父类已经实现),便可以进行过滤。

2.1 初始化方法

org.springframework.web.filter.GenericFilterBean#init中实现init方法,子类若是想执行init方法。需要实现org.springframework.web.filter.GenericFilterBean#initFilterBean默认的钩子方法,源码如图5所示。

 
图5-init模板方法模式源码.png

initFilterBean()方法,在两个地方使用到,一个是init方法中,一个是afterPropertiesSet方法(即Filter若放到Spring容器,初始化时执行该方法)。实际上会执行两次初始化方法,如图6所示:

 
图6-初始化方法会执行两次.png

SpringBoot2.x将Filter加入到容器的几种方法

2.2 过滤方法

以SpringBoot2.x自动装载的编码过滤器为例,如图7所示:

源码:org.springframework.web.filter.CharacterEncodingFilter

 
图7-CharacterEncodingFilter过滤器核心代码.png

 

源码:org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration

 
图8-SpringBoot将其加入到容器中.png

 

总结:在Spring环境中,推荐实现OncePerRequestFilter类,而非实现原生的Filter接口。

 

前两篇简单的讲了一下spring security的基本用法以及相关扩展,今天跟着我一起学习下spring security的原理吧。spring security是有一系列的过滤器组成的一条链,见下图:

其中绿色的过滤器需要满足一定的条件才会执行,其他三个颜色的过滤器一定会执行,红色的ExceptionTranslationFilter和黄色的FilterSecurityInterceptor在过滤器链中的顺序一定是倒数第二和倒数第一的位置。

本篇我们介绍几个主要的过滤器,下面一起进入到代码中,看看这四种颜色的过滤器是怎么发挥其作用的。

原理

SecurityContextPersistenceFilter

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
   throws IOException, ServletException {
 // ensure that filter is only applied once per request
 //确保这个过滤器只会执行一次
 if (request.getAttribute(FILTER_APPLIED) != null) {
  chain.doFilter(request, response);
  return;
 }
 //设置标识
 request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
 //默认情况下为false
 if (this.forceEagerSessionCreation) {
  HttpSession session = request.getSession();
  if (this.logger.isDebugEnabled() && session.isNew()) {
   this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
  }
 }
 HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
 //在当前过滤器的构造函数中创建了HttpSessionSecurityContextRepository,所以默认情况下this.repo为HttpSessionSecurityContextRepository实例,调用其loadContext方法,返回SecurityContext对象,一会再看这个方法是怎么将SecurityContext对象返回的
 SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
 try {
  //将SecurityContext放入到SecurityContextHolder中
  SecurityContextHolder.setContext(contextBeforeChainExecution);
  if (contextBeforeChainExecution.getAuthentication() == null) {
   logger.debug("Set SecurityContextHolder to empty SecurityContext");
  }
  else {
   if (this.logger.isDebugEnabled()) {
    this.logger
      .debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
   }
  }
  //调用过滤器链中的下一个过滤器
  chain.doFilter(holder.getRequest(), holder.getResponse());
 }
 finally {
  //最后当请求结束的时候,从SecurityContextHolder中获取SecurityContext对象
  SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
  // Crucial removal of SecurityContextHolder contents before anything else.
  //从SecurityContextHolder中清空当前请求绑定的SecurityContext对象
  SecurityContextHolder.clearContext();
  //调用HttpSessionSecurityContextRepository的saveContext方法
  this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
  //从请求中删除标记
  request.removeAttribute(FILTER_APPLIED);
  this.logger.debug("Cleared SecurityContextHolder to complete request");
 }
}

下面进入到HttpSessionSecurityContextRepositoryloadContext方法

public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
 HttpServletRequest request = requestResponseHolder.getRequest();
 HttpServletResponse response = requestResponseHolder.getResponse();
 HttpSession httpSession = request.getSession(false);
 //从session中获取SecurityContext对象
 SecurityContext context = readSecurityContextFromSession(httpSession);
 if (context == null) {
  context = generateNewContext();
  if (this.logger.isTraceEnabled()) {
   this.logger.trace(LogMessage.format("Created %s", context));
  }
 }
 if (response != null) {
  SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,
    httpSession != null, context);
  requestResponseHolder.setResponse(wrappedResponse);
  requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));
 }
 return context;
}

SecurityContextPersistenceFilter:会在每次请求处理之前从配置好的SecurityContextRepository中获取SecurityContext安全上下文信息,然后加载到SecurityContextHolder中,然后在该次请求处理完成之后,将SecurityContextHolder中关于这次请求的信息存储到一个“仓库”中,然后将SecurityContextHolder中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter继承AbstractAuthenticationProcessingFilter

public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
  "POST");

private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;

private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;

private boolean postOnly = true;

public UsernamePasswordAuthenticationFilter() {
 super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}

在构造器中调用父类的构造器传入AntPathRequestMatcher对象

protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
 Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");
 this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}

所以此时父类中requiresAuthenticationRequestMatcher属性是AntPathRequestMatcher对象,当请求进来的时候,会调用父类的doFilter方法

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
   throws IOException, ServletException {
 //是否需要验证,条件是请求路径为/login并且请求方法为post请求,则继续向下执行
 if (!requiresAuthentication(request, response)) {
  chain.doFilter(request, response);
  return;
 }
 try {
  //调用子类的方法,下面会讲
  Authentication authenticationResult = attemptAuthentication(request, response);
  if (authenticationResult == null) {
   // return immediately as subclass has indicated that it hasn't completed
   return;
  }
  //认证成功后session的处理策略,默认情况下什么也不做
  this.sessionStrategy.onAuthentication(authenticationResult, request, response);
  // Authentication success
  //默认情况下为false
  if (this.continueChainBeforeSuccessfulAuthentication) {
   chain.doFilter(request, response);
  }
  //成功认证后的处理,下面会将
  successfulAuthentication(request, response, chain, authenticationResult);
 }
 catch (InternalAuthenticationServiceException failed) {
  this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
  //如果认证过程中发生了异常,则调用认证失败后的处理,下面会讲
  unsuccessfulAuthentication(request, response, failed);
 }
 catch (AuthenticationException ex) {
  // Authentication failed
  unsuccessfulAuthentication(request, response, ex);
 }
}

认证成功后的逻辑处理

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
   Authentication authResult) throws IOException, ServletException {
 //创建SecurityContext对象
 SecurityContext context = SecurityContextHolder.createEmptyContext();
 //将认证成功的Authentication对象保存到SecurityContext安全上下文中
 context.setAuthentication(authResult);
 //将SecurityContext对象保存到SecurityContextHolder中
 SecurityContextHolder.setContext(context);
 //将SecurityContext安全上下文保存到仓库中,默认情况下什么也不做
 this.securityContextRepository.saveContext(context, request, response);
 if (this.logger.isDebugEnabled()) {
  this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
 }
 //调用rememberMeServices的loginSuccess方法,执行rememberMe的相关逻辑,这里就不展开看了,自行跟踪
 this.rememberMeServices.loginSuccess(request, response, authResult);
 //如果eventPublisher发布器不等于null,则发布一个事件,我们可以监听这个事件,进行自定义处理认证成功后的逻辑
 if (this.eventPublisher != null) {
  this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
 }
 //这里就会调用我们自己实现的AuthenticationSuccessHandler接口的方法
 this.successHandler.onAuthenticationSuccess(request, response, authResult);
}

认证失败后的逻辑处理

protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
   AuthenticationException failed) throws IOException, ServletException {
 SecurityContextHolder.clearContext();
 this.logger.trace("Failed to process authentication request", failed);
 this.logger.trace("Cleared SecurityContextHolder");
 this.logger.trace("Handling authentication failure");
 this.rememberMeServices.loginFail(request, response);
 //调用我们自己实现的AuthenticationFailureHandler接口的方法
 this.failureHandler.onAuthenticationFailure(request, response, failed);
}

重点看一下UsernamePasswordAuthenticationFilterattemptAuthentication方法

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
   throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
 throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
//从请求中获取参数名username对应的参数值
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
//从请求中获取参数名password对应的参数值
String password = obtainPassword(request);
password = (password != null) ? password : "";
//通过username和password创建一个为经过身份认证的UsernamePasswordAuthenticationToken对象
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
  password);
// Allow subclasses to set the "details" property
//允许子类设置一些客户端的详细信息,例如ip、port等
setDetails(request, authRequest);
//调用AuthenticationManager对象的authenticate方法进行身份认证
return this.getAuthenticationManager().authenticate(authRequest);
}

AuthenticationManager是一个接口,默认实现是ProviderManager

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 Class<? extends Authentication> toTest = authentication.getClass();
 AuthenticationException lastException = null;
 AuthenticationException parentException = null;
 Authentication result = null;
 Authentication parentResult = null;
 int currentPosition = 0;
 int size = this.providers.size();
 //循环所有的AuthenticationProvider实现类
 for (AuthenticationProvider provider : getProviders()) {
  //如果支持当前Authentication类,则继续向下执行
  if (!provider.supports(toTest)) {
   continue;
  }
  if (logger.isTraceEnabled()) {
   logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
     provider.getClass().getSimpleName(), ++currentPosition, size));
  }
  try {
   //调用provider的authenticate方法,做认证,如果认证失败会被捕获并继续向外抛异常
   result = provider.authenticate(authentication);
   if (result != null) {
    copyDetails(authentication, result);
    break;
   }
  }
  catch (AccountStatusException | InternalAuthenticationServiceException ex) {
   prepareException(ex, authentication);
   // SEC-546: Avoid polling additional providers if auth failure is due to
   // invalid account status
   throw ex;
  }
  catch (AuthenticationException ex) {
   lastException = ex;
  }
 }
 if (result == null && this.parent != null) {
  // Allow the parent to try.
  try {
   parentResult = this.parent.authenticate(authentication);
   result = parentResult;
  }
  catch (ProviderNotFoundException ex) {
   // ignore as we will throw below if no other exception occurred prior to
   // calling parent and the parent
   // may throw ProviderNotFound even though a provider in the child already
   // handled the request
  }
  catch (AuthenticationException ex) {
   parentException = ex;
   lastException = ex;
  }
 }
 if (result != null) {
  if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
   // Authentication is complete. Remove credentials and other secret data
   // from authentication
   ((CredentialsContainer) result).eraseCredentials();
  }
  // If the parent AuthenticationManager was attempted and successful then it
  // will publish an AuthenticationSuccessEvent
  // This check prevents a duplicate AuthenticationSuccessEvent if the parent
  // AuthenticationManager already published it
  if (parentResult == null) {
   this.eventPublisher.publishAuthenticationSuccess(result);
  }

  return result;
 }

 // Parent was null, or didn't authenticate (or throw an exception).
 if (lastException == null) {
  lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
    new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
 }
 // If the parent AuthenticationManager was attempted and failed then it will
 // publish an AbstractAuthenticationFailureEvent
 // This check prevents a duplicate AbstractAuthenticationFailureEvent if the
 // parent AuthenticationManager already published it
 if (parentException == null) {
  prepareException(lastException, authentication);
 }
 throw lastException;
}

如果当前认证方式是用户名密码,那么AuthenticationProvider的实现类AbstractUserDetailsAuthenticationProvider支持当前Authentication类的子类

public boolean supports(Class<?> authentication) {
 return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

则会进入AbstractUserDetailsAuthenticationProvider类中的authenticate方法

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
   () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
     "Only UsernamePasswordAuthenticationToken is supported"));
 //从Authentication中获取用户名
 String username = determineUsername(authentication);
 boolean cacheWasUsed = true;
 //从缓存中获取,第一次,没有
 UserDetails user = this.userCache.getUserFromCache(username);
 if (user == null) {
  cacheWasUsed = false;
  try {
   //retrieveUser是一个抽象方法,其子类DaoAuthenticationProvider实现了这个方法
   user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
  }
  catch (UsernameNotFoundException ex) {
   this.logger.debug("Failed to find user '" + username + "'");
   if (!this.hideUserNotFoundExceptions) {
    throw ex;
   }
   throw new BadCredentialsException(this.messages
     .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
  }
  Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
 }
 try {
  //如果获取UserDetails信息成功,则会进行一系列的检查,比如账号是否锁定、账号是否过期、密码是否过期登
  this.preAuthenticationChecks.check(user);
  additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
 }
 catch (AuthenticationException ex) {
  if (!cacheWasUsed) {
   throw ex;
  }
  // There was a problem, so try again after checking
  // we're using latest data (i.e. not from the cache)
  cacheWasUsed = false;
  user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
  this.preAuthenticationChecks.check(user);
  additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
 }
 this.postAuthenticationChecks.check(user);
 if (!cacheWasUsed) {
  this.userCache.putUserInCache(user);
 }
 Object principalToReturn = user;
 if (this.forcePrincipalAsString) {
  principalToReturn = user.getUsername();
 }
 //返回经过身份认证的Authentication对象
 return createSuccessAuthentication(principalToReturn, authentication, user);
}

进入DaoAuthenticationProviderretrieveUser方法

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
   throws AuthenticationException {
 prepareTimingAttackProtection();
 try {
  //调用UserDetailsService接口的实现类,根据用户名获取UserDetails信息
  UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
  if (loadedUser == null) {
   throw new InternalAuthenticationServiceException(
     "UserDetailsService returned null, which is an interface contract violation");
  }
  return loadedUser;
 }
 catch (UsernameNotFoundException ex) {
  mitigateAgainstTimingAttack(authentication);
  throw ex;
 }
 catch (InternalAuthenticationServiceException ex) {
  throw ex;
 }
 catch (Exception ex) {
  throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
 }
}

UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自/login的表单action。从表单中获取用户名和密码时,默认使用的表单name属性值为username和password,这俩个值也可以通过usernameParameter和passwordParameter在配置中自定义。

ExceptionTranslationFilter

此过滤器处于过滤器链倒数第二的位置

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
   throws IOException, ServletException {
 try {
  //放过直接调用下一个过滤器
  chain.doFilter(request, response);
 }
 catch (IOException ex) {
  //如果是IOException,直接向上抛出
  throw ex;
 }
 catch (Exception ex) {
  // Try to extract a SpringSecurityException from the stacktrace
  //尝试从堆栈信息中提取Spring Security Exception
  Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
  //先查找AuthenticationException类型的异常
  RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
    .getFirstThrowableOfType(AuthenticationException.class, causeChain);
  //如果没有查找到,接着查找AccessDeniedException类型的异常
  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);
  }
  //处理spring security 相关异常
  handleSpringSecurityException(request, response, chain, securityException);
 }
}

private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
   FilterChain chain, RuntimeException exception) throws IOException, ServletException {
 //如果异常类型是AuthenticationException
 if (exception instanceof AuthenticationException) {
  //处理认证异常
  handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
 }
 else if (exception instanceof AccessDeniedException) {
  //处理访问拒绝异常
  handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
 }
}

如果是AuthenticationException类型的异常

private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,
   FilterChain chain, AuthenticationException exception) throws ServletException, IOException {
 this.logger.trace("Sending to authentication entry point since authentication failed", exception);
 sendStartAuthentication(request, response, chain, exception);
}

protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
   AuthenticationException reason) throws ServletException, IOException {
 // SEC-112: Clear the SecurityContextHolder's Authentication, as the
 // existing Authentication is no longer considered valid
 SecurityContext context = SecurityContextHolder.createEmptyContext();
 SecurityContextHolder.setContext(context);
 //将当前请求保存到RequestCache中
 this.requestCache.saveRequest(request, response);
 //调用AuthenticationEntryPoint接口实现类的commence方法
 this.authenticationEntryPoint.commence(request, response, reason);
}

看一下LoginUrlAuthenticationEntryPoint的实现逻辑

public void commence(HttpServletRequest request, HttpServletResponse response,
   AuthenticationException authException) throws IOException, ServletException {
 //useForward默认false
 if (!this.useForward) {
  // redirect to login page. Use https if forceHttps true
  //构建重定向url到登录页
  String redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
  //重定向到登录页
  this.redirectStrategy.sendRedirect(request, response, redirectUrl);
  return;
 }
 String redirectUrl = null;
 if (this.forceHttps && "http".equals(request.getScheme())) {
  // First redirect the current request to HTTPS. When that request is received,
  // the forward to the login page will be used.
  redirectUrl = buildHttpsRedirectUrlForRequest(request);
 }
 if (redirectUrl != null) {
  this.redirectStrategy.sendRedirect(request, response, redirectUrl);
  return;
 }
 String loginForm = determineUrlToUseForThisRequest(request, response, authException);
 logger.debug(LogMessage.format("Server side forward to: %s", loginForm));
 RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
 dispatcher.forward(request, response);
 return;
}

如果是AccessDeniedException类型的异常

private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
   FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
 boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
 //如果是匿名访问或者记住我
 if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
  if (logger.isTraceEnabled()) {
   logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
     authentication), exception);
  }
  sendStartAuthentication(request, response, chain,
    new InsufficientAuthenticationException(
      this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
        "Full authentication is required to access this resource")));
 }
 else {
  if (logger.isTraceEnabled()) {
   logger.trace(
     LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
     exception);
  }
  //否则调用AccessDeniedHandler接口实现类的handle方法
  this.accessDeniedHandler.handle(request, response, exception);
 }
}

看一下AccessDeniedHandlerImpl的处理逻辑

public void handle(HttpServletRequest request, HttpServletResponse response,
   AccessDeniedException accessDeniedException) throws IOException, ServletException {
 if (response.isCommitted()) {
  logger.trace("Did not write to response since already committed");
  return;
 }
 if (this.errorPage == null) {
  logger.debug("Responding with 403 status code");
  response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
  return;
 }
 // Put exception into request scope (perhaps of use to a view)
 request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
 // Set the 403 status code.
 response.setStatus(HttpStatus.FORBIDDEN.value());
 // forward to error page.
 if (logger.isDebugEnabled()) {
  logger.debug(LogMessage.format("Forwarding to %s with status code 403", this.errorPage));
 }
 request.getRequestDispatcher(this.errorPage).forward(request, response);
}

设置403状态码并转发到错误页面

ExceptionTranslationFilter:捕获来自过滤器链的所有异常,并进行处理。但是只处理两类异常:AccessDeniedException和Authenti cationException 异常,其他的异常会继续抛出。

如果捕获到的AuthenticationException,那么将会使用其对应的AuthenticationEntryPoint的commence()方法处理。在处理之前,ExceptionTranslationFilter先使用RequestCache将当前的HTTPServletRequest的信息保存起来,方便用户登录成功后可以跳转到之前的页面。

可以自定义AuthenticationException的处理方法。需要实现AuthenticationEntryPoint接口,然后重写commence()方法。

如果捕获的AuthenticationDeniedException,那么将会根据当前访问的用户是否已经登录认证做不同的处理,如果未登录,则会使用关联的AuthenticationEntryPoint的commence()方法进行处理,否则将使用关联的AccessDeniedHandler的handle()方法进行处理。

可以进行自定义AuthenticationDeniedException的处理方法。需要实现AccessDeniedHandler接口,然后重写handle()方法。

FilterSecurityInterceptor

守门员

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
   throws IOException, ServletException {
 invoke(new FilterInvocation(request, response, chain));
}

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
 if (isApplied(filterInvocation) && this.observeOncePerRequest) {
  // filter already applied to this request and user wants us to observe
  // once-per-request handling, so don't re-do security checking
  filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
  return;
 }
 // first time this request being called, so perform security checking
 if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
  filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
 }
 //在调用下游的服务之前进行最后的检查
 InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
 try {
  //过滤器链的最后一个,调用下游服务
  filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
 }
 finally {
  super.finallyInvocation(token);
 }
 super.afterInvocation(token, null);
}

​FilterSecurityInterceptor:做为过滤器链中的最后一个过滤器,承担着最后的身份验证和鉴权,如果验证通过,就会调用到下游的服务。关于这个过滤器的执行逻辑,鉴于篇幅的原因,本篇暂不展开分析,后面会单独写一篇对这个过滤器的介绍。

总结

今天介绍了四类过滤器,其中绿色的过滤器,我们是可以根据需要进行扩展的,比如第二篇中,实现的手机短信认证方式。关于spring security过滤器链中的过滤器,远不止这些,这里只是介绍了几个关键的过滤器,其他更多过滤器请自行阅读,谢谢大家的阅读。

posted @ 2024-02-04 16:47  CharyGao  阅读(1543)  评论(0编辑  收藏  举报