Resolving Duplicate Access-Control-Allow-Origin Header Issues in Spring Cloud Gateway
This article explains why Spring Cloud Gateway may return multiple Access-Control-Allow-Origin headers, analyzes the internal processing flow that causes the duplication, and provides two practical solutions—using DedupeResponseHeader configuration or a custom GlobalFilter—to ensure a single, correct CORS header in responses.
When developing front‑end and back‑end separation with Spring Cloud, developers often encounter CORS errors, especially the "multiple Access-Control-Allow-Origin" header problem. The article first demonstrates the issue with example requests and responses, showing that both the microservice and the gateway can add the same CORS headers, resulting in duplicates that browsers reject.
Problem
In a Spring Cloud project, the front‑end accesses services directly or via Spring Cloud Gateway. Adding a global CORS filter in the service works, but the gateway also adds its own CORS headers, leading to two Access-Control-Allow-Origin and two Vary headers in the final response.
Analysis
The request first passes through DispatcherHandler, which delegates to RoutePredicateHandlerMapping. The handler then invokes DefaultCorsProcessor to apply CORS configuration from application.yml. This processor adds the Vary and Access-Control-Allow-Origin headers. Later, NettyRoutingFilter forwards the request to the downstream service and merges its response headers without deduplication, causing the duplicate headers.
Key code excerpts:
@Bean
public CorsFilter corsFilter() {
logger.debug("CORS限制打开");
CorsConfiguration config = new CorsConfiguration();
// Only in dev environment set to *
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
} spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowedMethods: "*"The DefaultCorsProcessor.process method checks for existing Access-Control-Allow-Origin and skips adding another if present, but because the gateway merges headers after this step, duplication still occurs.
Solution 1: Use DedupeResponseHeader
Configure the gateway to deduplicate specific headers:
spring:
cloud:
gateway:
default-filters:
- DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials RETAIN_FIRSTThe DedupeResponseHeaderGatewayFilterFactory applies strategies such as RETAIN_FIRST, RETAIN_LAST, or RETAIN_UNIQUE to remove duplicate values. For most cases, RETAIN_FIRST keeps the header set by the gateway configuration.
Solution 2: Custom GlobalFilter
Implement a filter that runs after the response is written and manually removes duplicate CORS headers:
@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
exchange.getResponse().getHeaders().entrySet().stream()
.filter(kv -> kv.getValue() != null && kv.getValue().size() > 1)
.filter(kv -> kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
|| kv.getKey().equals(HttpHeaders.VARY))
.forEach(kv -> {
if (kv.getKey().equals(HttpHeaders.VARY)) {
kv.setValue(kv.getValue().stream().distinct().collect(Collectors.toList()));
} else {
List<String> value = new ArrayList<>();
if (kv.getValue().contains("*")) {
value.add("*");
} else {
value.add(kv.getValue().get(0));
}
kv.setValue(value);
}
});
}));
}
}When ordering filters, ensure this custom filter runs after NettyWriteResponseFilter by setting a higher order value.
Both approaches effectively eliminate duplicate CORS headers, allowing the front‑end to receive a single, valid Access-Control-Allow-Origin header and avoid browser CORS errors.
Note: The original article also includes a promotional giveaway for "实战Alibaba Sentinel" books, which is unrelated to the technical content.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
