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

Spring Boot连接MySQL时出现Bean循环依赖问题求助

解决Spring Boot登录切换数据库时的循环依赖与登录失败问题

一、先解决循环依赖问题

循环依赖的根源是Spring Boot自动配置的InMemoryUserDetailsManager仍在生效,和你的WebSecurityConfig形成依赖闭环。直接禁用这个自动配置即可:

  • 方案1:在启动类添加排除注解
    @SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class)
    public class YourApplication {
        public static void main(String[] args) {
            SpringApplication.run(YourApplication.class, args);
        }
    }
    
  • 方案2:在application.properties中添加配置
    spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
    
    无需再设置spring.main.allow-circular-references=true,彻底消除循环依赖。

二、排查登录失败的核心问题

1. 确保自定义UserDetailsService被正确注册

检查UserDetailsServiceImpl和AdminDetailsServiceImpl是否添加了@Service或@Component注解,让Spring能扫描到并创建Bean。

2. 配置WebSecurity时区分多UserDetailsService

因为你同时有普通用户和管理员两个认证源,需要分别配置认证提供者:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    private final UserDetailsService userUserDetailsService;
    private final UserDetailsService adminUserDetailsService;

    // 用@Qualifier区分两个UserDetailsService Bean
    public WebSecurityConfig(@Qualifier("userDetailsServiceImpl") UserDetailsService userUserDetailsService,
                             @Qualifier("adminDetailsServiceImpl") UserDetailsService adminUserDetailsService) {
        this.userUserDetailsService = userUserDetailsService;
        this.adminUserDetailsService = adminUserDetailsService;
    }

    // 密码编码器必须统一,确保和数据库存储的加密规则一致
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 普通用户认证提供者
    @Bean
    public AuthenticationProvider userAuthProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userUserDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    // 管理员认证提供者
    @Bean
    public AuthenticationProvider adminAuthProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(adminUserDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(userAuthProvider())
            .authenticationProvider(adminAuthProvider());
    }

    // 根据业务配置HttpSecurity权限规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/login", "/css/**", "/js/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login") // 自定义登录页路径,无自定义可省略
                .loginProcessingUrl("/do-login")
                .defaultSuccessUrl("/dashboard")
                .permitAll()
            .and()
            .logout()
                .permitAll();
    }
}

3. 检查UserDetails实现类的正确性

确保CustomUserDetails和CustomAdminDetails正确实现UserDetails接口,重点关注:

  • getAuthorities():返回对应角色的权限集合,比如return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
  • isAccountNonExpired()、isAccountNonLocked()、isCredentialsNonExpired()、isEnabled():根据业务需求返回true或false

4. 确认数据库密码的加密一致性

数据库中存储的用户/管理员密码必须是用BCryptPasswordEncoder加密后的字符串,不能存明文。可以通过以下代码生成加密密码:

public class PasswordGenerator {
    public static void main(String[] args) {
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        System.out.println(encoder.encode("your_plain_password"));
    }
}

将生成的字符串存入数据库的密码字段。

5. 验证Repository查询逻辑

检查UserRepository和AdminRepository是否有正确的查询方法,比如:

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

确保UserDetailsServiceImpl的loadUserByUsername()方法是通过邮箱(作为登录用户名)查询用户:

@Service("userDetailsServiceImpl")
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException("用户不存在:" + email));
        return new CustomUserDetails(user);
    }
}

三、最后验证

启动应用后,用数据库中存储的加密密码对应的明文密码登录,同时可以通过调试模式查看loadUserByUsername()是否被正确调用,以及返回的UserDetails信息是否正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 06:52:50