瀏覽代碼

Add reactive support for Referrer-Policy security header

Vedran Pavic 7 年之前
父節點
當前提交
f382b69507

+ 47 - 1
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -102,6 +102,8 @@ import org.springframework.security.web.server.header.ContentSecurityPolicyServe
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.HttpHeaderWriterWebFilter;
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
 import org.springframework.security.web.server.header.ServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter;
@@ -1667,6 +1669,8 @@ public class ServerHttpSecurity {
 
 		private ContentSecurityPolicyServerHttpHeadersWriter contentSecurityPolicy = new ContentSecurityPolicyServerHttpHeadersWriter();
 
+		private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter();
+
 		/**
 		 * Allows method chaining to continue configuring the {@link ServerHttpSecurity}
 		 * @return the {@link ServerHttpSecurity} to continue configuring
@@ -1748,6 +1752,14 @@ public class ServerHttpSecurity {
 			return new FeaturePolicySpec(policyDirectives);
 		}
 
+		/**
+		 * Configures {@code Referrer-Policy} response header.
+		 * @return the {@link ReferrerPolicySpec} to configure
+		 */
+		public ReferrerPolicySpec referrerPolicy() {
+			return new ReferrerPolicySpec();
+		}
+
 		/**
 		 * Configures cache control headers
 		 * @see #cache()
@@ -1937,10 +1949,44 @@ public class ServerHttpSecurity {
 
 		}
 
+		/**
+		 * Configures {@code Referrer-Policy} response header.
+		 *
+		 * @see #referrerPolicy()
+		 * @since 5.1
+		 */
+		public class ReferrerPolicySpec {
+
+			/**
+			 * Set the policy to be used in the response header. Defaults to the
+			 * {@link ReferrerPolicy#NO_REFERRER} header.
+			 * @param referrerPolicy the policy
+			 * @return the {@link HeaderSpec} to continue configuring
+			 */
+			public HeaderSpec referrerPolicy(ReferrerPolicy referrerPolicy) {
+				HeaderSpec.this.referrerPolicy.setPolicy(referrerPolicy);
+				return HeaderSpec.this;
+			}
+
+			/**
+			 * Allows method chaining to continue configuring the
+			 * {@link ServerHttpSecurity}.
+			 * @return the {@link HeaderSpec} to continue configuring
+			 */
+			public HeaderSpec and() {
+				return HeaderSpec.this;
+			}
+
+			private ReferrerPolicySpec() {
+			}
+
+		}
+
 		private HeaderSpec() {
 			this.writers = new ArrayList<>(
 					Arrays.asList(this.cacheControl, this.contentTypeOptions, this.hsts,
-							this.frameOptions, this.xss, this.featurePolicy, this.contentSecurityPolicy));
+							this.frameOptions, this.xss, this.featurePolicy, this.contentSecurityPolicy,
+							this.referrerPolicy));
 		}
 
 	}

+ 11 - 0
config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java

@@ -30,6 +30,8 @@ import org.springframework.security.test.web.reactive.server.WebTestClientBuilde
 import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
 import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter;
@@ -171,6 +173,15 @@ public class HeaderSpecTests {
 		assertHeaders();
 	}
 
