You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security自定义JwtFilter在permitAll端点仍执行问题求助

问题描述

使用Spring Security保护接口,需要开放以下两个POST端点,使其不经过自定义JWT认证过滤器:

  • /api/users/generate-token:首次登录生成Token,无需JWT认证
  • /api/users/register:用户注册,无需JWT认证

当前遇到的问题:

  1. 即使为这两个端点设置了permitAll(),自定义JwtAuthenticationFilter仍会被执行
  2. 从配置类中移除addFilterBefore后,调用这些端点时该过滤器依然运行
  3. 调用/api/users/generate-token时无法正常获取Token,请求被拦截

本人是Java和Spring 6新手,已卡3天无法推进。

问题原因分析
  1. 全局过滤器自动注册:JwtAuthenticationFilter上标注了@Component,Spring会自动将其注册到全局Servlet过滤器链,而非仅Spring Security过滤器链,所以即使在Security配置中移除addFilterBefore,过滤器依然会执行。
  2. permitAll的作用局限:permitAll()仅表示允许匿名访问该端点,不会跳过Security过滤器链中的过滤器,过滤器仍会执行JWT验证逻辑。
  3. 过滤器逻辑漏洞: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();
    }
}
验证说明
  1. 调用POST /api/users/generate-token:过滤器会识别到该路径属于跳过列表,直接执行filterChain.doFilter,请求到达控制器,完成认证后返回Token。
  2. 调用POST /api/users/register:同样跳过JWT验证逻辑,直接处理注册请求。
  3. 调用其他需要认证的端点:过滤器会正常执行JWT验证逻辑,验证通过后允许访问。

内容的提问来源于stack exchange,提问作者Akash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 08:36:13