Merge pull request #2234 from alek-sys/main
Fix secure headers duplication
This commit is contained in:
@@ -92,39 +92,39 @@ public class SecureHeadersGatewayFilterFactory
|
|||||||
List<String> disabled = properties.getDisable();
|
List<String> disabled = properties.getDisable();
|
||||||
Config config = originalConfig.withDefaults(properties);
|
Config config = originalConfig.withDefaults(properties);
|
||||||
|
|
||||||
if (isEnabled(disabled, X_XSS_PROTECTION_HEADER)) {
|
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
|
||||||
headers.add(X_XSS_PROTECTION_HEADER, config.getXssProtectionHeader());
|
if (isEnabled(disabled, X_XSS_PROTECTION_HEADER)) {
|
||||||
}
|
headers.addIfAbsent(X_XSS_PROTECTION_HEADER, config.getXssProtectionHeader());
|
||||||
|
}
|
||||||
|
|
||||||
if (isEnabled(disabled, STRICT_TRANSPORT_SECURITY_HEADER)) {
|
if (isEnabled(disabled, STRICT_TRANSPORT_SECURITY_HEADER)) {
|
||||||
headers.add(STRICT_TRANSPORT_SECURITY_HEADER, config.getStrictTransportSecurity());
|
headers.addIfAbsent(STRICT_TRANSPORT_SECURITY_HEADER, config.getStrictTransportSecurity());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEnabled(disabled, X_FRAME_OPTIONS_HEADER)) {
|
if (isEnabled(disabled, X_FRAME_OPTIONS_HEADER)) {
|
||||||
headers.add(X_FRAME_OPTIONS_HEADER, config.getFrameOptions());
|
headers.addIfAbsent(X_FRAME_OPTIONS_HEADER, config.getFrameOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEnabled(disabled, X_CONTENT_TYPE_OPTIONS_HEADER)) {
|
if (isEnabled(disabled, X_CONTENT_TYPE_OPTIONS_HEADER)) {
|
||||||
headers.add(X_CONTENT_TYPE_OPTIONS_HEADER, config.getContentTypeOptions());
|
headers.addIfAbsent(X_CONTENT_TYPE_OPTIONS_HEADER, config.getContentTypeOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEnabled(disabled, REFERRER_POLICY_HEADER)) {
|
if (isEnabled(disabled, REFERRER_POLICY_HEADER)) {
|
||||||
headers.add(REFERRER_POLICY_HEADER, config.getReferrerPolicy());
|
headers.addIfAbsent(REFERRER_POLICY_HEADER, config.getReferrerPolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEnabled(disabled, CONTENT_SECURITY_POLICY_HEADER)) {
|
if (isEnabled(disabled, CONTENT_SECURITY_POLICY_HEADER)) {
|
||||||
headers.add(CONTENT_SECURITY_POLICY_HEADER, config.getContentSecurityPolicy());
|
headers.addIfAbsent(CONTENT_SECURITY_POLICY_HEADER, config.getContentSecurityPolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEnabled(disabled, X_DOWNLOAD_OPTIONS_HEADER)) {
|
if (isEnabled(disabled, X_DOWNLOAD_OPTIONS_HEADER)) {
|
||||||
headers.add(X_DOWNLOAD_OPTIONS_HEADER, config.getDownloadOptions());
|
headers.addIfAbsent(X_DOWNLOAD_OPTIONS_HEADER, config.getDownloadOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEnabled(disabled, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER)) {
|
if (isEnabled(disabled, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER)) {
|
||||||
headers.add(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER, config.getPermittedCrossDomainPolicies());
|
headers.addIfAbsent(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER, config.getPermittedCrossDomainPolicies());
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.springframework.cloud.gateway.test.BaseWebClientTests;
|
|||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||||
@@ -73,6 +74,18 @@ public class SecureHeadersGatewayFilterFactoryTests extends BaseWebClientTests {
|
|||||||
}).expectComplete().verify(DURATION);
|
}).expectComplete().verify(DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addsSecureHeadersAfterResponseIsReceived() {
|
||||||
|
Mono<ClientResponse> result = webClient.patch().uri("/headers").header("Host", "www.secureheaders.org")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON).bodyValue("{ \"X-Frame-Options\": \"sameorigin\" }")
|
||||||
|
.exchangeToMono(Mono::just);
|
||||||
|
|
||||||
|
StepVerifier.create(result).consumeNextWith(response -> {
|
||||||
|
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.headers().header(X_FRAME_OPTIONS_HEADER)).containsOnly("sameorigin");
|
||||||
|
}).expectComplete().verify(DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
@EnableAutoConfiguration
|
@EnableAutoConfiguration
|
||||||
@SpringBootConfiguration
|
@SpringBootConfiguration
|
||||||
@Import(DefaultTestConfig.class)
|
@Import(DefaultTestConfig.class)
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ public class SecureHeadersGatewayFilterFactoryUnitTests {
|
|||||||
new SecureHeadersProperties());
|
new SecureHeadersProperties());
|
||||||
filter = filterFactory.apply(new Config());
|
filter = filterFactory.apply(new Config());
|
||||||
|
|
||||||
filter.filter(exchange, filterChain);
|
filter.filter(exchange, filterChain).block();
|
||||||
|
|
||||||
ServerHttpResponse response = captor.getValue().getResponse();
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
assertThat(response.getHeaders()).containsKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER,
|
assertThat(response.getHeaders()).containsKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER,
|
||||||
X_FRAME_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER,
|
X_FRAME_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER,
|
||||||
CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER);
|
CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER);
|
||||||
@@ -90,7 +90,7 @@ public class SecureHeadersGatewayFilterFactoryUnitTests {
|
|||||||
SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(properties);
|
SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(properties);
|
||||||
filter = filterFactory.apply(new Config());
|
filter = filterFactory.apply(new Config());
|
||||||
|
|
||||||
filter.filter(exchange, filterChain);
|
filter.filter(exchange, filterChain).block();
|
||||||
|
|
||||||
ServerHttpResponse response = captor.getValue().getResponse();
|
ServerHttpResponse response = captor.getValue().getResponse();
|
||||||
assertThat(response.getHeaders()).doesNotContainKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER,
|
assertThat(response.getHeaders()).doesNotContainKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER,
|
||||||
@@ -109,9 +109,9 @@ public class SecureHeadersGatewayFilterFactoryUnitTests {
|
|||||||
config.setReferrerPolicy("referrer");
|
config.setReferrerPolicy("referrer");
|
||||||
filter = filterFactory.apply(config);
|
filter = filterFactory.apply(config);
|
||||||
|
|
||||||
filter.filter(exchange, filterChain);
|
filter.filter(exchange, filterChain).block();
|
||||||
|
|
||||||
ServerHttpResponse response = captor.getValue().getResponse();
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
assertThat(response.getHeaders()).containsKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER,
|
assertThat(response.getHeaders()).containsKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER,
|
||||||
X_FRAME_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER,
|
X_FRAME_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER,
|
||||||
CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER);
|
CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER);
|
||||||
@@ -132,6 +132,31 @@ public class SecureHeadersGatewayFilterFactoryUnitTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doesNotDuplicateHeaders() {
|
||||||
|
String originalHeaderValue = "original-header-value";
|
||||||
|
SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(
|
||||||
|
new SecureHeadersProperties());
|
||||||
|
Config config = new Config();
|
||||||
|
|
||||||
|
String[] headers = { X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER, X_FRAME_OPTIONS_HEADER,
|
||||||
|
X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER, CONTENT_SECURITY_POLICY_HEADER,
|
||||||
|
X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER };
|
||||||
|
|
||||||
|
for (String header : headers) {
|
||||||
|
filter = filterFactory.apply(config);
|
||||||
|
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build();
|
||||||
|
exchange = MockServerWebExchange.from(request);
|
||||||
|
exchange.getResponse().getHeaders().set(header, originalHeaderValue);
|
||||||
|
|
||||||
|
filter.filter(exchange, filterChain).block();
|
||||||
|
|
||||||
|
ServerHttpResponse response = captor.getValue().getResponse();
|
||||||
|
assertThat(response.getHeaders().get(header)).containsOnly(originalHeaderValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void toStringFormat() {
|
public void toStringFormat() {
|
||||||
GatewayFilter filter = new SecureHeadersGatewayFilterFactory(new SecureHeadersProperties()).apply(new Config());
|
GatewayFilter filter = new SecureHeadersGatewayFilterFactory(new SecureHeadersProperties()).apply(new Config());
|
||||||
|
|||||||
@@ -73,6 +73,17 @@ public class HttpBinCompatibleController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping(path = "/headers", method = RequestMethod.PATCH)
|
||||||
|
public ResponseEntity<Map<String, Object>> headersPatch(ServerWebExchange exchange,
|
||||||
|
@RequestBody Map<String, String> headersToAdd) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("headers", getHeaders(exchange));
|
||||||
|
ResponseEntity.BodyBuilder responseEntity = ResponseEntity.status(HttpStatus.OK);
|
||||||
|
headersToAdd.forEach(responseEntity::header);
|
||||||
|
|
||||||
|
return responseEntity.body(result);
|
||||||
|
}
|
||||||
|
|
||||||
@RequestMapping(path = "/multivalueheaders", method = { RequestMethod.GET, RequestMethod.POST },
|
@RequestMapping(path = "/multivalueheaders", method = { RequestMethod.GET, RequestMethod.POST },
|
||||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public Map<String, Object> multiValueHeaders(ServerWebExchange exchange) {
|
public Map<String, Object> multiValueHeaders(ServerWebExchange exchange) {
|
||||||
|
|||||||
Reference in New Issue
Block a user