+	@Test
+	public void headersWhenReferrerPolicyEnabledThenFeaturePolicyWritten() {
+		this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
+				ReferrerPolicy.NO_REFERRER.getPolicy());
+		this.headers.referrerPolicy();
+
+		assertHeaders();
+	}
+
 	private void expectHeaderNamesNotPresent(String... headerNames) {
 		for(String headerName : headerNames) {
 			this.expectedHeaders.remove(headerName);

+ 103 - 0
web/src/main/java/org/springframework/security/web/server/header/ReferrerPolicyServerHttpHeadersWriter.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.web.server.header;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Writes the {@code Referrer-Policy} response header.
+ *
+ * @author Vedran Pavic
+ * @since 5.1
+ */
+public final class ReferrerPolicyServerHttpHeadersWriter
+		implements ServerHttpHeadersWriter {
+
+	public static final String REFERRER_POLICY = "Referrer-Policy";
+
+	private ServerHttpHeadersWriter delegate;
+
+	public ReferrerPolicyServerHttpHeadersWriter() {
+		this.delegate = createDelegate(ReferrerPolicy.NO_REFERRER);
+	}
+
+	@Override
+	public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
+		return this.delegate.writeHttpHeaders(exchange);
+	}
+
+	/**
+	 * Set the policy to be used in the response header.
+	 * @param policy the policy
+	 * @throws IllegalArgumentException if policy is {@code null}
+	 */
+	public void setPolicy(ReferrerPolicy policy) {
+		Assert.notNull(policy, "policy must not be null");
+		this.delegate = createDelegate(policy);
+	}
+
+	private static ServerHttpHeadersWriter createDelegate(ReferrerPolicy policy) {
+		// @formatter:off
+		return StaticServerHttpHeadersWriter.builder()
+				.header(REFERRER_POLICY, policy.getPolicy())
+				.build();
+		// @formatter:on
+	}
+
+	public enum ReferrerPolicy {
+
+		// @formatter:off
+		NO_REFERRER("no-referrer"),
+		NO_REFERRER_WHEN_DOWNGRADE("no-referrer-when-downgrade"),
+		SAME_ORIGIN("same-origin"),
+		ORIGIN("origin"),
+		STRICT_ORIGIN("strict-origin"),
+		ORIGIN_WHEN_CROSS_ORIGIN("origin-when-cross-origin"),
+		STRICT_ORIGIN_WHEN_CROSS_ORIGIN("strict-origin-when-cross-origin"),
+		UNSAFE_URL("unsafe-url");
+		// @formatter:on
+
+		private static final Map<String, ReferrerPolicy> REFERRER_POLICIES;
+
+		static {
+			Map<String, ReferrerPolicy> referrerPolicies = new HashMap<>();
+			for (ReferrerPolicy referrerPolicy : values()) {
+				referrerPolicies.put(referrerPolicy.getPolicy(), referrerPolicy);
+			}
+			REFERRER_POLICIES = Collections.unmodifiableMap(referrerPolicies);
+		}
+
+		private String policy;
+
+		ReferrerPolicy(String policy) {
+			this.policy = policy;
+		}
+
+		public String getPolicy() {
+			return this.policy;
+		}
+
+	}
+
+}

+ 81 - 0
web/src/test/java/org/springframework/security/web/server/header/ReferrerPolicyServerHttpHeadersWriterTests.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.web.server.header;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
+import org.springframework.web.server.ServerWebExchange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ReferrerPolicyServerHttpHeadersWriter}.
+ *
+ * @author Vedran Pavic
+ */
+public class ReferrerPolicyServerHttpHeadersWriterTests {
+
+	private ServerWebExchange exchange;
+
+	private ReferrerPolicyServerHttpHeadersWriter writer;
+
+	@Before
+	public void setup() {
+		this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
+		this.writer = new ReferrerPolicyServerHttpHeadersWriter();
+	}
+
+	@Test
+	public void writeHeadersWhenUsingDefaultsThenDoesNotWrite() {
+		this.writer.writeHttpHeaders(this.exchange);
+
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY))
+				.containsOnly(ReferrerPolicy.NO_REFERRER.getPolicy());
+	}
+
+	@Test
+	public void writeHeadersWhenUsingPolicyThenWritesPolicy() {
+		this.writer.setPolicy(ReferrerPolicy.SAME_ORIGIN);
+		this.writer.writeHttpHeaders(this.exchange);
+
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY))
+				.containsOnly(ReferrerPolicy.SAME_ORIGIN.getPolicy());
+	}
+
+	@Test
+	public void writeHeadersWhenAlreadyWrittenThenWritesHeader() {
+		String headerValue = ReferrerPolicy.SAME_ORIGIN.getPolicy();
+		this.exchange.getResponse().getHeaders()
+				.set(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, headerValue);
+		this.writer.writeHttpHeaders(this.exchange);
+
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY))
+				.containsOnly(headerValue);
+	}
+
+}