'The dependencies in the application context form a cycle

The dependencies of some of the beans in the application context form a cycle:

   authController defined in file [...\AuthController.class]
      ↓
   userServiceImpl defined in file [...\UserServiceImpl.class]
      ↓
   jwtManager defined in file [...\JwtManager.class]
┌─────┐
|  securityConfig defined in file [...\SecurityConfig.class]
└─────┘

I have tried with @Lazy but not working.

When I try to change the constructor into setters it gives me errors like:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userServiceImpl' defined in file [...\UserServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 3;

SecurityConfig

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final Logger LOG = LoggerFactory.getLogger(getClass());
    private UserDetailsService userService;
    private PasswordEncoder bCryptPasswordEncoder;
    private ObjectMapper mapper;

    @Value("${app.security.jwt.keystore-location}")
    private String keyStorePath;
    @Value("${app.security.jwt.keystore-password}")
    private String keyStorePassword;
    @Value("${app.security.jwt.key-alias}")
    private String keyAlias;
    @Value("${app.security.jwt.private-key-passphrase}")
    private String privateKeyPassphrase;

    public SecurityConfig(UserDetailsService userService,
                          PasswordEncoder bCryptPasswordEncoder, ObjectMapper mapper) {
        this.userService = userService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
        this.mapper = mapper;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic().disable().formLogin().disable()
              .csrf().ignoringAntMatchers(API_URL_PREFIX, H2_URL_PREFIX)
              .and()
              .headers().frameOptions().sameOrigin() // for H2 Console
              .and()
              .cors()
              .and()
              .authorizeRequests()
              .antMatchers(HttpMethod.POST, TOKEN_URL).permitAll()
              .antMatchers(HttpMethod.DELETE, TOKEN_URL).permitAll()
              .antMatchers(HttpMethod.POST, SIGNUP_URL).permitAll()
              .antMatchers(HttpMethod.POST, REFRESH_URL).permitAll()
              .antMatchers(HttpMethod.GET, PRODUCTS_URL).permitAll()
              .mvcMatchers(HttpMethod.POST, "/api/v1/restaurants**")
              .hasAuthority(RoleEnum.ADMIN.getAuthority())
              .anyRequest().authenticated()
              .and()
              /* Filter based security configuration
              .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
              .and()
              .httpBasic()
              .authenticationEntryPoint(authenticationEntryPoint)
              .and()
              .addFilterBefore(failureHandler , BearerTokenAuthenticationFilter.class)
              .addFilter(new LoginFilter(super.authenticationManager(), mapper))
              .addFilter(new JwtAuthenticationFilter(super.authenticationManager()))
              */
              .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt(
                    jwt -> jwt.jwtAuthenticationConverter(getJwtAuthenticationConverter())))
              .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    private Converter<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter authorityConverter = new JwtGrantedAuthoritiesConverter();
        authorityConverter.setAuthorityPrefix(AUTHORITY_PREFIX);
        authorityConverter.setAuthoritiesClaimName(ROLE_CLAIM);
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(authorityConverter);
        return converter;
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return userService;
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH"));
        //configuration.setAllowCredentials(true);
        // For CORS response headers
        configuration.addAllowedOrigin("*");
        configuration.addAllowedHeader("*");
        configuration.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public KeyStore keyStore() {
        try {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream resourceAsStream = Thread.currentThread().getContextClassLoader()
                  .getResourceAsStream(keyStorePath);
            keyStore.load(resourceAsStream, keyStorePassword.toCharArray());
            return keyStore;
        } catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
            LOG.error("Unable to load keystore: {}", keyStorePath, e);
        }
        throw new IllegalArgumentException("Unable to load keystore");
    }

    @Bean
    public RSAPrivateKey jwtSigningKey(KeyStore keyStore) {
        try {
            Key key = keyStore.getKey(keyAlias, privateKeyPassphrase.toCharArray());
            if (key instanceof RSAPrivateKey) {
                return (RSAPrivateKey) key;
            }
        } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
            LOG.error("Unable to load private key from keystore: {}", keyStorePath, e);
        }
        throw new IllegalArgumentException("Unable to load private key");
    }

    @Bean
    public RSAPublicKey jwtValidationKey(KeyStore keyStore) {
        try {
            Certificate certificate = keyStore.getCertificate(keyAlias);
            PublicKey publicKey = certificate.getPublicKey();
            if (publicKey instanceof RSAPublicKey) {
                return (RSAPublicKey) publicKey;
            }
        } catch (KeyStoreException e) {
            LOG.error("Unable to load private key from keystore: {}", keyStorePath, e);
        }
        throw new IllegalArgumentException("Unable to load RSA public key");
    }

    @Bean
    public JwtDecoder jwtDecoder(RSAPublicKey rsaPublicKey) {
        return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
    }

