Spring Security自定义JwtFilter在permitAll端点仍执行问题求助
问题描述
使用Spring Security保护接口,需要开放以下两个POST端点,使其不经过自定义JWT认证过滤器:
/api/users/generate-token:首次登录生成Token,无需JWT认证/api/users/register:用户注册,无需JWT认证
当前遇到的问题:
- 即使为这两个端点设置了
permitAll(),自定义JwtAuthenticationFilter仍会被执行 - 从配置类中移除
addFilterBefore后,调用这些端点时该过滤器依然运行 - 调用
/api/users/generate-token时无法正常获取Token,请求被拦截
本人是Java和Spring 6新手,已卡3天无法推进。
问题原因分析
- 全局过滤器自动注册:
JwtAuthenticationFilter上标注了@Component,Spring会自动将其注册到全局Servlet过滤器链,而非仅Spring Security过滤器链,所以即使在Security配置中移除addFilterBefore,过滤器依然会执行。 permitAll的作用局限:permitAll()仅表示允许匿名访问该端点,不会跳过Security过滤器链中的过滤器,过滤器仍会执行JWT验证逻辑。- 过滤器逻辑漏洞:
doFilterInternal方法中,filterChain.doFilter(request,response)仅在username!=null的分支中调用,当没有Token时,请求无法继续传递到控制器,直接被拦截。
解决方案
步骤1:移除过滤器的@Component注解
取消JwtAuthenticationFilter上的@Component,避免Spring自动将其注册到全局过滤器链,仅通过Security配置管理该过滤器。
步骤2:添加端点跳过逻辑
在JwtAuthenticationFilter中判断请求路径,如果是开放端点,直接跳过JWT验证逻辑,继续执行过滤器链。
步骤3:修复过滤器逻辑漏洞
确保无论是否存在Token、Token是否有效,都要调用filterChain.doFilter(request,response),保证请求能传递到后续环节。
修改后的代码
1. JwtAuthenticationFilter.java
// 移除@Component注解 public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Autowired private JwtAuthenticationEntryPoint authenticationEntryPoint; @Autowired private UserDetailsServiceImpl userDetailsServiceImpl; // 需要跳过的端点路径 private static final List<String> SKIP_PATHS = Arrays.asList( "/api/users/generate-token", "/api/users/register" ); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 判断当前请求是否是需要跳过的端点 String requestUri = request.getRequestURI(); if (SKIP_PATHS.contains(requestUri)) { filterChain.doFilter(request, response); return; } final String requestTokenHeader = request.getHeader("Authorization"); System.out.println("jwt authentication filter is being called"); String username = null; String jwtToken = null; if(requestTokenHeader!=null && requestTokenHeader.startsWith("Bearer")){ jwtToken = requestTokenHeader.substring(7); try { username = this.jwtUtil.extractUsername(jwtToken); }catch (ExpiredJwtException e){ e.printStackTrace(); System.out.println("jwt token expired"); }catch (Exception e){ e.printStackTrace(); System.out.println("error"); } }else { System.out.println("invalid or token doesn't start with Bearer"); } // validate if(username!=null && SecurityContextHolder.getContext().getAuthentication()==null){ final UserDetails userDetails = this.userDetailsServiceImpl.loadUserByUsername(username); if (this.jwtUtil.validateToken(jwtToken, userDetails)){ // token is valid UsernamePasswordAuthenticationToken usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()); usernamePasswordAuthentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthentication); }else { System.out.println("invalid token"); } } // 无论是否有token、token是否有效,都要继续执行过滤器链 filterChain.doFilter(request,response); } }
2. SecurityConfig.java
@Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { @Autowired private UserDetailsServiceImpl userDetailsServiceImpl; @Autowired private PasswordEncoderConfig passwordEncoderConfig; @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; // 因为JwtAuthenticationFilter不再是@Component,需要在这里创建Bean @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ http .csrf(csrf->csrf.disable()) .cors(cors->cors.disable()) .authorizeHttpRequests( auth->auth.requestMatchers("/api/users/generate-token", "/api/users/register").permitAll() .requestMatchers(HttpMethod.OPTIONS).permitAll() .anyRequest().authenticated()) .exceptionHandling((ex)->ex.authenticationEntryPoint(unauthorizedHandler)) .sessionManagement((session)->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 添加自定义过滤器到Security过滤器链 http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); System.out.println("http configuration is being used"); return http.build(); } @Bean public AuthenticationManager authenticationManagerBean(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); authenticationManagerBuilder.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoderConfig.passwordEncoder()); return authenticationManagerBuilder.build(); } }
验证说明
- 调用
POST /api/users/generate-token:过滤器会识别到该路径属于跳过列表,直接执行filterChain.doFilter,请求到达控制器,完成认证后返回Token。 - 调用
POST /api/users/register:同样跳过JWT验证逻辑,直接处理注册请求。 - 调用其他需要认证的端点:过滤器会正常执行JWT验证逻辑,验证通过后允许访问。
内容的提问来源于stack exchange,提问作者Akash
相关产品推荐
相关产品推荐

