OncePerRequestFilter作用(转)

本文深入探讨了Spring中OncePerRequestFilter的原理及其存在的必要性,解释了为何它能确保每个请求仅执行一次过滤操作,并讨论了不同Servlet版本下过滤行为的变化,强调了此设计对兼容多种Web容器的重要性。

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

在spring中,filter都默认继承OncePerRequestFilter,但为什么要这样呢?

 

OncePerRequestFilter顾名思义,他能够确保在一次请求只通过一次filter,而不需要重复执行。

 

Java代码  收藏代码
  1. public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)  
  2.             throws ServletException, IOException {  
  3.   
  4.         if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {  
  5.             throw new ServletException("OncePerRequestFilter just supports HTTP requests");  
  6.         }  
  7.         HttpServletRequest httpRequest = (HttpServletRequest) request;  
  8.         HttpServletResponse httpResponse = (HttpServletResponse) response;  
  9.   
  10.         String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();  
  11.         if (request.getAttribute(alreadyFilteredAttributeName) != null || shouldNotFilter(httpRequest)) {  
  12.             // Proceed without invoking this filter...  
  13.             filterChain.doFilter(request, response);  
  14.         }  
  15.         else {  
  16.             // Do invoke this filter...  
  17.             request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);  
  18.             try {  
  19.                 doFilterInternal(httpRequest, httpResponse, filterChain);  
  20.             }  
  21.             finally {  
  22.                 // Remove the "already filtered" request attribute for this request.  
  23.                 request.removeAttribute(alreadyFilteredAttributeName);  
  24.             }  
  25.         }  
  26.     }  

 

大家常识上都认为,一次请求本来就只过一次,为什么还要由此特别限定呢,呵呵实际上我们常识和实际的实现并不真的一样,经过一番查阅后,此方式是为 了兼容不同的web container,特意而为之(jsr168),也就是说并不是所有的container都像我们期望的只过滤一次,servlet版本不同,表现也不 同:。

 

 写道
/**
* Filter base class that guarantees to be just executed once per request,
* on any servlet container. It provides a {@link #doFilterInternal}
* method with HttpServletRequest and HttpServletResponse arguments.
*
* <p>The {@link #getAlreadyFilteredAttributeName} method determines how
* to identify that a request is already filtered. The default implementation
* is based on the configured name of the concrete filter instance.
*
* @author Juergen Hoeller
* @since 06.12.2003
*/

 

如,servlet2.3与servlet2.4也有一定差异

 写道
在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。

到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤,但是有时候我们需要 forward的时候也用到Filter。

 因此,为了兼容各种不同的运行环境和版本,默认filter继承OncePerRequestFilter是一个比较稳妥的选择。

转载于:https://www.cnblogs.com/cornucopia/p/4502664.html