UserServiceImpl

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository repository;
    private final UserTokenRepository userTokenRepository;
    private final PasswordEncoder bCryptPasswordEncoder;
    private final JwtManager tokenManager;


    public UserServiceImpl(UserRepository repository, UserTokenRepository userTokenRepository,
                           PasswordEncoder bCryptPasswordEncoder, JwtManager tokenManager) {
        this.repository = repository;
        this.userTokenRepository = userTokenRepository;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
        this.tokenManager = tokenManager;
    }

JWTManager JwtManager is a custom class that is responsible for generating a new JWT. It uses the java-jwt library from auth0.com. I use public/private keys for signing the token.

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;

import static java.util.stream.Collectors.toList;
import static the.postbooking.app.security.Constants.EXPIRATION_TIME;
import static the.postbooking.app.security.Constants.ROLE_CLAIM;

@Component
public class JwtManager {

    private final RSAPrivateKey privateKey;
    private final RSAPublicKey publicKey;

    public JwtManager(RSAPrivateKey privateKey, RSAPublicKey publicKey) {
        this.privateKey = privateKey;
        this.publicKey = publicKey;
    }

    public String create(UserDetails principal) {
        final long now = System.currentTimeMillis();
        return JWT.create()
              .withIssuer("The Post Booking-App")
              .withSubject(principal.getUsername())
              .withClaim(ROLE_CLAIM,
                    principal.getAuthorities().stream().map(GrantedAuthority::getAuthority)
                          .collect(toList()))
              .withIssuedAt(new Date(now))
              .withExpiresAt(new Date(now + EXPIRATION_TIME))
              //.sign(Algorithm.HMAC512(SECRET_KEY.getBytes(StandardCharsets.UTF_8)));
              .sign(Algorithm.RSA256(publicKey, privateKey));
    }
}

AuthController The AuthController class is annotated with @RestController to mark it as a REST controller. Then, it uses two beans, UserService and PasswordEncoder, which will be injected at the time of the AuthController construction.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.RestController;
import postbookingapp.api.*;
import the.postbooking.app.entity.UserEntity;
import the.postbooking.app.exception.InvalidRefreshTokenException;
import the.postbooking.app.service.UserService;

import javax.validation.Valid;

import static org.springframework.http.ResponseEntity.*;

@RestController
public class AuthController implements UserApi {

    private final UserService service;
    private final PasswordEncoder passwordEncoder;

    public AuthController(UserService service, PasswordEncoder passwordEncoder) {
        this.service = service;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public ResponseEntity<SignedInUser> signIn(@Valid SignInReq signInReq) {
        UserEntity userEntity = service.findUserByUsername(signInReq.getUsername());
        if (passwordEncoder.matches(
              signInReq.getPassword(),
              userEntity.getPassword())) {
            return ok(service.getSignedInUser(userEntity));
        }
        throw new InsufficientAuthenticationException("Unauthorized.");
    }

    @Override
    public ResponseEntity<Void> signOut(@Valid RefreshToken refreshToken) {
        service.removeRefreshToken(refreshToken);
        return accepted().build();
    }

    @Override
    public ResponseEntity<SignedInUser> signUp(@Valid User user) {
        return status(HttpStatus.CREATED).body(service.createUser(user).get());
    }

    @Override
    public ResponseEntity<SignedInUser> getAccessToken(@Valid RefreshToken refreshToken) {
        return ok(service.getAccessToken(refreshToken)
              .orElseThrow(InvalidRefreshTokenException::new));
    }
}


Solution 1:[1]

Try adding spring.main.allow-circular-reference=true in your application.properties file

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Chinedu