diff --git a/api/src/main/java/io/kafbat/ui/config/CorsGlobalConfiguration.java b/api/src/main/java/io/kafbat/ui/config/CorsGlobalConfiguration.java index d39fda91d..68d819a33 100644 --- a/api/src/main/java/io/kafbat/ui/config/CorsGlobalConfiguration.java +++ b/api/src/main/java/io/kafbat/ui/config/CorsGlobalConfiguration.java @@ -1,5 +1,6 @@ package io.kafbat.ui.config; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; @@ -15,6 +16,9 @@ @Configuration public class CorsGlobalConfiguration { + @Autowired + private CorsProperties corsProperties; + @Bean public WebFilter corsFilter() { return (final ServerWebExchange ctx, final WebFilterChain chain) -> { @@ -22,7 +26,7 @@ public WebFilter corsFilter() { final ServerHttpResponse response = ctx.getResponse(); final HttpHeaders headers = response.getHeaders(); - fillCorsHeader(headers, request); + fillCorsHeader(headers); if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); @@ -33,11 +37,11 @@ public WebFilter corsFilter() { }; } - public static void fillCorsHeader(HttpHeaders responseHeaders, ServerHttpRequest request) { - responseHeaders.add("Access-Control-Allow-Origin", request.getHeaders().getOrigin()); - responseHeaders.add("Access-Control-Allow-Credentials", "true"); - responseHeaders.add("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS"); - responseHeaders.add("Access-Control-Max-Age", "3600"); - responseHeaders.add("Access-Control-Allow-Headers", "Content-Type"); + public void fillCorsHeader(HttpHeaders responseHeaders) { + responseHeaders.add("Access-Control-Allow-Origin", corsProperties.getAllowedOrigins()); + responseHeaders.add("Access-Control-Allow-Credentials", corsProperties.getAllowCredentials()); + responseHeaders.add("Access-Control-Allow-Methods", corsProperties.getAllowedMethods()); + responseHeaders.add("Access-Control-Max-Age", corsProperties.getMaxAge()); + responseHeaders.add("Access-Control-Allow-Headers", corsProperties.getAllowedHeaders()); } } diff --git a/api/src/main/java/io/kafbat/ui/config/CorsProperties.java b/api/src/main/java/io/kafbat/ui/config/CorsProperties.java new file mode 100644 index 000000000..60858cde6 --- /dev/null +++ b/api/src/main/java/io/kafbat/ui/config/CorsProperties.java @@ -0,0 +1,18 @@ +package io.kafbat.ui.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "cors") +@Data +public class CorsProperties { + + private String allowedOrigins; + private String allowedMethods; + private String allowedHeaders; + private String allowCredentials; + private String maxAge; + +} diff --git a/api/src/main/java/io/kafbat/ui/exception/GlobalErrorWebExceptionHandler.java b/api/src/main/java/io/kafbat/ui/exception/GlobalErrorWebExceptionHandler.java index 61236f801..24b26f3a5 100644 --- a/api/src/main/java/io/kafbat/ui/exception/GlobalErrorWebExceptionHandler.java +++ b/api/src/main/java/io/kafbat/ui/exception/GlobalErrorWebExceptionHandler.java @@ -12,6 +12,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler; import org.springframework.boot.web.reactive.error.ErrorAttributes; @@ -38,6 +39,9 @@ @Order(Ordered.HIGHEST_PRECEDENCE) public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { + @Autowired + private CorsGlobalConfiguration corsGlobalConfiguration; + public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes, ApplicationContext applicationContext, ServerCodecConfigurer codecConfigurer) { @@ -151,9 +155,7 @@ private String requestId(ServerRequest request) { } private Consumer headers(ServerRequest request) { - return (HttpHeaders headers) -> { - CorsGlobalConfiguration.fillCorsHeader(headers, request.exchange().getRequest()); - }; + return corsGlobalConfiguration::fillCorsHeader; } private BigDecimal currentTimestamp() { diff --git a/api/src/main/resources/application.yml b/api/src/main/resources/application.yml index ba26c1f9c..ba3178109 100644 --- a/api/src/main/resources/application.yml +++ b/api/src/main/resources/application.yml @@ -19,3 +19,10 @@ logging: reactor.netty.http.server.AccessLog: INFO org.hibernate.validator: WARN +cors: + allowed-origins: "*" + allowed-methods: "GET, PUT, POST, DELETE, OPTIONS" + allowed-headers: "Content-Type" + allow-credentials: "true" + max-age: "3600" + diff --git a/contract/src/main/resources/swagger/kafbat-ui-api.yaml b/contract/src/main/resources/swagger/kafbat-ui-api.yaml index 24530bcb4..bc04b9de5 100644 --- a/contract/src/main/resources/swagger/kafbat-ui-api.yaml +++ b/contract/src/main/resources/swagger/kafbat-ui-api.yaml @@ -4426,3 +4426,42 @@ components: type: object additionalProperties: type: string + cors: + type: object + properties: + allowedOrigins: + type: array + items: + type: string + description: >- + List of allowed origins for CORS. + If not provided, defaults to allowing all origins (`*`) + default: ["*"] + allowedMethods: + type: array + items: + type: string + description: >- + List of allowed HTTP methods for CORS + If not provided, defaults to `GET, POST, PUT, DELETE, OPTIONS`. + default: ["GET", "POST", "PUT", "DELETE", "OPTIONS"] + allowedHeaders: + type: array + items: + type: string + description: >- + List of allowed HTTP headers for CORS. + If not provided, defaults to allowing all headers (`*`). + default: ["*"] + allowCredentials: + type: boolean + description: >- + Whether to allow credentials in CORS requests. + If not provided, defaults to `true` + default: true + maxAge: + type: integer + description: >- + Maximum age (in seconds) for CORS preflight requests. + If not provided, defaults to `3600` seconds. + default: 3600