Springboot之OncePerRequestFilter用法及说明
单次请求过滤器确保每个请求仅执行一次过滤,避免重复执行。在安全框架中,已放权的URL若请求头仍携带有效令牌,会错误地被拦截。可通过继承该过滤器,在放权路径下移除令牌头,从而解决此问题。
Springboot之OncePerRequestFilter
先说个基础认知:OncePerRequestFilter 这个过滤器,核心作用就是确保在一次请求的生命周期里,同一个过滤器只被执行一次。听起来好像理所当然?但在复杂的过滤器链里,尤其是在 Spring Security 这种多层嵌套的环境下,一个过滤器被重复调用的情况并不罕见。它存在的意义,就是避免重复执行带来的副作用和性能浪费。

记录到问题
实际使用中,会碰上一个很典型的坑:在 Spring Security 里,明明已经配置了某些 URL 不走鉴权(即放权校验),但诡异的是——如果请求头里依然带着有效的 token,这些本应放行的 URL 还是会被拦截下来,弹回一个未授权或错误的响应。这显然不是我们想要的行为,毕竟放权就意味着完全跳过鉴权逻辑,而不是“看你有没有 token 再决定”。
解决方案
解决思路其实非常直接:既然问题出在 token 信息被错误地带进了放权路径的过滤逻辑里,那就干脆在进入校验前就把 token 从请求头里“摘掉”。具体怎么做?让自定义的 JwtAuthenticationTokenFilter 继承 OncePerRequestFilter,重写 doFilterInternal() 方法,在里面手动处理请求头中的 Authorization 字段。
下面就是一个完整的实现示例:
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private TokenService tokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
log.info("当前访问的地址:{}", request.getRequestURI());
String url = request.getRequestURI();
// 校验请求的url是否在忽略鉴权的url中
if (checkUrl(url)) {
request = new HttpServletRequestWrapper(request) {
private Set headerNameSet;
@Override
public Enumeration getHeaderNames() {
if (headerNameSet == null) {
// first time this method is called, cache the wrapped request's header names:
headerNameSet = new HashSet<>();
Enumeration wrappedHeaderNames = super.getHeaderNames();
while (wrappedHeaderNames.hasMoreElements()) {
String headerName = wrappedHeaderNames.nextElement();
if (!"Authorization".equalsIgnoreCase(headerName)) {
headerNameSet.add(headerName);
}
}
}
return Collections.enumeration(headerNameSet);
}
@Override
public Enumeration getHeaders(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return Collections.emptyEnumeration();
}
return super.getHeaders(name);
}
@Override
public String getHeader(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return null;
}
return super.getHeader(name);
}
};
}
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);
}
/**
* 校验路径是否是要忽略鉴权路径
* @param url
*/
private boolean checkUrl(String url){
for (String method : PatternsConstant.PATTERNS) {
if (!method.equals("/**") && !method.equals("**")) {
if (method.endsWith("/**")){
method = method.substring(0, method.length() - 3);
if (url.startsWith(method)) {
return true;
}
}else {
if (url.equals(method)) {
return true;
}
}
}else {
return false;
}
}
return false;
}
}
有一个细节需要留意:上面代码里用到的 PatternsConstant.PATTERNS 是存放忽略鉴权 URL 的集合,需要单独定义在一个类里,否则 checkUrl 方法拿不到这些路径。当然你也可以把路径硬编码在过滤器中,但为了维护方便,还是建议集中管理。
总结
回过头来看,这个问题的本质并不是 Spring Security 的 bug,而是过滤器链对请求头处理顺序导致的“副作用”。通过继承 OncePerRequestFilter 并在放权路径下移除 Authorization 头,就能干净利落地解决。代码量不大,但逻辑上需要想清楚——你是真的想放权,而不是“检查有没有 token 再决定”。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















