Skip to content

CORS configuration is based on outdated RouteDefinitions when using Redis and RefreshRoutesEvent #3774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
PeterMue opened this issue Apr 17, 2025 · 0 comments · Fixed by #3778
Labels
Milestone

Comments

@PeterMue
Copy link
Contributor

 Describe the bug
When using the RedisRouteDefinitionRepository and the actuator endpoint /gateway/refresh to refresh the routes, the per-route CORS configuration is done before the actual reload of the routes from the redis completes and thus the CORS configuration is based on the previous state of the route.

This happens due to the asynchronous behaviour of the lettuce driver that loads the RouteDefintions from Redis.

The timeline is as follows:

  1. POST to the Actuator /gateway/refresh Endpoint emits a RefreshRoutesEvent
  2. Method org.springframework.cloud.gateway.route.CachingRouteLocator.onApplicationEvent() is processed
    (which contains allRoutes.subscribe(list -> updateCache(Flux.fromIterable(list)), this::handleRefreshError);)
  3. Method org.springframework.cloud.gateway.filter.cors.CorsGatewayFilterApplicationListener.onApplicationEvent() is
    processed
  4. The subscribe callback from 2. is executed which updates the cache

Setting some Breakpoints that just log the current Thread reveals exactly this sequence:

Method 'org.springframework.cloud.gateway.route.CachingRouteLocator.onApplicationEvent()' entered at org.springframework.cloud.gateway.route.CachingRouteLocator.onApplicationEvent(CachingRouteLocator.java:87)
reactor-http-nio-2 -> CachingRouteLocator
Method 'org.springframework.cloud.gateway.filter.cors.CorsGatewayFilterApplicationListener.onApplicationEvent()' entered at org.springframework.cloud.gateway.filter.cors.CorsGatewayFilterApplicationListener.onApplicationEvent(CorsGatewayFilterApplicationListener.java:65)
reactor-http-nio-2 -> CorsGatewayFilterApplicationListener
Breakpoint reached at org.springframework.cloud.gateway.route.CachingRouteLocator.lambda$onApplicationEvent$2(CachingRouteLocator.java:98)
lettuce-nioEventLoop-5-1 -> CachingRouteLocator

As the fetch from redis runs async inside the lettuce EventLoop, there is no guarantee of the correct order of execution.

Versions

  • org.springframework.boot:spring-boot-starter-parent:3.4.4
  • org.springframework.boot:spring-boot-starter-data-redis-reactive:3.4.4
  • org.springframework.boot:spring-boot-starter-actuator:3.4.4
  • org.springframework.cloud:spring-cloud-dependencies:2024.0.1
  • org.springframework.cloud:spring-cloud-starter-gateway:4.2.1

Sample

  1. Download https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.4.4&packaging=jar&jvmVersion=17&groupId=de.nuernberger.apigw.cors&artifactId=cors-sample&name=cors-sample&description=CORS%20Refresh%20Error%20Sample&packageName=de.nuernberger.apigw.cors.cors-sample&dependencies=cloud-gateway-reactive,actuator
  2. Configure application.properties
    spring.application.name=cors-sample
    management.endpoint.gateway.access=unrestricted
    management.endpoints.web.exposure.include=gateway
    spring.cloud.gateway.redis-route-definition-repository.enabled=true
    
  3. Create a Route via /actuator/gateway/routes/cors-sample
    POST http://localhost:8080/actuator/gateway/routes/cors-sample
    Content-Type: application/json
    
    {
       "id": "cors-sample",
       "uri": "http://localhost:9000/health",
       "predicates": [{
          "name": "Path",
          "args": {
             "patterns": "/cors-sample"
          }
       }],
       "filters": [],
       "metadata": {
          "cors": {
             "allowCredentials": false,
             "allowedOrigins": "*",
             "allowedMethods": [
                "GET",
                "POST"
             ],
             "allowedHeaders": "*",
             "maxAge": 1
          }
       }
    }
    
  4. Restart Application
  5. Verify Route is loaded via GET http://localhost:8080/actuator/gateway/routes/cors-sample
  6. Verify that a CORS Preflight request to /cors-sample returns the correct CORS headers
    OPTIONS http://localhost:8080/cors-sample
    Origin: http://localhost:8080
    Access-Control-Request-Method: GET
    
    should always return a 200 regardless of the origin
  7. Modify CORS configuration: set an explicit origin
    POST http://localhost:8080/actuator/gateway/routes/cors-sample
    Content-Type: application/json
    
    {
       "id": "cors-sample",
       "uri": "http://localhost:9000/health",
       "predicates": [{
          "name": "Path",
          "args": {
             "patterns": "/cors-sample"
          }
       }],
       "filters": [],
       "metadata": {
          "cors": {
             "allowCredentials": false,
             "allowedOrigins": "http://foobar",
             "allowedMethods": [
                "GET",
                "POST"
             ],
             "allowedHeaders": "*",
             "maxAge": 1
          }
       }
    } 
    
  8. Refresh via the actuator endpoint /actuator/gateway/refresh
    POST http://localhost:8080/actuator/gateway/refresh
    
  9. Verify that the route itself is returned correctly via
    GET http://localhost:8080/actuator/gateway/routes/cors-sample
    
    this should show the changed origin
  10. Although it seems correct, the internal state of the CORS configuration is still the old one. Which can be verified by sending a CORS preflight request to /cors-sample with the old origin
    OPTIONS http://localhost:8080/cors-sample
    Origin: http://localhost:8080
    Access-Control-Request-Method: GET
    
    this should return a 403 as the origin is not allowed anymore but returns a 200 and the old value for Access-Control-Allow-Origin: *
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue Apr 22, 2025
Configure CORS after refresh routes completed on RefreshRoutesResultEvent

Signed-off-by: PeterMue <[email protected]>
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue Apr 22, 2025
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue Apr 22, 2025
Configure CORS after refresh routes completed on RefreshRoutesResultEvent

Signed-off-by: PeterMue <[email protected]>
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue Apr 22, 2025
Configure CORS after refresh routes completed on RefreshRoutesResultEvent

Signed-off-by: PeterMue <[email protected]>
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue Apr 22, 2025
Configure CORS after refresh routes completed on RefreshRoutesResultEvent

Signed-off-by: PeterMue <[email protected]>
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue May 5, 2025
Configure CORS after refresh routes completed on RefreshRoutesResultEvent

Signed-off-by: PeterMue <[email protected]>
@ryanjbaxter ryanjbaxter added this to the 4.1.8 milestone May 12, 2025
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue May 13, 2025
Configure CORS after refresh routes completed on RefreshRoutesResultEvent

Signed-off-by: PeterMue <[email protected]>
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue May 14, 2025
Configure CORS after refresh routes completed on RefreshRoutesResultEvent

Signed-off-by: PeterMue <[email protected]>
PeterMue added a commit to PeterMue/spring-cloud-gateway that referenced this issue May 14, 2025
ryanjbaxter added a commit that referenced this issue May 14, 2025
…redis-refresh

Fix CORS configuration timing issue with RedisRouteDefinitionRepository and RefreshRoutesEvent
@github-project-automation github-project-automation bot moved this to Done in 2025.0.0 May 14, 2025
@github-project-automation github-project-automation bot moved this to Done in 2023.0.6 May 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Done
Status: No status
Status: Done
2 participants