Skip to content

Commit 0c12054

Browse files
artembilanspring-builds
authored andcommitted
GH-10988: Align HTTP Cross-Origin with Spring MVC (#11003)
* GH-10988: Align HTTP Cross-Origin with Spring MVC Fixes: #10988 The Spring MVC comes with `allowedOrigins` as empty list and `allowCredentials` as `false` by default. In addition, Spring MVC provides now a flexible `allowedOriginPatterns` * Fix HTTP and WebFlux module to handle the required by Spring MVC defaults for `CrossOrigin` configuration * Fix `http/namespace.adoc` for `One Sentence per Line` (cherry picked from commit d8e2c89)
1 parent c16d479 commit 0c12054

8 files changed

Lines changed: 123 additions & 73 deletions

File tree

spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ private void parseCrossOrigin(Element element, BeanDefinitionBuilder builder) {
220220
if (crossOriginElement != null) {
221221
BeanDefinitionBuilder crossOriginBuilder =
222222
BeanDefinitionBuilder.genericBeanDefinition(CrossOrigin.class);
223-
String[] attributes = {"origin", "allowed-headers", "exposed-headers", "max-age", "method"};
223+
String[] attributes =
224+
{"origin", "origin-patterns", "allowed-headers", "exposed-headers", "max-age", "method"};
224225
for (String crossOriginAttribute : attributes) {
225226
IntegrationNamespaceUtils.setValueIfAttributeDefined(crossOriginBuilder, crossOriginElement,
226227
crossOriginAttribute);

spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,18 @@ public CrossOriginSpec origin(String... origin) {
394394
return this;
395395
}
396396

397+
/**
398+
* Alternative to {@link #origin} that supports more flexible origin
399+
* patterns.
400+
* @param originPatterns the list of allowed origins.
401+
* @return the spec
402+
* @since 6.5.9
403+
*/
404+
public CrossOriginSpec originPatterns(String... originPatterns) {
405+
this.crossOrigin.setOriginPatterns(originPatterns);
406+
return this;
407+
}
408+
397409
/**
398410
* List of request headers that can be used during the actual request.
399411
* <p>This property controls the value of the pre-flight response's

spring-integration-http/src/main/java/org/springframework/integration/http/inbound/CrossOrigin.java

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@
1717
package org.springframework.integration.http.inbound;
1818

1919
import java.util.Arrays;
20+
import java.util.List;
2021

22+
import org.jspecify.annotations.Nullable;
23+
24+
import org.springframework.util.ObjectUtils;
2125
import org.springframework.web.bind.annotation.RequestMethod;
2226

2327
/**
24-
* The mapping to permit cross origin requests (CORS) for {@link HttpRequestHandlingEndpointSupport}.
28+
* The mapping to permit cross-origin requests (CORS) for {@link HttpRequestHandlingEndpointSupport}.
2529
* Provides direct mapping in terms of functionality compared to
2630
* {@link org.springframework.web.bind.annotation.CrossOrigin}.
2731
*
@@ -34,48 +38,83 @@
3438
*/
3539
public class CrossOrigin {
3640

37-
private String[] origin = {"*"};
41+
private String[] origin = {};
42+
43+
private String[] originPatterns = {};
3844

39-
private String[] allowedHeaders = {"*"};
45+
private String[] allowedHeaders = {};
4046

4147
private String[] exposedHeaders = {};
4248

4349
private RequestMethod[] method = {};
4450

45-
private Boolean allowCredentials = true;
51+
private Boolean allowCredentials = false;
4652

47-
private long maxAge = 1800; // NOSONAR magic number
53+
private long maxAge = 1800L;
4854

4955
public void setOrigin(String... origin) {
5056
this.origin = Arrays.copyOf(origin, origin.length);
5157
}
5258

5359
public String[] getOrigin() {
54-
return this.origin; // NOSONAR - expose internals
60+
return this.origin;
61+
}
62+
63+
@Nullable
64+
public List<String> getOriginsList() {
65+
return ObjectUtils.isEmpty(this.origin) ? null : Arrays.asList(this.origin);
66+
}
67+
68+
public void setOriginPatterns(String... originPatterns) {
69+
this.originPatterns = Arrays.copyOf(originPatterns, originPatterns.length);
70+
}
71+
72+
public String[] getOriginPatterns() {
73+
return this.originPatterns;
74+
}
75+
76+
@Nullable
77+
public List<String> getOriginPatternsList() {
78+
return ObjectUtils.isEmpty(this.originPatterns) ? null : Arrays.asList(this.originPatterns);
5579
}
5680

5781
public void setAllowedHeaders(String... allowedHeaders) {
5882
this.allowedHeaders = Arrays.copyOf(allowedHeaders, allowedHeaders.length);
5983
}
6084

6185
public String[] getAllowedHeaders() {
62-
return this.allowedHeaders; // NOSONAR - expose internals
86+
return this.allowedHeaders;
87+
}
88+
89+
@Nullable
90+
public List<String> getAllowedHeadersList() {
91+
return ObjectUtils.isEmpty(this.allowedHeaders) ? null : Arrays.asList(this.allowedHeaders);
6392
}
6493

6594
public void setExposedHeaders(String... exposedHeaders) {
6695
this.exposedHeaders = Arrays.copyOf(exposedHeaders, exposedHeaders.length);
6796
}
6897

6998
public String[] getExposedHeaders() {
70-
return this.exposedHeaders; // NOSONAR - expose internals
99+
return this.exposedHeaders;
100+
}
101+
102+
@Nullable
103+
public List<String> getExposedHeadersList() {
104+
return ObjectUtils.isEmpty(this.exposedHeaders) ? null : Arrays.asList(this.exposedHeaders);
71105
}
72106

73107
public void setMethod(RequestMethod... method) {
74108
this.method = Arrays.copyOf(method, method.length);
75109
}
76110

77111
public RequestMethod[] getMethod() {
78-
return this.method; // NOSONAR - expose internals
112+
return this.method;
113+
}
114+
115+
@Nullable
116+
public List<RequestMethod> getMethodsList() {
117+
return ObjectUtils.isEmpty(this.method) ? null : Arrays.asList(this.method);
79118
}
80119

81120
public void setAllowCredentials(Boolean allowCredentials) {

spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.integration.http.inbound;
1818

1919
import java.lang.reflect.Method;
20-
import java.util.Arrays;
2120
import java.util.HashMap;
2221
import java.util.List;
2322
import java.util.Map;
@@ -159,28 +158,23 @@ private static CorsConfiguration buildCorsConfiguration(CrossOrigin crossOrigin,
159158
for (RequestMethod requestMethod : crossOrigin.getMethod()) {
160159
config.addAllowedMethod(requestMethod.name());
161160
}
162-
config.setAllowedHeaders(Arrays.asList(crossOrigin.getAllowedHeaders()));
163-
config.setExposedHeaders(Arrays.asList(crossOrigin.getExposedHeaders()));
164-
Boolean allowCredentials = crossOrigin.getAllowCredentials();
165-
config.setAllowCredentials(allowCredentials);
166-
List<String> allowedOrigins = Arrays.asList(crossOrigin.getOrigin());
167-
if (Boolean.TRUE.equals(allowCredentials)
168-
&& CollectionUtils.contains(allowedOrigins.iterator(), CorsConfiguration.ALL)) {
169-
config.setAllowedOriginPatterns(allowedOrigins);
170-
}
171-
else {
172-
config.setAllowedOrigins(allowedOrigins);
173-
}
174-
175-
if (crossOrigin.getMaxAge() != -1) {
176-
config.setMaxAge(crossOrigin.getMaxAge());
161+
List<String> allowedHeadersList = crossOrigin.getAllowedHeadersList();
162+
config.setAllowedHeaders(allowedHeadersList);
163+
config.setExposedHeaders(crossOrigin.getExposedHeadersList());
164+
config.setAllowCredentials(crossOrigin.getAllowCredentials());
165+
config.setAllowedOrigins(crossOrigin.getOriginsList());
166+
config.setAllowedOriginPatterns(crossOrigin.getOriginPatternsList());
167+
168+
long maxAge = crossOrigin.getMaxAge();
169+
if (maxAge != -1) {
170+
config.setMaxAge(maxAge);
177171
}
178172
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
179173
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
180174
config.addAllowedMethod(allowedMethod.name());
181175
}
182176
}
183-
if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
177+
if (CollectionUtils.isEmpty(allowedHeadersList)) {
184178
for (NameValueExpression<String> headerExpression :
185179
mappingInfo.getHeadersCondition().getExpressions()) {
186180

spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<xsd:element name="cross-origin" type="crossOriginType" minOccurs="0">
3737
<xsd:annotation>
3838
<xsd:documentation>
39-
Marks this endpoint as permitting cross origin requests (CORS).
39+
Marks this endpoint as permitting cross-origin requests (CORS).
4040
</xsd:documentation>
4141
</xsd:annotation>
4242
</xsd:element>
@@ -716,16 +716,24 @@
716716
Defines configuration for org.springframework.web.cors.CorsConfiguration.
717717
</xsd:documentation>
718718
</xsd:annotation>
719-
<xsd:attribute name="origin" type="xsd:string" default="*">
719+
<xsd:attribute name="origin" type="xsd:string">
720720
<xsd:annotation>
721721
<xsd:documentation>
722722
List of allowed origins. "*" means that all origins are allowed. These values
723723
are placed in the 'Access-Control-Allow-Origin' header of both the pre-flight
724-
and actual responses. Default value is "*".
724+
and actual responses.
725725
</xsd:documentation>
726726
</xsd:annotation>
727727
</xsd:attribute>
728-
<xsd:attribute name="allowed-headers" type="xsd:string" default="*">
728+
<xsd:attribute name="origin-patterns" type="xsd:string">
729+
<xsd:annotation>
730+
<xsd:documentation>
731+
Alternative list to `origin` that supports more flexible
732+
origins patterns with "*" anywhere in the host name in addition to port lists.
733+
</xsd:documentation>
734+
</xsd:annotation>
735+
</xsd:attribute>
736+
<xsd:attribute name="allowed-headers" type="xsd:string">
729737
<xsd:annotation>
730738
<xsd:documentation>
731739
Indicates which request headers can be used during the actual request. "*" means
@@ -747,20 +755,20 @@
747755
<xsd:annotation>
748756
<xsd:documentation>
749757
The HTTP request methods to allow: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
750-
Methods specified here overrides 'supported-methods' ones.
758+
Methods specified here override 'supported-methods' ones.
751759
</xsd:documentation>
752760
</xsd:annotation>
753761
<xsd:simpleType>
754762
<xsd:union memberTypes="httpMethodEnumeration xsd:string"/>
755763
</xsd:simpleType>
756764
</xsd:attribute>
757-
<xsd:attribute name="allow-credentials" default="true">
765+
<xsd:attribute name="allow-credentials">
758766
<xsd:annotation>
759767
<xsd:documentation>
760-
Set to "true" if the browser should include any cookies associated to the domain
768+
Set to "true" if the browser should include any cookies associated with the domain
761769
of the request being annotated, or "false" if it should not. Empty string "" means undefined.
762770
If true, the pre-flight response will include the header
763-
'Access-Control-Allow-Credentials=true'. Default value is "true".
771+
'Access-Control-Allow-Credentials=true'.
764772
</xsd:documentation>
765773
</xsd:annotation>
766774
<xsd:simpleType>
@@ -771,7 +779,7 @@
771779
<xsd:annotation>
772780
<xsd:documentation>
773781
Controls the cache duration for pre-flight responses. Setting this to a reasonable
774-
value can reduce the number of pre-flight request/response interaction required by
782+
value can reduce the amount of pre-flight request/response interaction required by
775783
the browser. This property controls the value of the 'Access-Control-Max-Age' header
776784
in the pre-flight response. Value set to '-1' means undefined.
777785
Default value is 1800 seconds, or 30 minutes.

spring-integration-http/src/test/java/org/springframework/integration/http/inbound/CrossOriginTests.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ public void defaultEndpointWithCrossOrigin() throws Exception {
8484
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
8585
CorsConfiguration config = getCorsConfiguration(chain, false);
8686
assertThat(config).isNotNull();
87-
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
88-
assertThat(config.getAllowedOrigins()).isNull();
89-
assertThat(config.getAllowedOriginPatterns().toArray()).isEqualTo(new String[] {"*"});
90-
assertThat(config.getAllowCredentials()).isTrue();
91-
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"*"});
92-
assertThat(config.getExposedHeaders()).isEmpty();
87+
assertThat(config.getAllowedMethods()).containsOnly("GET");
88+
assertThat(config.getAllowedOrigins()).containsOnly("*");
89+
assertThat(config.getAllowedOriginPatterns()).isNull();
90+
assertThat(config.getAllowCredentials()).isFalse();
91+
assertThat(config.getAllowedHeaders()).containsOnly("*");
92+
assertThat(config.getExposedHeaders()).isNull();
9393
assertThat(config.getMaxAge()).isEqualTo(1800L);
9494
}
9595

@@ -116,12 +116,12 @@ public void preFlightRequest() throws Exception {
116116
HandlerExecutionChain chain = this.handlerMapping.getHandler(this.request);
117117
CorsConfiguration config = getCorsConfiguration(chain, true);
118118
assertThat(config).isNotNull();
119-
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[] {"GET"});
120-
assertThat(config.getAllowedOrigins()).isNull();
121-
assertThat(config.getAllowedOriginPatterns().toArray()).isEqualTo(new String[] {"*"});
122-
assertThat(config.getAllowCredentials()).isTrue();
123-
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] {"*"});
124-
assertThat(config.getExposedHeaders()).isEmpty();
119+
assertThat(config.getAllowedMethods()).containsOnly("GET");
120+
assertThat(config.getAllowedOrigins()).containsOnly("*");
121+
assertThat(config.getAllowedOriginPatterns()).isNull();
122+
assertThat(config.getAllowCredentials()).isFalse();
123+
assertThat(config.getAllowedHeaders()).containsOnly("*");
124+
assertThat(config.getExposedHeaders()).isNull();
125125
assertThat(config.getMaxAge()).isEqualTo(1800L);
126126
}
127127

spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.integration.webflux.inbound;
1818

1919
import java.lang.reflect.Method;
20-
import java.util.Arrays;
2120
import java.util.List;
2221
import java.util.concurrent.atomic.AtomicBoolean;
2322

@@ -159,28 +158,23 @@ private static CorsConfiguration buildCorsConfiguration(CrossOrigin crossOrigin,
159158
for (RequestMethod requestMethod : crossOrigin.getMethod()) {
160159
config.addAllowedMethod(requestMethod.name());
161160
}
162-
config.setAllowedHeaders(Arrays.asList(crossOrigin.getAllowedHeaders()));
163-
config.setExposedHeaders(Arrays.asList(crossOrigin.getExposedHeaders()));
164-
Boolean allowCredentials = crossOrigin.getAllowCredentials();
165-
config.setAllowCredentials(allowCredentials);
166-
List<String> allowedOrigins = Arrays.asList(crossOrigin.getOrigin());
167-
if (Boolean.TRUE.equals(allowCredentials)
168-
&& CollectionUtils.contains(allowedOrigins.iterator(), CorsConfiguration.ALL)) {
169-
config.setAllowedOriginPatterns(allowedOrigins);
170-
}
171-
else {
172-
config.setAllowedOrigins(allowedOrigins);
173-
}
174-
175-
if (crossOrigin.getMaxAge() != -1) {
176-
config.setMaxAge(crossOrigin.getMaxAge());
161+
List<String> allowedHeadersList = crossOrigin.getAllowedHeadersList();
162+
config.setAllowedHeaders(allowedHeadersList);
163+
config.setExposedHeaders(crossOrigin.getExposedHeadersList());
164+
config.setAllowCredentials(crossOrigin.getAllowCredentials());
165+
config.setAllowedOrigins(crossOrigin.getOriginsList());
166+
config.setAllowedOriginPatterns(crossOrigin.getOriginPatternsList());
167+
168+
long maxAge = crossOrigin.getMaxAge();
169+
if (maxAge != -1) {
170+
config.setMaxAge(maxAge);
177171
}
178172
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
179173
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
180174
config.addAllowedMethod(allowedMethod.name());
181175
}
182176
}
183-
if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
177+
if (CollectionUtils.isEmpty(allowedHeadersList)) {
184178
for (NameValueExpression<String> headerExpression :
185179
mappingInfo.getHeadersCondition().getExpressions()) {
186180

src/reference/antora/modules/ROOT/pages/http/namespace.adoc

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,24 +122,27 @@ For this reason, configuring the same path for both Spring Integration and Sprin
122122
== Cross-origin Resource Sharing (CORS) Support
123123

124124
Starting with version 4.2, you can configure the `<http:inbound-channel-adapter>` and `<http:inbound-gateway>` with a `<cross-origin>` element.
125-
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:
125+
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:
126126

127127
* `origin`: List of allowed origins.
128128
The `pass:[*]` means that all origins are allowed.
129129
These values are placed in the `Access-Control-Allow-Origin` header of both the pre-flight and actual responses.
130-
The default value is `pass:[*]`.
130+
The default value is empty.
131+
* `origin-patterns`: List of allowed origin patterns.
132+
Alternative list to `origin` that supports more flexible origins patterns with `pass:[*]` anywhere in the host name in addition to port lists.
133+
The default value is empty.
131134
* `allowed-headers`: Indicates which request headers can be used during the actual request.
132135
The `pass:[*]` means that all headers requested by the client are allowed.
133136
This property controls the value of the pre-flight response's `Access-Control-Allow-Headers` header.
134-
The default value is `pass:[*]`.
137+
The default value is empty.
135138
* `exposed-headers`: List of response headers that the user-agent lets the client access.
136139
This property controls the value of the actual response's `Access-Control-Expose-Headers` header.
137140
* `method`: The HTTP request methods to allow: `GET`, `POST`, `HEAD`, `OPTIONS`, `PUT`, `PATCH`, `DELETE`, `TRACE`.
138-
Methods specified here overrides those in `supported-methods`.
139-
* `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.
140-
An empty string ("") means undefined.
141+
Methods specified here override those in `supported-methods`.
142+
* `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.
143+
An empty string means undefined.
141144
If `true`, the pre-flight response includes the `Access-Control-Allow-Credentials=true` header.
142-
The default value is `true`.
145+
The default value is empty.
143146
* `max-age`: Controls the cache duration for pre-flight responses.
144147
Setting this to a reasonable value can reduce the number of pre-flight request-response interactions required by the browser.
145148
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
470473
With Java DSL this option can be controlled by the `BaseHttpMessageHandlerSpec.encodingMode()` option.
471474
The same configuration applies for similar outbound components in the xref:webflux.adoc[WebFlux module] and xref:ws.adoc[Web Services module].
472475
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`.
473-

0 commit comments

Comments
 (0)