Skip to content

Commit 84138ab

Browse files
committed
Avoid rejecting same-origin requests detected as CORS requests
Browsers like Chrome or Safari include an Origin header for same-origin POST/PUT/DELETE requests, not only for cross-origin requests. Before this commit, these same-origin requests would have been detected as potential cross-origin requests, and rejected if the same-origin domain is not part of the configured allowedOrigins. This commit avoid to reject same-origin requests by reusing the logic introduced in Spring 4.1 for detecting reliably Websocket/SockJS same-origin requests with the WebUtils.isValidOrigin() method. This logic has been extracted in a new WebUtils.isSameOrigin() method. Issue: SPR-13206
1 parent 882fe12 commit 84138ab

File tree

3 files changed

+57
-28
lines changed

3 files changed

+57
-28
lines changed

spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,17 @@
3535
import org.springframework.http.server.ServletServerHttpRequest;
3636
import org.springframework.http.server.ServletServerHttpResponse;
3737
import org.springframework.util.CollectionUtils;
38+
import org.springframework.web.util.WebUtils;
3839

3940
/**
4041
* Default implementation of {@link CorsProcessor}, as defined by the
4142
* <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>.
4243
*
4344
* <p>Note that when input {@link CorsConfiguration} is {@code null}, this
4445
* implementation does not reject simple or actual requests outright but simply
45-
* avoid adding CORS headers to the response.
46+
* avoid adding CORS headers to the response. CORS processing is also skipped
47+
* if the response already contains CORS headers, or if the request is detected
48+
* as a same-origin one.
4649
*
4750
* @author Sebastien Deleuze
4851
* @author Rossen Stoyanhcev
@@ -66,12 +69,16 @@ public boolean processRequest(CorsConfiguration config, HttpServletRequest reque
6669
ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
6770
ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
6871

72+
if (WebUtils.isSameOrigin(serverRequest)) {
73+
logger.debug("Skip CORS processing, request is a same-origin one");
74+
return true;
75+
}
6976
if (responseHasCors(serverResponse)) {
77+
logger.debug("Skip CORS processing, response already contains \"Access-Control-Allow-Origin\" header");
7078
return true;
7179
}
7280

7381
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
74-
7582
if (config == null) {
7683
if (preFlightRequest) {
7784
rejectRequest(serverResponse);
@@ -93,9 +100,6 @@ private boolean responseHasCors(ServerHttpResponse response) {
93100
catch (NullPointerException npe) {
94101
// SPR-11919 and https://issues.jboss.org/browse/WFLY-3474
95102
}
96-
if (hasAllowOrigin) {
97-
logger.debug("Skip adding CORS headers, response already contains \"Access-Control-Allow-Origin\"");
98-
}
99103
return hasAllowOrigin;
100104
}
101105

spring-web/src/main/java/org/springframework/web/util/WebUtils.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -789,15 +789,30 @@ public static boolean isValidOrigin(HttpRequest request, Collection<String> allo
789789
return true;
790790
}
791791
else if (CollectionUtils.isEmpty(allowedOrigins)) {
792-
UriComponents actualUrl = UriComponentsBuilder.fromHttpRequest(request).build();
793-
UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
794-
return (actualUrl.getHost().equals(originUrl.getHost()) && getPort(actualUrl) == getPort(originUrl));
792+
return isSameOrigin(request);
795793
}
796794
else {
797795
return allowedOrigins.contains(origin);
798796
}
799797
}
800798

799+
/**
800+
* Check if the request is a same-origin one, based on {@code Origin}, {@code Host},
801+
* {@code Forwarded} and {@code X-Forwarded-Host} headers.
802+
* @return {@code true} if the request is a same-origin one, {@code false} in case
803+
* of cross-origin request.
804+
* @since 4.2
805+
*/
806+
public static boolean isSameOrigin(HttpRequest request) {
807+
String origin = request.getHeaders().getOrigin();
808+
if (origin == null) {
809+
return true;
810+
}
811+
UriComponents actualUrl = UriComponentsBuilder.fromHttpRequest(request).build();
812+
UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
813+
return (actualUrl.getHost().equals(originUrl.getHost()) && getPort(actualUrl) == getPort(originUrl));
814+
}
815+
801816
private static int getPort(UriComponents component) {
802817
int port = component.getPort();
803818
if (port == -1) {

spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -106,45 +106,55 @@ public void parseMatrixVariablesString() {
106106
}
107107

108108
@Test
109-
public void isValidOriginSuccess() {
110-
109+
public void isValidOrigin() {
111110
List<String> allowed = Collections.emptyList();
112-
assertTrue(checkOrigin("mydomain1.com", -1, "http://mydomain1.com", allowed));
113-
assertTrue(checkOrigin("mydomain1.com", -1, "http://mydomain1.com:80", allowed));
114-
assertTrue(checkOrigin("mydomain1.com", 443, "https://mydomain1.com", allowed));
115-
assertTrue(checkOrigin("mydomain1.com", 443, "https://mydomain1.com:443", allowed));
116-
assertTrue(checkOrigin("mydomain1.com", 123, "http://mydomain1.com:123", allowed));
117-
assertTrue(checkOrigin("mydomain1.com", -1, "ws://mydomain1.com", allowed));
118-
assertTrue(checkOrigin("mydomain1.com", 443, "wss://mydomain1.com", allowed));
111+
assertTrue(checkValidOrigin("mydomain1.com", -1, "http://mydomain1.com", allowed));
112+
assertFalse(checkValidOrigin("mydomain1.com", -1, "http://mydomain2.com", allowed));
119113

120114
allowed = Collections.singletonList("*");
121-
assertTrue(checkOrigin("mydomain1.com", -1, "http://mydomain2.com", allowed));
115+
assertTrue(checkValidOrigin("mydomain1.com", -1, "http://mydomain2.com", allowed));
122116

123117
allowed = Collections.singletonList("http://mydomain1.com");
124-
assertTrue(checkOrigin("mydomain2.com", -1, "http://mydomain1.com", allowed));
118+
assertTrue(checkValidOrigin("mydomain2.com", -1, "http://mydomain1.com", allowed));
119+
assertFalse(checkValidOrigin("mydomain2.com", -1, "http://mydomain3.com", allowed));
125120
}
126121

127122
@Test
128-
public void isValidOriginFailure() {
123+
public void isSameOrigin() {
124+
assertTrue(checkSameOrigin("mydomain1.com", -1, "http://mydomain1.com"));
125+
assertTrue(checkSameOrigin("mydomain1.com", -1, "http://mydomain1.com:80"));
126+
assertTrue(checkSameOrigin("mydomain1.com", 443, "https://mydomain1.com"));
127+
assertTrue(checkSameOrigin("mydomain1.com", 443, "https://mydomain1.com:443"));
128+
assertTrue(checkSameOrigin("mydomain1.com", 123, "http://mydomain1.com:123"));
129+
assertTrue(checkSameOrigin("mydomain1.com", -1, "ws://mydomain1.com"));
130+
assertTrue(checkSameOrigin("mydomain1.com", 443, "wss://mydomain1.com"));
131+
132+
assertFalse(checkSameOrigin("mydomain1.com", -1, "http://mydomain2.com"));
133+
assertFalse(checkSameOrigin("mydomain1.com", -1, "https://mydomain1.com"));
134+
assertFalse(checkSameOrigin("mydomain1.com", -1, "invalid-origin"));
135+
}
129136

130-
List<String> allowed = Collections.emptyList();
131-
assertFalse(checkOrigin("mydomain1.com", -1, "http://mydomain2.com", allowed));
132-
assertFalse(checkOrigin("mydomain1.com", -1, "https://mydomain1.com", allowed));
133-
assertFalse(checkOrigin("mydomain1.com", -1, "invalid-origin", allowed));
134137

135-
allowed = Collections.singletonList("http://mydomain1.com");
136-
assertFalse(checkOrigin("mydomain2.com", -1, "http://mydomain3.com", allowed));
138+
private boolean checkValidOrigin(String serverName, int port, String originHeader, List<String> allowed) {
139+
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
140+
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
141+
servletRequest.setServerName(serverName);
142+
if (port != -1) {
143+
servletRequest.setServerPort(port);
144+
}
145+
request.getHeaders().set(HttpHeaders.ORIGIN, originHeader);
146+
return WebUtils.isValidOrigin(request, allowed);
137147
}
138148

139-
private boolean checkOrigin(String serverName, int port, String originHeader, List<String> allowed) {
149+
private boolean checkSameOrigin(String serverName, int port, String originHeader) {
140150
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
141151
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
142152
servletRequest.setServerName(serverName);
143153
if (port != -1) {
144154
servletRequest.setServerPort(port);
145155
}
146156
request.getHeaders().set(HttpHeaders.ORIGIN, originHeader);
147-
return WebUtils.isValidOrigin(request, allowed);
157+
return WebUtils.isSameOrigin(request);
148158
}
149159

150160
}

0 commit comments

Comments
 (0)