kernel
kernel

Reputation: 793

Feign Client with Spring Cloud GatewayFilter cause cycle dependency

During certain checks, I need to make a request to my Microservice in the Gateway Filter. When I define the Feign class in the GatewayFilter(my SecurityFilter.java) class, it gives the following error. How can I resolve this situation.

Error:

Description:

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

┌─────┐
|  securityFilter defined in file [/target/classes/com/example/SecurityFilter.class]
↑     ↓
|  cachedCompositeRouteLocator defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class]
↑     ↓
|  routeDefinitionRouteLocator defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class]
└─────┘


Action:

Despite circular references being allowed, the dependency cycle between beans could not be broken. Update your application to remove the dependency cycle.

GatewayFilter class

@Component
public class SecurityFilter implements GatewayFilterFactory<SecurityFilter.Config> {

    private final UserApi userApi;

    public SecurityFilter(UserApi userApi) {
        this.userApi = userApi;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            return chain.filter(exchange.mutate().request(exchange.getRequest()).build());
        });
    }

    @Override
    public Class<Config> getConfigClass() {
        return Config.class;
    }

    @Override
    public Config newConfig() {
        Config c = new Config();
        return c;
    }

    public static class Config {
    }
}

pom.xml

    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2021.0.3</spring-cloud.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

Main class

@SpringBootApplication
@ComponentScan("com.example")
@EnableFeignClients(basePackages = {"com.example.feign"})
public class GatewayApplicationMain {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplicationMain.class, args);
    }
}

Feign api class

@FeignClient(name = "user", path="userService/", url="http://localhost:8091/")
public interface UserApi {
    @GetMapping("/getUserByUserName/{userName}")
    ResponseEntity<Object> getUserByUserName(@PathVariable(name = "userName") String userName);
}

Security class

@Configuration
public class SecurityConfig {
    @Bean
    public SecurityWebFilterChain configure(ServerHttpSecurity http) {
        http.csrf().disable()
                .authorizeExchange()
                .anyExchange()
                .authenticated()
                .and()
                .oauth2Login()
                .and().oauth2ResourceServer().jwt();
        return http.build();
    }
}

application.properties file

server.port=8090
spring.main.allow-circular-references=true

Upvotes: 2

Views: 2638

Answers (3)

kernel
kernel

Reputation: 793

private final UserApi userApi;

@Autowired
public SecurityFilter(@Lazy UserApi userApi) {
   this.userApi = userApi;
}

This is how I was able to fix the problem by adding @Lazy.

Upvotes: 6

Josescalia
Josescalia

Reputation: 51

I'm using @Lazy annotation whenever the feign client intent to be use, to avoid Circular Reference Error when using spring-cloud-gateway along with OpenFeign, even in my CustomGatewayFilter :)

 @Component
 public class CustomApiGatewayFilter extends AbstractGatewayFilterFactory<CustomApiGatewayFilter.Config> {
      private static Logger logger = LogManager.getLogger(CustomApiGatewayFilter.class);
      private final JwtConfig jwtConfig;
      private final ThirdPartyFeignClientService thirdPartyFeignClientService;

     public CustomApiGatewayFilter(JwtConfig jwtConfig, @Lazy ThirdPartyFeignClientService thirdPartyFeignClientService) {
        super(Config.class);
        this.jwtConfig = jwtConfig;
        this.thirdPartyFeignClientService = thirdPartyFeignClientService;
     }
   ....
   ....
}

And the usage of this feign client placed on a separate function used by this filter

private String validateToken(String jwtToken) throws MyAppException {
    String json = "";
    ObjectMapper mapper = new ObjectMapper();
    try {
        Claims claims = jwtConfig.getAllClaimsFromToken(jwtToken);
        String additionalInfo = (String) claims.get("additionalInfo");
        JwtPayload payload = mapper.readValue(additionalInfo, JwtPayload.class);
        String username = jwtConfig.getUsernameFromToken(jwtToken);
        if(thirdPartyFeignClientService.validateClient(payload.getClientId(), payload.getClientSecret())){
            return username;
        }else{
            throw new MyAppException("Invalid Client", ErrorCode.GENERIC_FAILURE);
        }
    } catch (ExpiredJwtException e) {
        logger.error(e.getMessage());
        throw new MyAppException(e.getMessage(), ErrorCode.GENERIC_FAILURE);
    } catch (Exception e) {
        logger.error("Other Exception :" + e);
        throw new MyAppException (e.getMessage(), ErrorCode.GENERIC_FAILURE);
    }
}

And it works :)

Upvotes: 0

Herindra Setiawan
Herindra Setiawan

Reputation: 150

If you intend to keep the circular dependency you can add this configuration

spring.main.allow-circular-references: true

Upvotes: 0

Related Questions