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
This commit is contained in:
@@ -35,6 +35,7 @@ import org.springframework.http.server.ServerHttpResponse;
|
|||||||
import org.springframework.http.server.ServletServerHttpRequest;
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
import org.springframework.http.server.ServletServerHttpResponse;
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of {@link CorsProcessor}, as defined by the
|
* Default implementation of {@link CorsProcessor}, as defined by the
|
||||||
@@ -42,7 +43,9 @@ import org.springframework.util.CollectionUtils;
|
|||||||
*
|
*
|
||||||
* <p>Note that when input {@link CorsConfiguration} is {@code null}, this
|
* <p>Note that when input {@link CorsConfiguration} is {@code null}, this
|
||||||
* implementation does not reject simple or actual requests outright but simply
|
* implementation does not reject simple or actual requests outright but simply
|
||||||
* avoid adding CORS headers to the response.
|
* avoid adding CORS headers to the response. CORS processing is also skipped
|
||||||
|
* if the response already contains CORS headers, or if the request is detected
|
||||||
|
* as a same-origin one.
|
||||||
*
|
*
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @author Rossen Stoyanhcev
|
* @author Rossen Stoyanhcev
|
||||||
@@ -66,12 +69,16 @@ public class DefaultCorsProcessor implements CorsProcessor {
|
|||||||
ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
|
ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
|
||||||
ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
|
ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
|
||||||
|
|
||||||
|
if (WebUtils.isSameOrigin(serverRequest)) {
|
||||||
|
logger.debug("Skip CORS processing, request is a same-origin one");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (responseHasCors(serverResponse)) {
|
if (responseHasCors(serverResponse)) {
|
||||||
|
logger.debug("Skip CORS processing, response already contains \"Access-Control-Allow-Origin\" header");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
|
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
|
||||||
|
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
if (preFlightRequest) {
|
if (preFlightRequest) {
|
||||||
rejectRequest(serverResponse);
|
rejectRequest(serverResponse);
|
||||||
@@ -93,9 +100,6 @@ public class DefaultCorsProcessor implements CorsProcessor {
|
|||||||
catch (NullPointerException npe) {
|
catch (NullPointerException npe) {
|
||||||
// SPR-11919 and https://issues.jboss.org/browse/WFLY-3474
|
// SPR-11919 and https://issues.jboss.org/browse/WFLY-3474
|
||||||
}
|
}
|
||||||
if (hasAllowOrigin) {
|
|
||||||
logger.debug("Skip adding CORS headers, response already contains \"Access-Control-Allow-Origin\"");
|
|
||||||
}
|
|
||||||
return hasAllowOrigin;
|
return hasAllowOrigin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -789,15 +789,30 @@ public abstract class WebUtils {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (CollectionUtils.isEmpty(allowedOrigins)) {
|
else if (CollectionUtils.isEmpty(allowedOrigins)) {
|
||||||
UriComponents actualUrl = UriComponentsBuilder.fromHttpRequest(request).build();
|
return isSameOrigin(request);
|
||||||
UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
|
|
||||||
return (actualUrl.getHost().equals(originUrl.getHost()) && getPort(actualUrl) == getPort(originUrl));
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return allowedOrigins.contains(origin);
|
return allowedOrigins.contains(origin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the request is a same-origin one, based on {@code Origin}, {@code Host},
|
||||||
|
* {@code Forwarded} and {@code X-Forwarded-Host} headers.
|
||||||
|
* @return {@code true} if the request is a same-origin one, {@code false} in case
|
||||||
|
* of cross-origin request.
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public static boolean isSameOrigin(HttpRequest request) {
|
||||||
|
String origin = request.getHeaders().getOrigin();
|
||||||
|
if (origin == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
UriComponents actualUrl = UriComponentsBuilder.fromHttpRequest(request).build();
|
||||||
|
UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
|
||||||
|
return (actualUrl.getHost().equals(originUrl.getHost()) && getPort(actualUrl) == getPort(originUrl));
|
||||||
|
}
|
||||||
|
|
||||||
private static int getPort(UriComponents component) {
|
private static int getPort(UriComponents component) {
|
||||||
int port = component.getPort();
|
int port = component.getPort();
|
||||||
if (port == -1) {
|
if (port == -1) {
|
||||||
|
|||||||
@@ -106,37 +106,36 @@ public class WebUtilsTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isValidOriginSuccess() {
|
public void isValidOrigin() {
|
||||||
|
|
||||||
List<String> allowed = Collections.emptyList();
|
List<String> allowed = Collections.emptyList();
|
||||||
assertTrue(checkOrigin("mydomain1.com", -1, "http://mydomain1.com", allowed));
|
assertTrue(checkValidOrigin("mydomain1.com", -1, "http://mydomain1.com", allowed));
|
||||||
assertTrue(checkOrigin("mydomain1.com", -1, "http://mydomain1.com:80", allowed));
|
assertFalse(checkValidOrigin("mydomain1.com", -1, "http://mydomain2.com", allowed));
|
||||||
assertTrue(checkOrigin("mydomain1.com", 443, "https://mydomain1.com", allowed));
|
|
||||||
assertTrue(checkOrigin("mydomain1.com", 443, "https://mydomain1.com:443", allowed));
|
|
||||||
assertTrue(checkOrigin("mydomain1.com", 123, "http://mydomain1.com:123", allowed));
|
|
||||||
assertTrue(checkOrigin("mydomain1.com", -1, "ws://mydomain1.com", allowed));
|
|
||||||
assertTrue(checkOrigin("mydomain1.com", 443, "wss://mydomain1.com", allowed));
|
|
||||||
|
|
||||||
allowed = Collections.singletonList("*");
|
allowed = Collections.singletonList("*");
|
||||||
assertTrue(checkOrigin("mydomain1.com", -1, "http://mydomain2.com", allowed));
|
assertTrue(checkValidOrigin("mydomain1.com", -1, "http://mydomain2.com", allowed));
|
||||||
|
|
||||||
allowed = Collections.singletonList("http://mydomain1.com");
|
allowed = Collections.singletonList("http://mydomain1.com");
|
||||||
assertTrue(checkOrigin("mydomain2.com", -1, "http://mydomain1.com", allowed));
|
assertTrue(checkValidOrigin("mydomain2.com", -1, "http://mydomain1.com", allowed));
|
||||||
|
assertFalse(checkValidOrigin("mydomain2.com", -1, "http://mydomain3.com", allowed));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isValidOriginFailure() {
|
public void isSameOrigin() {
|
||||||
|
assertTrue(checkSameOrigin("mydomain1.com", -1, "http://mydomain1.com"));
|
||||||
|
assertTrue(checkSameOrigin("mydomain1.com", -1, "http://mydomain1.com:80"));
|
||||||
|
assertTrue(checkSameOrigin("mydomain1.com", 443, "https://mydomain1.com"));
|
||||||
|
assertTrue(checkSameOrigin("mydomain1.com", 443, "https://mydomain1.com:443"));
|
||||||
|
assertTrue(checkSameOrigin("mydomain1.com", 123, "http://mydomain1.com:123"));
|
||||||
|
assertTrue(checkSameOrigin("mydomain1.com", -1, "ws://mydomain1.com"));
|
||||||
|
assertTrue(checkSameOrigin("mydomain1.com", 443, "wss://mydomain1.com"));
|
||||||
|
|
||||||
List<String> allowed = Collections.emptyList();
|
assertFalse(checkSameOrigin("mydomain1.com", -1, "http://mydomain2.com"));
|
||||||
assertFalse(checkOrigin("mydomain1.com", -1, "http://mydomain2.com", allowed));
|
assertFalse(checkSameOrigin("mydomain1.com", -1, "https://mydomain1.com"));
|
||||||
assertFalse(checkOrigin("mydomain1.com", -1, "https://mydomain1.com", allowed));
|
assertFalse(checkSameOrigin("mydomain1.com", -1, "invalid-origin"));
|
||||||
assertFalse(checkOrigin("mydomain1.com", -1, "invalid-origin", allowed));
|
|
||||||
|
|
||||||
allowed = Collections.singletonList("http://mydomain1.com");
|
|
||||||
assertFalse(checkOrigin("mydomain2.com", -1, "http://mydomain3.com", allowed));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkOrigin(String serverName, int port, String originHeader, List<String> allowed) {
|
|
||||||
|
private boolean checkValidOrigin(String serverName, int port, String originHeader, List<String> allowed) {
|
||||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||||
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
|
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
|
||||||
servletRequest.setServerName(serverName);
|
servletRequest.setServerName(serverName);
|
||||||
@@ -147,4 +146,15 @@ public class WebUtilsTests {
|
|||||||
return WebUtils.isValidOrigin(request, allowed);
|
return WebUtils.isValidOrigin(request, allowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean checkSameOrigin(String serverName, int port, String originHeader) {
|
||||||
|
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||||
|
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
|
||||||
|
servletRequest.setServerName(serverName);
|
||||||
|
if (port != -1) {
|
||||||
|
servletRequest.setServerPort(port);
|
||||||
|
}
|
||||||
|
request.getHeaders().set(HttpHeaders.ORIGIN, originHeader);
|
||||||
|
return WebUtils.isSameOrigin(request);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user