package com.hw.camunda.config.filter; import com.hw.camunda.config.SecurityProperties; import com.hw.camunda.exception.BaseException; import com.hw.camunda.exception.ErrorMsg; import com.hw.camunda.service.impl.CustomUserDetailsService; import com.hw.camunda.utils.JwtUtil; import jakarta.annotation.Resource; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Order(1) @Component public class TokenFilter extends OncePerRequestFilter { private final CustomUserDetailsService userDetailsService; private final SecurityProperties securityProperties; @Autowired public TokenFilter(SecurityProperties securityProperties,CustomUserDetailsService userDetailsService) { this.securityProperties = securityProperties; this.userDetailsService = userDetailsService; } // 允许的过期后刷新时间(分钟) private static final int ALLOW_EXPIRED_MINUTES = 10; @Override public boolean shouldNotFilter(HttpServletRequest request) { String uri = request.getRequestURI(); return securityProperties.getPermitUrls().contains(uri); } @Override public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 从请求头中获取token String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { throw new BaseException(ErrorMsg.JWT_TOKEN_INVALID); } // 去掉可能的"Bearer "前缀 if (token.startsWith("Bearer ")) { token = token.substring(7); } try { // 先尝试验证token(未过期的情况) JwtUtil.validateToken(token); } catch (BaseException e) { // 如果捕获到过期异常,我们尝试刷新 if (e.getErrorCode().toString().equals(ErrorMsg.LOGIN_TOKEN_EXPIRE.getCode()+"")) { // 尝试刷新令牌 try { token = JwtUtil.refreshToken(token, ALLOW_EXPIRED_MINUTES); // 将新令牌设置到响应头,客户端需要保存并替换旧令牌 response.setHeader("New-Access-Token", token); } catch (BaseException ex) { // 刷新失败,抛出异常(可能是过期时间超过10分钟或无效token) throw ex; } } else { // 其他验证失败的情况(如无效token) throw e; } } //最好保存到redis中 UserDetails userDetails = userDetailsService.loadUserByUsername(token); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); chain.doFilter(request, response); } } package com.hw.camunda.exception; import com.hw.camunda.utils.JsonResult; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.resource.NoResourceFoundException; import javax.security.sasl.AuthenticationException; @ControllerAdvice @ResponseBody @Order(Ordered.HIGHEST_PRECEDENCE) public class ExceptionHandlerAdvice { @ExceptionHandler(BaseException.class) @ResponseBody public JsonResult<String> baseException(BaseException e){ if (e.getErrorCode() != null) { return JsonResult.error(e.getErrorCode(),e.getMessage()); }else { return JsonResult.error(e.getMessage()); } } @ExceptionHandler(NoResourceFoundException.class) @ResponseBody public JsonResult<String> resourceFound(NoResourceFoundException ex) { return JsonResult.error(ErrorMsg.NOT_PERMISSION); } @ExceptionHandler(NoHandlerFoundException.class) @ResponseBody public JsonResult<String> handleFound(NoHandlerFoundException ex) { return JsonResult.error(ErrorMsg.NOT_PERMISSION); } @ExceptionHandler(AccessDeniedException.class) @ResponseBody public JsonResult<String> handleAccessDenied(AccessDeniedException ex) { return JsonResult.error(ErrorMsg.NOT_PERMISSION); } // 处理认证失败异常 @ExceptionHandler(AuthenticationException.class) @ResponseBody public JsonResult<String> handleAuthFailure(AuthenticationException ex) { return JsonResult.error(ErrorMsg.NOT_LOGIN); } @ExceptionHandler(value = RuntimeException.class) @ResponseBody public JsonResult<String> runtimeException(RuntimeException e){ return JsonResult.error("业务异常:"+e.getMessage()); } @ExceptionHandler(value = Exception.class) @ResponseBody public JsonResult<String> allException(Exception e){ e.printStackTrace(); return JsonResult.error("系统异常:"+e.getMessage()); } } 为什么返回的是500package com.hw.camunda.config; import com.hw.camunda.config.filter.TokenFilter; import com.hw.camunda.service.impl.CustomUserDetailsService; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @EnableMethodSecurity // 启用方法级注解鉴权 public class SecurityConfig { private final CustomUserDetailsService userDetailsService; private final SecurityProperties securityProperties; @Autowired public SecurityConfig(SecurityProperties securityProperties,CustomUserDetailsService userDetailsService) { this.securityProperties = securityProperties; this.userDetailsService = userDetailsService; } /** * 安全过滤链配置 - 清晰定义拦截规则 * 优先级:从上到下匹配规则 */ @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .addFilterBefore(new TokenFilter(securityProperties,userDetailsService), UsernamePasswordAuthenticationFilter.class); /*.authorizeHttpRequests(auth -> { for (String permitUrl : securityProperties.getPermitUrls()) { auth.requestMatchers(permitUrl).permitAll(); } auth.requestMatchers("/api/**").authenticated() .anyRequest().denyAll(); } );*/ return http.build(); } }
最新发布
08-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值