Add CORS support for Private Network Access
This commit adds CORS support for Private Network Access by adding an Access-Control-Allow-Private-Network response header when the preflight request is sent with an Access-Control-Request-Private-Network header and that Private Network Access has been enabled in the CORS configuration. See https://developer.chrome.com/blog/private-network-access-preflight/ for more details. Closes gh-28546
This commit is contained in:
@@ -116,6 +116,14 @@ public @interface CrossOrigin {
|
||||
*/
|
||||
String allowCredentials() default "";
|
||||
|
||||
/**
|
||||
* Whether private network access is supported. Please, see
|
||||
* {@link CorsConfiguration#setAllowPrivateNetwork(Boolean)} for details.
|
||||
* <p>By default this is not set (i.e. private network access is not supported).
|
||||
* @since 6.1.3
|
||||
*/
|
||||
String allowPrivateNetwork() default "";
|
||||
|
||||
/**
|
||||
* The maximum age (in seconds) of the cache duration for preflight responses.
|
||||
* <p>This property controls the value of the {@code Access-Control-Max-Age}
|
||||
|
||||
@@ -90,6 +90,9 @@ public class CorsConfiguration {
|
||||
@Nullable
|
||||
private Boolean allowCredentials;
|
||||
|
||||
@Nullable
|
||||
private Boolean allowPrivateNetwork;
|
||||
|
||||
@Nullable
|
||||
private Long maxAge;
|
||||
|
||||
@@ -114,6 +117,7 @@ public class CorsConfiguration {
|
||||
this.allowedHeaders = other.allowedHeaders;
|
||||
this.exposedHeaders = other.exposedHeaders;
|
||||
this.allowCredentials = other.allowCredentials;
|
||||
this.allowPrivateNetwork = other.allowPrivateNetwork;
|
||||
this.maxAge = other.maxAge;
|
||||
}
|
||||
|
||||
@@ -133,9 +137,10 @@ public class CorsConfiguration {
|
||||
* {@code Access-Control-Allow-Origin} response header is set either to the
|
||||
* matched domain value or to {@code "*"}. Keep in mind however that the
|
||||
* CORS spec does not allow {@code "*"} when {@link #setAllowCredentials
|
||||
* allowCredentials} is set to {@code true} and as of 5.3 that combination
|
||||
* is rejected in favor of using {@link #setAllowedOriginPatterns
|
||||
* allowedOriginPatterns} instead.
|
||||
* allowCredentials} is set to {@code true}, and does not recommend {@code "*"}
|
||||
* when {@link #setAllowPrivateNetwork allowPrivateNetwork} is set to {@code true}.
|
||||
* As a consequence, those combinations are rejected in favor of using
|
||||
* {@link #setAllowedOriginPatterns allowedOriginPatterns} instead.
|
||||
* <p>By default this is not set which means that no origins are allowed.
|
||||
* However, an instance of this class is often initialized further, e.g. for
|
||||
* {@code @CrossOrigin}, via {@link #applyPermitDefaultValues()}.
|
||||
@@ -199,11 +204,13 @@ public class CorsConfiguration {
|
||||
* note that such placeholders must be resolved externally.
|
||||
* </ul>
|
||||
* <p>In contrast to {@link #setAllowedOrigins(List) allowedOrigins} which
|
||||
* only supports "*" and cannot be used with {@code allowCredentials}, when
|
||||
* an allowedOriginPattern is matched, the {@code Access-Control-Allow-Origin}
|
||||
* response header is set to the matched origin and not to {@code "*"} nor
|
||||
* to the pattern. Therefore, allowedOriginPatterns can be used in combination
|
||||
* with {@link #setAllowCredentials} set to {@code true}.
|
||||
* only supports "*" and cannot be used with {@code allowCredentials} or
|
||||
* {@code allowPrivateNetwork}, when an {@code allowedOriginPattern} is matched,
|
||||
* the {@code Access-Control-Allow-Origin} response header is set to the
|
||||
* matched origin and not to {@code "*"} nor to the pattern.
|
||||
* Therefore, {@code allowedOriginPatterns} can be used in combination with
|
||||
* {@link #setAllowCredentials} and {@link #setAllowPrivateNetwork} set to
|
||||
* {@code true}
|
||||
* <p>By default this is not set.
|
||||
* @since 5.3
|
||||
*/
|
||||
@@ -461,6 +468,33 @@ public class CorsConfiguration {
|
||||
return this.allowCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether private network access is supported for user-agents restricting such access by default.
|
||||
* <p>Private network requests are requests whose target server's IP address is more private than
|
||||
* that from which the request initiator was fetched. For example, a request from a public website
|
||||
* (https://example.com) to a private website (https://router.local), or a request from a private
|
||||
* website to localhost.
|
||||
* <p>Setting this property has an impact on how {@link #setAllowedOrigins(List)
|
||||
* origins} and {@link #setAllowedOriginPatterns(List) originPatterns} are processed,
|
||||
* see related API documentation for more details.
|
||||
* <p>By default this is not set (i.e. private network access is not supported).
|
||||
* @since 6.1.3
|
||||
* @see <a href="https://wicg.github.io/private-network-access/">Private network access specifications</a>
|
||||
*/
|
||||
public void setAllowPrivateNetwork(@Nullable Boolean allowPrivateNetwork) {
|
||||
this.allowPrivateNetwork = allowPrivateNetwork;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@code allowPrivateNetwork} flag, or {@code null} if none.
|
||||
* @since 6.1.3
|
||||
* @see #setAllowPrivateNetwork(Boolean)
|
||||
*/
|
||||
@Nullable
|
||||
public Boolean getAllowPrivateNetwork() {
|
||||
return this.allowPrivateNetwork;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure how long, as a duration, the response from a pre-flight request
|
||||
* can be cached by clients.
|
||||
@@ -543,6 +577,25 @@ public class CorsConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that when {@link #setAllowPrivateNetwork allowPrivateNetwork} is {@code true},
|
||||
* {@link #setAllowedOrigins allowedOrigins} does not contain the special
|
||||
* value {@code "*"} since this is insecure.
|
||||
* @throws IllegalArgumentException if the validation fails
|
||||
* @since 6.1.3
|
||||
*/
|
||||
public void validateAllowPrivateNetwork() {
|
||||
if (this.allowPrivateNetwork == Boolean.TRUE &&
|
||||
this.allowedOrigins != null && this.allowedOrigins.contains(ALL)) {
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"When allowPrivateNetwork is true, allowedOrigins cannot contain the special value \"*\" " +
|
||||
"as it is not recommended from a security perspective. " +
|
||||
"To allow private network access to a set of origins, list them explicitly " +
|
||||
"or consider using \"allowedOriginPatterns\" instead.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the non-null properties of the supplied
|
||||
* {@code CorsConfiguration} with this one.
|
||||
@@ -577,6 +630,10 @@ public class CorsConfiguration {
|
||||
if (allowCredentials != null) {
|
||||
config.setAllowCredentials(allowCredentials);
|
||||
}
|
||||
Boolean allowPrivateNetwork = other.getAllowPrivateNetwork();
|
||||
if (allowPrivateNetwork != null) {
|
||||
config.setAllowPrivateNetwork(allowPrivateNetwork);
|
||||
}
|
||||
Long maxAge = other.getMaxAge();
|
||||
if (maxAge != null) {
|
||||
config.setMaxAge(maxAge);
|
||||
@@ -640,6 +697,7 @@ public class CorsConfiguration {
|
||||
if (!ObjectUtils.isEmpty(this.allowedOrigins)) {
|
||||
if (this.allowedOrigins.contains(ALL)) {
|
||||
validateAllowCredentials();
|
||||
validateAllowPrivateNetwork();
|
||||
return ALL;
|
||||
}
|
||||
for (String allowedOrigin : this.allowedOrigins) {
|
||||
|
||||
@@ -54,6 +54,18 @@ public class DefaultCorsProcessor implements CorsProcessor {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultCorsProcessor.class);
|
||||
|
||||
/**
|
||||
* The {@code Access-Control-Request-Private-Network} request header field name.
|
||||
* @see <a href="https://wicg.github.io/private-network-access/">Private Network Access specification</a>
|
||||
*/
|
||||
static final String ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK = "Access-Control-Request-Private-Network";
|
||||
|
||||
/**
|
||||
* The {@code Access-Control-Allow-Private-Network} response header field name.
|
||||
* @see <a href="https://wicg.github.io/private-network-access/">Private Network Access specification</a>
|
||||
*/
|
||||
static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network";
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("resource")
|
||||
@@ -155,6 +167,11 @@ public class DefaultCorsProcessor implements CorsProcessor {
|
||||
responseHeaders.setAccessControlAllowCredentials(true);
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) &&
|
||||
Boolean.parseBoolean(request.getHeaders().getFirst(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK))) {
|
||||
responseHeaders.set(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.toString(true));
|
||||
}
|
||||
|
||||
if (preFlightRequest && config.getMaxAge() != null) {
|
||||
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
|
||||
}
|
||||
|
||||
@@ -52,6 +52,18 @@ public class DefaultCorsProcessor implements CorsProcessor {
|
||||
private static final List<String> VARY_HEADERS = List.of(
|
||||
HttpHeaders.ORIGIN, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
|
||||
|
||||
/**
|
||||
* The {@code Access-Control-Request-Private-Network} request header field name.
|
||||
* @see <a href="https://wicg.github.io/private-network-access/">Private Network Access specification</a>
|
||||
*/
|
||||
static final String ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK = "Access-Control-Request-Private-Network";
|
||||
|
||||
/**
|
||||
* The {@code Access-Control-Allow-Private-Network} response header field name.
|
||||
* @see <a href="https://wicg.github.io/private-network-access/">Private Network Access specification</a>
|
||||
*/
|
||||
static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network";
|
||||
|
||||
|
||||
@Override
|
||||
public boolean process(@Nullable CorsConfiguration config, ServerWebExchange exchange) {
|
||||
@@ -153,6 +165,11 @@ public class DefaultCorsProcessor implements CorsProcessor {
|
||||
responseHeaders.setAccessControlAllowCredentials(true);
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) &&
|
||||
Boolean.parseBoolean(request.getHeaders().getFirst(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK))) {
|
||||
responseHeaders.set(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.toString(true));
|
||||
}
|
||||
|
||||
if (preFlightRequest && config.getMaxAge() != null) {
|
||||
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user