How to solve this beans conflict?

47 views Asked by At

I am trying to add Login and Registration to my API Application. But I ran into problems. Two beans conflict with each other. I put all classes which interact with these beans in the message.

2024-03-11T00:13:22.768+03:00  WARN 22820 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityController': Unsatisfied dependency expressed through method 'setAuthenticationManager' parameter 0: Error creating bean with name 'authenticationManager' defined in class path resource [com/weather/weather/config/SecurityConfigurator.class]: Failed to instantiate [org.springframework.security.authentication.AuthenticationManager]: Factory method 'authenticationManager' threw exception with message: No qualifying bean of type 'org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder' available: expected single matching bean but found 2: authenticationManagerBuilder,configureAuthenticationManagerBuilder
2024-03-11T00:13:22.769+03:00  INFO 22820 --- [           main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-03-11T00:13:22.771+03:00  INFO 22820 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-03-11T00:13:22.774+03:00  INFO 22820 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2024-03-11T00:13:22.776+03:00  INFO 22820 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2024-03-11T00:13:22.788+03:00  INFO 22820 --- [           main] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-03-11T00:13:22.800+03:00 ERROR 22820 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of method setAuthenticationManager in com.weather.weather.controller.SecurityController required a single bean, but 2 were found:
    - authenticationManagerBuilder: defined by method 'authenticationManagerBuilder' in class path resource [org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.class]
    - configureAuthenticationManagerBuilder: defined by method 'configureAuthenticationManagerBuilder' in class path resource [com/weather/weather/config/SecurityConfigurator.class]

This may be due to missing parameter name information

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Ensure that your compiler is configured to use the '-parameters' flag.
You may need to update both your build tool settings as well as your IDE.

SecurityConfigurator

@Configuration
@EnableWebSecurity
@Data
public class SecurityConfigurator {
    private UserService userService;
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)throws Exception{
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public AuthenticationManagerBuilder configureAuthenticationManagerBuilder(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userService).passwordEncoder(passwordEncoder());
        return authenticationManagerBuilder;
    }
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors(AbstractHttpConfigurer::disable)
                .cors(httpSecurityCorsConfigurer ->
                        httpSecurityCorsConfigurer.configurationSource(request ->
                                new CorsConfiguration().applyPermitDefaultValues())
                )
                .exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/auth/**").permitAll()
                        .requestMatchers("/api/v1/weather").fullyAuthenticated()
                        .anyRequest().permitAll()
                );
        return http.build();
    }
}

SecurityController

@RestController
@RequestMapping("/auth")
public class SecurityController {
    private UserRepository userRepository;
    private PasswordEncoder passwordEncoder;
    private AuthenticationManager authenticationManager;
    private JwtCore jwtCore;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    @Autowired
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }
    @Autowired
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }
    @Autowired
    public void setJwtCore(JwtCore jwtCore) {
        this.jwtCore = jwtCore;
    }


    @PostMapping("/signup")
    ResponseEntity<?> signup(@RequestBody SignUpRequest signUpRequest){
        if(userRepository.existsUserByUsername(signUpRequest.getUsername())){
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("This nickname already exist");
        }
        User user = new User();
        user.setUsername(signUpRequest.getUsername());
        user.setPassword(passwordEncoder.encode(signUpRequest.getPassword()));
        userRepository.save(user);
        return ResponseEntity.ok("Chinazes");

    }

    @PostMapping("/signin")
    ResponseEntity<?> signin(@RequestBody SignInRequest signInRequest){
        Authentication authentication = null;
        try{
            authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(signInRequest.getUsername(),signInRequest.getPassword()));
        } catch (BadCredentialsException e){
            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtCore.generateToken(authentication);
        return ResponseEntity.ok(jwt);

    }

}

I tried to use @Primary and @Qualifier, but they caused more errors. I don't understand why Spring complains about it, if these beans are different.

1

There are 1 answers

0
jwanger On

The issue lies in the fact that the @EnableWebSecurity creates an AuthenticationManagerBuilder bean, and you do as well. I confirmed locally that if you mark the AuthenticationManagerBuilder with @Primary the error goes away as spring boot is now able to determine which bean to use in your SecurityController.

    @Bean
    @Primary
    public AuthenticationManagerBuilder configureAuthenticationManagerBuilder(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userService).passwordEncoder(passwordEncoder());
        return authenticationManagerBuilder;
    }