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

Spring应用上下文Bean依赖循环问题求助(JWT授权场景)

搞定Spring JWT授权里的Bean循环依赖问题

遇到的问题

给项目加JWT授权时,碰到Spring上下文里的Bean循环依赖:JwtAuthenticationFilter和SecurityConfig互相依赖。试了@Lazy注解和开spring.main.allow-circular-references=true都没用,得找个彻底解决循环的办法。

控制台报错

应用上下文中部分Bean的依赖形成循环:

┌─────┐
| jwtAuthenticationFilter 定义在文件 [C:\Users\User\Desktop\PizzaCloud2\target\classes\pizzas\security\authentication\JwtAuthenticationFilter.class]
↑ ↓
| securityConfig 定义在文件 [C:\Users\User\Desktop\PizzaCloud2\target\classes\pizzas\security\SecurityConfig.class]
└─────┘

操作建议:

不建议用循环引用,默认是禁止的。请修改代码移除Bean间的循环依赖。实在不行可以开spring.main.allow-circular-references=true尝试自动打破循环,但这是下策。

原代码

JwtAuthenticationFilter类

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter{
    
    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request, 
            @NonNull HttpServletResponse response, 
            @NonNull FilterChain filterChain)
                    throws ServletException, IOException {
        
        final String authHeader = request.getHeader("Äuthorization"); // 这里拼写错了,应该是Authorization
        final String jwt;
        final String username;
        if (authHeader == null || !authHeader.startsWith("Bearer")) {
            filterChain.doFilter(request, response);
            return;
        }
        jwt = authHeader.substring(7);
        username = jwtService.extractUsername(jwt);
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtService.isTokenValid(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                    userDetails, userDetails.getAuthorities()
                );
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

SecurityConfig类

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    
    private final JwtAuthenticationFilter jwtAuthFilter;
    
    private final AuthenticationProvider authenticationProvider;
  
    @Bean
    public UserDetailsService userDetailsService(UserRepository userRepo) {
        return username -> {
            User2 user = userRepo.findByUsername(username);
            if (user != null) {
                return user;
            }
            throw new UsernameNotFoundException(
                    "User '" + username + "' not found");
        };
    }
  
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        
        http
        .authenticationProvider(authenticationProvider)
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.csrf()
                .disable()
                .authorizeHttpRequests()
                .requestMatchers("/design", "/orders").hasRole("USER")
                .anyRequest().permitAll()
              
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
              
                .and()
                .formLogin()
                .loginPage("/login")

                .and()
                .logout()
                .logoutSuccessUrl("/")
        
                .and()  
                .headers()
                .frameOptions()
                .sameOrigin()
        
                .and()
                .build();
        }
}

为啥会循环

JwtAuthenticationFilter标了@Component,Spring会提前实例化它,它需要UserDetailsService——这个Bean刚好是在SecurityConfig里定义的。同时SecurityConfig又要注入JwtAuthenticationFilter,就形成了死循环:JwtAuthenticationFilter要UserDetailsService,UserDetailsService属于SecurityConfig,SecurityConfig又要JwtAuthenticationFilter。

解决办法

办法一:把JwtAuthenticationFilter改成在SecurityConfig里用@Bean创建

  1. 先删掉JwtAuthenticationFilter上的@Component注解,不让Spring提前实例化它
  2. 在SecurityConfig里加个方法创建JwtAuthenticationFilter的Bean,直接传它需要的依赖,这样就不用提前注入整个Filter了

修改后的代码:

JwtAuthenticationFilter类(去掉@Component)

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter{
    
    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request, 
            @NonNull HttpServletResponse response, 
            @NonNull FilterChain filterChain)
                    throws ServletException, IOException {
        
        final String authHeader = request.getHeader("Authorization"); // 修正拼写错误
        final String jwt;
        final String username;
        if (authHeader == null || !authHeader.startsWith("Bearer")) {
            filterChain.doFilter(request, response);
            return;
        }
        jwt = authHeader.substring(7);
        username = jwtService.extractUsername(jwt);
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtService.isTokenValid(jwt, userDetails)) {
                // 修正构造方法:第三个参数是权限,第二个参数传null(因为JWT不需要密码凭证)
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities()
                );
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

SecurityConfig类(新增@Bean方法,移除成员变量注入)

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    
    private final AuthenticationProvider authenticationProvider;
  
    @Bean
    public UserDetailsService userDetailsService(UserRepository userRepo) {
        return username -> {
            User2 user = userRepo.findByUsername(username);
            if (user != null) {
                return user;
            }
            throw new UsernameNotFoundException(
                    "User '" + username + "' not found");
        };
    }

    // 新增:创建JwtAuthenticationFilter的Bean
    @Bean
    public JwtAuthenticationFilter jwtAuthFilter(JwtService jwtService, UserDetailsService userDetailsService) {
        return new JwtAuthenticationFilter(jwtService, userDetailsService);
    }
  
    // 直接在方法参数里注入jwtAuthFilter,不用存在成员变量里
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthFilter) throws Exception {
        
        http
        .authenticationProvider(authenticationProvider)
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.csrf()
                .disable()
                .authorizeHttpRequests()
                .requestMatchers("/design", "/orders").hasRole("USER")
                .anyRequest().permitAll()
              
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
              
                .and()
                .formLogin()
                .loginPage("/login")

                .and()
                .logout()
                .logoutSuccessUrl("/")
        
                .and()  
                .headers()
                .frameOptions()
                .sameOrigin()
        
                .and()
                .build();
        }
}

办法二:把UserDetailsService移到单独的配置类

把UserDetailsService的Bean定义从SecurityConfig里抽出来,放到新的配置类比如UserConfig里,这样JwtAuthenticationFilter依赖的UserDetailsService就和SecurityConfig没关系了,循环自然就断了。

新建UserConfig类:

@Configuration
@RequiredArgsConstructor
public class UserConfig {
    private final UserRepository userRepo;

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            User2 user = userRepo.findByUsername(username);
            if (user != null) {
                return user;
            }
            throw new UsernameNotFoundException(
                    "User '" + username + "' not found");
        };
    }
}

然后SecurityConfig里删掉原来的userDetailsService方法,正常注入JwtAuthenticationFilter就行,因为现在JwtAuthenticationFilter的依赖不依赖SecurityConfig了。

额外提醒

  • 原代码里Authorization拼写错成Äuthorization,这个一定要改,不然拿不到认证头
  • UsernamePasswordAuthenticationToken的构造方法要传三个参数:用户信息、凭证(JWT场景传null)、权限,原代码少了凭证参数,会导致认证信息不全

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 07:50:11