Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter;
import org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter;
import org.springframework.cloud.gateway.filter.cors.CorsGatewayFilterApplicationListener;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddRequestHeadersIfNotPresentGatewayFilterFactory;
Expand Down Expand Up @@ -207,6 +206,7 @@
* @author Alberto C. Ríos
* @author Olga Maciaszek-Sharma
* @author FuYiNan Guo
* @author Nan Chiu
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.enabled", matchIfMissing = true)
Expand Down Expand Up @@ -292,15 +292,6 @@ public GlobalCorsProperties globalCorsProperties() {
return new GlobalCorsProperties();
}

@Bean
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.globalcors.enabled", matchIfMissing = true)
public CorsGatewayFilterApplicationListener corsGatewayFilterApplicationListener(
GlobalCorsProperties globalCorsProperties, RoutePredicateHandlerMapping routePredicateHandlerMapping,
RouteLocator routeLocator) {
return new CorsGatewayFilterApplicationListener(globalCorsProperties, routePredicateHandlerMapping,
routeLocator);
}

@Bean
@ConditionalOnMissingBean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

package org.springframework.cloud.gateway.handler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import org.jspecify.annotations.Nullable;
Expand All @@ -41,9 +46,15 @@

/**
* @author Spencer Gibb
* @author Fredrich Ombico
* @author Abel Salgado Romero
* @author Yavor Chamov
* @author Nan Chiu
*/
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {

private static final String CORS_METADATA_KEY = "cors";

private final FilteringWebHandler webHandler;

private final RouteLocator routeLocator;
Expand Down Expand Up @@ -109,13 +120,87 @@ protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
});
}

/**
* Returns CORS configuration for the current request.
*
* <p>
* Retrieves route-level CORS config from the matched route's metadata. If present,
* returns it directly (route-specific CORS takes precedence). Otherwise, falls back
* to global CORS configurations.
* </p>
*
* <p>
* Route-level CORS is defined in route metadata under key {@code "cors"} with
* properties: allowedOrigins, allowedOriginPatterns, allowedMethods, allowedHeaders,
* exposedHeaders, allowCredentials, maxAge.
* </p>
* @param handler the handler to check (never {@code null})
* @param exchange the current exchange
* @return the CORS configuration for the handler, or {@code null} if none
*/
@Override
protected @Nullable CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
// TODO: support cors configuration via properties on a route see gh-229
// see RequestMappingHandlerMapping.initCorsConfiguration()
// also see
// https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsWebFilterTests.java
return super.getCorsConfiguration(handler, exchange);
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
if (route == null) {
return super.getCorsConfiguration(handler, exchange);
}
// Route-level CORS config is associated with the matched route, return directly
// if present
Optional<CorsConfiguration> corsConfiguration = getCorsConfiguration(route);

return corsConfiguration.orElseGet(() -> super.getCorsConfiguration(handler, exchange));
}

@SuppressWarnings("unchecked")
private Optional<CorsConfiguration> getCorsConfiguration(Route route) {
Map<String, Object> corsMetadata = (Map<String, Object>) route.getMetadata().get(CORS_METADATA_KEY);
if (corsMetadata != null) {
final CorsConfiguration corsConfiguration = new CorsConfiguration();

findValue(corsMetadata, "allowCredentials")
.ifPresent(value -> corsConfiguration.setAllowCredentials((Boolean) value));
findValue(corsMetadata, "allowedHeaders")
.ifPresent(value -> corsConfiguration.setAllowedHeaders(asList(value)));
findValue(corsMetadata, "allowedMethods")
.ifPresent(value -> corsConfiguration.setAllowedMethods(asList(value)));
findValue(corsMetadata, "allowedOriginPatterns")
.ifPresent(value -> corsConfiguration.setAllowedOriginPatterns(asList(value)));
findValue(corsMetadata, "allowedOrigins")
.ifPresent(value -> corsConfiguration.setAllowedOrigins(asList(value)));
findValue(corsMetadata, "exposedHeaders")
.ifPresent(value -> corsConfiguration.setExposedHeaders(asList(value)));
findValue(corsMetadata, "maxAge").ifPresent(value -> corsConfiguration.setMaxAge(asLong(value)));

return Optional.of(corsConfiguration);
}

return Optional.empty();
}

private Optional<Object> findValue(Map<String, Object> metadata, String key) {
Object value = metadata.get(key);
return Optional.ofNullable(value);
}

private List<String> asList(Object value) {
if (value instanceof String) {
return Arrays.asList((String) value);
}
if (value instanceof Map) {
return new ArrayList<>(((Map<?, String>) value).values());
}
else {
return (List<String>) value;
}
}

private Long asLong(Object value) {
if (value instanceof Integer) {
return ((Integer) value).longValue();
}
else {
return (Long) value;
}
}

// TODO: get desc from factory?
Expand Down
Loading
Loading