diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java
index 263360953fa..79befd63c31 100644
--- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java
+++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java
@@ -220,7 +220,8 @@ private void parseCrossOrigin(Element element, BeanDefinitionBuilder builder) {
if (crossOriginElement != null) {
BeanDefinitionBuilder crossOriginBuilder =
BeanDefinitionBuilder.genericBeanDefinition(CrossOrigin.class);
- String[] attributes = {"origin", "allowed-headers", "exposed-headers", "max-age", "method"};
+ String[] attributes =
+ {"origin", "origin-patterns", "allowed-headers", "exposed-headers", "max-age", "method"};
for (String crossOriginAttribute : attributes) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(crossOriginBuilder, crossOriginElement,
crossOriginAttribute);
diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java
index c48d2441531..c1209e15c35 100644
--- a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java
+++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java
@@ -395,6 +395,18 @@ public CrossOriginSpec origin(String... origin) {
return this;
}
+ /**
+ * Alternative to {@link #origin} that supports more flexible origin
+ * patterns.
+ * @param originPatterns the list of allowed origins.
+ * @return the spec
+ * @since 6.5.9
+ */
+ public CrossOriginSpec originPatterns(String... originPatterns) {
+ this.crossOrigin.setOriginPatterns(originPatterns);
+ return this;
+ }
+
/**
* List of request headers that can be used during the actual request.
*
This property controls the value of the pre-flight response's
diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/CrossOrigin.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/CrossOrigin.java
index 61e6d781056..c3b62eb0c6d 100644
--- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/CrossOrigin.java
+++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/CrossOrigin.java
@@ -17,11 +17,15 @@
package org.springframework.integration.http.inbound;
import java.util.Arrays;
+import java.util.List;
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
/**
- * The mapping to permit cross origin requests (CORS) for {@link HttpRequestHandlingEndpointSupport}.
+ * The mapping to permit cross-origin requests (CORS) for {@link HttpRequestHandlingEndpointSupport}.
* Provides direct mapping in terms of functionality compared to
* {@link org.springframework.web.bind.annotation.CrossOrigin}.
*
@@ -34,24 +38,44 @@
*/
public class CrossOrigin {
- private String[] origin = {"*"};
+ private String[] origin = {};
+
+ private String[] originPatterns = {};
- private String[] allowedHeaders = {"*"};
+ private String[] allowedHeaders = {};
private String[] exposedHeaders = {};
private RequestMethod[] method = {};
- private Boolean allowCredentials = true;
+ private Boolean allowCredentials = false;
- private long maxAge = 1800; // NOSONAR magic number
+ private long maxAge = 1800L;
public void setOrigin(String... origin) {
this.origin = Arrays.copyOf(origin, origin.length);
}
public String[] getOrigin() {
- return this.origin; // NOSONAR - expose internals
+ return this.origin;
+ }
+
+ @Nullable
+ public List getOriginsList() {
+ return ObjectUtils.isEmpty(this.origin) ? null : Arrays.asList(this.origin);
+ }
+
+ public void setOriginPatterns(String... originPatterns) {
+ this.originPatterns = Arrays.copyOf(originPatterns, originPatterns.length);
+ }
+
+ public String[] getOriginPatterns() {
+ return this.originPatterns;
+ }
+
+ @Nullable
+ public List getOriginPatternsList() {
+ return ObjectUtils.isEmpty(this.originPatterns) ? null : Arrays.asList(this.originPatterns);
}
public void setAllowedHeaders(String... allowedHeaders) {
@@ -59,7 +83,12 @@ public void setAllowedHeaders(String... allowedHeaders) {
}
public String[] getAllowedHeaders() {
- return this.allowedHeaders; // NOSONAR - expose internals
+ return this.allowedHeaders;
+ }
+
+ @Nullable
+ public List getAllowedHeadersList() {
+ return ObjectUtils.isEmpty(this.allowedHeaders) ? null : Arrays.asList(this.allowedHeaders);
}
public void setExposedHeaders(String... exposedHeaders) {
@@ -67,7 +96,12 @@ public void setExposedHeaders(String... exposedHeaders) {
}
public String[] getExposedHeaders() {
- return this.exposedHeaders; // NOSONAR - expose internals
+ return this.exposedHeaders;
+ }
+
+ @Nullable
+ public List getExposedHeadersList() {
+ return ObjectUtils.isEmpty(this.exposedHeaders) ? null : Arrays.asList(this.exposedHeaders);
}
public void setMethod(RequestMethod... method) {
@@ -75,7 +109,12 @@ public void setMethod(RequestMethod... method) {
}
public RequestMethod[] getMethod() {
- return this.method; // NOSONAR - expose internals
+ return this.method;
+ }
+
+ @Nullable
+ public List getMethodsList() {
+ return ObjectUtils.isEmpty(this.method) ? null : Arrays.asList(this.method);
}
public void setAllowCredentials(Boolean allowCredentials) {
diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java
index f3af62277e1..2cbf701c390 100644
--- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java
+++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java
@@ -17,7 +17,6 @@
package org.springframework.integration.http.inbound;
import java.lang.reflect.Method;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -161,28 +160,23 @@ private static CorsConfiguration buildCorsConfiguration(CrossOrigin crossOrigin,
for (RequestMethod requestMethod : crossOrigin.getMethod()) {
config.addAllowedMethod(requestMethod.name());
}
- config.setAllowedHeaders(Arrays.asList(crossOrigin.getAllowedHeaders()));
- config.setExposedHeaders(Arrays.asList(crossOrigin.getExposedHeaders()));
- Boolean allowCredentials = crossOrigin.getAllowCredentials();
- config.setAllowCredentials(allowCredentials);
- List allowedOrigins = Arrays.asList(crossOrigin.getOrigin());
- if (Boolean.TRUE.equals(allowCredentials)
- && CollectionUtils.contains(allowedOrigins.iterator(), CorsConfiguration.ALL)) {
- config.setAllowedOriginPatterns(allowedOrigins);
- }
- else {
- config.setAllowedOrigins(allowedOrigins);
- }
-
- if (crossOrigin.getMaxAge() != -1) {
- config.setMaxAge(crossOrigin.getMaxAge());
+ List allowedHeadersList = crossOrigin.getAllowedHeadersList();
+ config.setAllowedHeaders(allowedHeadersList);
+ config.setExposedHeaders(crossOrigin.getExposedHeadersList());
+ config.setAllowCredentials(crossOrigin.getAllowCredentials());
+ config.setAllowedOrigins(crossOrigin.getOriginsList());
+ config.setAllowedOriginPatterns(crossOrigin.getOriginPatternsList());
+
+ long maxAge = crossOrigin.getMaxAge();
+ if (maxAge != -1) {
+ config.setMaxAge(maxAge);
}
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
config.addAllowedMethod(allowedMethod.name());
}
}
- if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
+ if (CollectionUtils.isEmpty(allowedHeadersList)) {
for (NameValueExpression headerExpression :
mappingInfo.getHeadersCondition().getExpressions()) {
diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd
index 2f2db81bb5c..d85d8f1b5f2 100644
--- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd
+++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd
@@ -36,7 +36,7 @@
- Marks this endpoint as permitting cross origin requests (CORS).
+ Marks this endpoint as permitting cross-origin requests (CORS).
@@ -716,16 +716,24 @@
Defines configuration for org.springframework.web.cors.CorsConfiguration.
-
+
List of allowed origins. "*" means that all origins are allowed. These values
are placed in the 'Access-Control-Allow-Origin' header of both the pre-flight
- and actual responses. Default value is "*".
+ and actual responses.
-
+
+
+
+ Alternative list to `origin` that supports more flexible
+ origins patterns with "*" anywhere in the host name in addition to port lists.
+
+
+
+
Indicates which request headers can be used during the actual request. "*" means
@@ -747,20 +755,20 @@
The HTTP request methods to allow: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
- Methods specified here overrides 'supported-methods' ones.
+ Methods specified here override 'supported-methods' ones.
-
+
- Set to "true" if the browser should include any cookies associated to the domain
+ Set to "true" if the browser should include any cookies associated with the domain
of the request being annotated, or "false" if it should not. Empty string "" means undefined.
If true, the pre-flight response will include the header
- 'Access-Control-Allow-Credentials=true'. Default value is "true".
+ 'Access-Control-Allow-Credentials=true'.
@@ -771,7 +779,7 @@
Controls the cache duration for pre-flight responses. Setting this to a reasonable
- value can reduce the number of pre-flight request/response interaction required by
+ value can reduce the amount of pre-flight request/response interaction required by
the browser. This property controls the value of the 'Access-Control-Max-Age' header
in the pre-flight response. Value set to '-1' means undefined.
Default value is 1800 seconds, or 30 minutes.
diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/CrossOriginTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/CrossOriginTests.java
index 06bd0987fbb..7c391745a9b 100644
--- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/CrossOriginTests.java
+++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/CrossOriginTests.java
@@ -85,12 +85,12 @@ public void defaultEndpointWithCrossOrigin() throws Exception {
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
CorsConfiguration config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
- assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
- assertThat(config.getAllowedOrigins()).isNull();
- assertThat(config.getAllowedOriginPatterns().toArray()).isEqualTo(new String[] {"*"});
- assertThat(config.getAllowCredentials()).isTrue();
- assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"*"});
- assertThat(config.getExposedHeaders()).isEmpty();
+ assertThat(config.getAllowedMethods()).containsOnly("GET");
+ assertThat(config.getAllowedOrigins()).containsOnly("*");
+ assertThat(config.getAllowedOriginPatterns()).isNull();
+ assertThat(config.getAllowCredentials()).isFalse();
+ assertThat(config.getAllowedHeaders()).containsOnly("*");
+ assertThat(config.getExposedHeaders()).isNull();
assertThat(config.getMaxAge()).isEqualTo(1800L);
}
@@ -117,12 +117,12 @@ public void preFlightRequest() throws Exception {
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
CorsConfiguration config = getCorsConfiguration(chain, true);
assertThat(config).isNotNull();
- assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
- assertThat(config.getAllowedOrigins()).isNull();
- assertThat(config.getAllowedOriginPatterns().toArray()).isEqualTo(new String[] {"*"});
- assertThat(config.getAllowCredentials()).isTrue();
- assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"*"});
- assertThat(config.getExposedHeaders()).isEmpty();
+ assertThat(config.getAllowedMethods()).containsOnly("GET");
+ assertThat(config.getAllowedOrigins()).containsOnly("*");
+ assertThat(config.getAllowedOriginPatterns()).isNull();
+ assertThat(config.getAllowCredentials()).isFalse();
+ assertThat(config.getAllowedHeaders()).containsOnly("*");
+ assertThat(config.getExposedHeaders()).isNull();
assertThat(config.getMaxAge()).isEqualTo(1800L);
}
diff --git a/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java b/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java
index 53fcc6388a6..001b4a1c4a6 100644
--- a/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java
+++ b/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java
@@ -17,7 +17,6 @@
package org.springframework.integration.webflux.inbound;
import java.lang.reflect.Method;
-import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -166,28 +165,23 @@ private static CorsConfiguration buildCorsConfiguration(CrossOrigin crossOrigin,
for (RequestMethod requestMethod : crossOrigin.getMethod()) {
config.addAllowedMethod(requestMethod.name());
}
- config.setAllowedHeaders(Arrays.asList(crossOrigin.getAllowedHeaders()));
- config.setExposedHeaders(Arrays.asList(crossOrigin.getExposedHeaders()));
- Boolean allowCredentials = crossOrigin.getAllowCredentials();
- config.setAllowCredentials(allowCredentials);
- List allowedOrigins = Arrays.asList(crossOrigin.getOrigin());
- if (Boolean.TRUE.equals(allowCredentials)
- && CollectionUtils.contains(allowedOrigins.iterator(), CorsConfiguration.ALL)) {
- config.setAllowedOriginPatterns(allowedOrigins);
- }
- else {
- config.setAllowedOrigins(allowedOrigins);
- }
-
- if (crossOrigin.getMaxAge() != -1) {
- config.setMaxAge(crossOrigin.getMaxAge());
+ List allowedHeadersList = crossOrigin.getAllowedHeadersList();
+ config.setAllowedHeaders(allowedHeadersList);
+ config.setExposedHeaders(crossOrigin.getExposedHeadersList());
+ config.setAllowCredentials(crossOrigin.getAllowCredentials());
+ config.setAllowedOrigins(crossOrigin.getOriginsList());
+ config.setAllowedOriginPatterns(crossOrigin.getOriginPatternsList());
+
+ long maxAge = crossOrigin.getMaxAge();
+ if (maxAge != -1) {
+ config.setMaxAge(maxAge);
}
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
config.addAllowedMethod(allowedMethod.name());
}
}
- if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
+ if (CollectionUtils.isEmpty(allowedHeadersList)) {
for (NameValueExpression headerExpression :
mappingInfo.getHeadersCondition().getExpressions()) {
diff --git a/src/reference/antora/modules/ROOT/pages/http/namespace.adoc b/src/reference/antora/modules/ROOT/pages/http/namespace.adoc
index 6bdae19bc7f..2360a1961b0 100644
--- a/src/reference/antora/modules/ROOT/pages/http/namespace.adoc
+++ b/src/reference/antora/modules/ROOT/pages/http/namespace.adoc
@@ -122,24 +122,27 @@ For this reason, configuring the same path for both Spring Integration and Sprin
== Cross-origin Resource Sharing (CORS) Support
Starting with version 4.2, you can configure the `` and `` with a `` element.
-It represents the same options as Spring MVC's `@CrossOrigin` for `@Controller` annotations and allows the configuration of cross-origin resource sharing (CORS) for Spring Integration HTTP endpoints:
+It represents the same options as Spring MVC `@CrossOrigin` for `@Controller` annotations and allows the configuration of cross-origin resource sharing (CORS) for Spring Integration HTTP endpoints:
* `origin`: List of allowed origins.
The `pass:[*]` means that all origins are allowed.
These values are placed in the `Access-Control-Allow-Origin` header of both the pre-flight and actual responses.
-The default value is `pass:[*]`.
+The default value is empty.
+* `origin-patterns`: List of allowed origin patterns.
+Alternative list to `origin` that supports more flexible origins patterns with `pass:[*]` anywhere in the host name in addition to port lists.
+The default value is empty.
* `allowed-headers`: Indicates which request headers can be used during the actual request.
The `pass:[*]` means that all headers requested by the client are allowed.
This property controls the value of the pre-flight response's `Access-Control-Allow-Headers` header.
-The default value is `pass:[*]`.
+The default value is empty.
* `exposed-headers`: List of response headers that the user-agent lets the client access.
This property controls the value of the actual response's `Access-Control-Expose-Headers` header.
* `method`: The HTTP request methods to allow: `GET`, `POST`, `HEAD`, `OPTIONS`, `PUT`, `PATCH`, `DELETE`, `TRACE`.
-Methods specified here overrides those in `supported-methods`.
-* `allow-credentials`: Set to `true` if the browser should include any cookies associated to the domain of the request or `false` if it should not.
-An empty string ("") means undefined.
+Methods specified here override those in `supported-methods`.
+* `allow-credentials`: Set to `true` if the browser should include any cookies associated with the domain of the request or `false` if it should not.
+An empty string means undefined.
If `true`, the pre-flight response includes the `Access-Control-Allow-Credentials=true` header.
-The default value is `true`.
+The default value is empty.
* `max-age`: Controls the cache duration for pre-flight responses.
Setting this to a reasonable value can reduce the number of pre-flight request-response interactions required by the browser.
This property controls the value of the `Access-Control-Max-Age` header in the pre-flight response.
@@ -470,4 +473,3 @@ If you wish to partially encode some part of the URL, use an `expression` within
With Java DSL this option can be controlled by the `BaseHttpMessageHandlerSpec.encodingMode()` option.
The same configuration applies for similar outbound components in the xref:webflux.adoc[WebFlux module] and xref:ws.adoc[Web Services module].
For much sophisticated scenarios it is recommended to configure an `UriTemplateHandler` on the externally provided `RestTemplate`; or in case of WebFlux - `WebClient` with it `UriBuilderFactory`.
-