From ac13b55ecd93b9eaf40866aabb043b14dd4f3350 Mon Sep 17 00:00:00 2001 From: Ankur Pathak Date: Wed, 13 Feb 2019 15:04:46 +0530 Subject: [PATCH] HeaderWriterFilter writes headers at beginning Add support for HeaderWriterFilter to write headers at the beginning of the request Fixes: gh-6501 --- .../HeadersConfigurerJavaTests.java | 131 ++++++++++++++++++ .../web/header/HeaderWriterFilter.java | 30 +++- .../web/header/HeaderWriterFilterTests.java | 21 +++ 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/HeadersConfigurerJavaTests.java diff --git a/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/HeadersConfigurerJavaTests.java b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/HeadersConfigurerJavaTests.java new file mode 100644 index 0000000000..485cadf1dd --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/HeadersConfigurerJavaTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2019 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.config.annotation.authentication.configurers; + +import javax.servlet.Filter; +import javax.servlet.ServletException; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.header.HeaderWriterFilter; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HeadersConfigurer}. + * + * @author Ankur Pathak + */ +public class HeadersConfigurerJavaTests { + + private boolean allowCircularReferences = false; + private MockServletContext servletContext; + private MockHttpServletRequest request; + private MockHttpServletResponse response; + private MockFilterChain chain; + private ConfigurableWebApplicationContext context; + + + @Before + public void setUp() { + this.servletContext = new MockServletContext(); + this.request = new MockHttpServletRequest(this.servletContext, "GET", ""); + this.response = new MockHttpServletResponse(); + this.chain = new MockFilterChain(); + } + + + @After + public void cleanup(){ + if (this.context != null){ + this.context.close(); + } + } + + + @EnableWebSecurity + public static class HeadersAtTheBeginningOfRequestConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .headers() + .addObjectPostProcessor(new ObjectPostProcessor() { + @Override + public HeaderWriterFilter postProcess(HeaderWriterFilter filter) { + filter.setShouldWriteHeadersEagerly(true); + return filter; + } + }); + } + } + + @Test + public void headersWrittenAtBeginningOfRequest() throws IOException, ServletException { + this.context = loadConfig(HeadersAtTheBeginningOfRequestConfig.class); + this.request.setSecure(true); + getSpringSecurityFilterChain().doFilter(this.request, this.response, this.chain); + assertThat(getResponseHeaders()).containsAllEntriesOf(new LinkedHashMap(){{ + put("X-Content-Type-Options", "nosniff"); + put("X-Frame-Options", "DENY"); + put("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains"); + put("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"); + put("Expires", "0"); + put("Pragma", "no-cache"); + put("X-XSS-Protection", "1; mode=block"); + }}); + } + + + @SuppressWarnings("unchecked") + private Map getResponseHeaders() { + Map headers = new LinkedHashMap<>(); + this.response.getHeaderNames().forEach(name -> { + List values = this.response.getHeaderValues(name); + headers.put(name, String.join(",", values)); + }); + return headers; + } + + private ConfigurableWebApplicationContext loadConfig(Class... configs) { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.register(configs); + context.setAllowCircularReferences(this.allowCircularReferences); + context.setServletContext(this.servletContext); + context.refresh(); + return context; + } + + private Filter getSpringSecurityFilterChain() { + return this.context.getBean("springSecurityFilterChain", Filter.class); + } +} diff --git a/web/src/main/java/org/springframework/security/web/header/HeaderWriterFilter.java b/web/src/main/java/org/springframework/security/web/header/HeaderWriterFilter.java index c50097d137..511a39a24f 100644 --- a/web/src/main/java/org/springframework/security/web/header/HeaderWriterFilter.java +++ b/web/src/main/java/org/springframework/security/web/header/HeaderWriterFilter.java @@ -51,6 +51,11 @@ public class HeaderWriterFilter extends OncePerRequestFilter { */ private final HeaderWriter headerWriter; + /** + * Indicates whether to write the headers at the beginning of the request. + */ + private boolean shouldWriteHeadersEagerly = false; + /** * Creates a new instance. * @@ -67,11 +72,23 @@ public class HeaderWriterFilter extends OncePerRequestFilter { HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + if (this.shouldWriteHeadersEagerly) { + doHeadersBefore(request, response, filterChain); + } else { + doHeadersAfter(request, response, filterChain); + } + } + + private void doHeadersBefore(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { + this.headerWriter.writeHeaders(request, response); + filterChain.doFilter(request, response); + } + + private void doHeadersAfter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request, response, this.headerWriter); HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request, headerWriterResponse); - try { filterChain.doFilter(headerWriterRequest, headerWriterResponse); } finally { @@ -79,6 +96,17 @@ public class HeaderWriterFilter extends OncePerRequestFilter { } } + /** + * Allow writing headers at the beginning of the request. + * + * @param shouldWriteHeadersEagerly boolean to allow writing headers at the beginning of the request. + * @author Ankur Pathak + * @since 5.2 + */ + public void setShouldWriteHeadersEagerly(boolean shouldWriteHeadersEagerly) { + this.shouldWriteHeadersEagerly = shouldWriteHeadersEagerly; + } + static class HeaderWriterResponse extends OnCommittedResponseWrapper { private final HttpServletRequest request; private final HeaderWriter headerWriter; diff --git a/web/src/test/java/org/springframework/security/web/header/HeaderWriterFilterTests.java b/web/src/test/java/org/springframework/security/web/header/HeaderWriterFilterTests.java index 07a84b6352..3e44defc3c 100644 --- a/web/src/test/java/org/springframework/security/web/header/HeaderWriterFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/header/HeaderWriterFilterTests.java @@ -136,4 +136,25 @@ public class HeaderWriterFilterTests { verifyNoMoreInteractions(this.writer1); } + + @Test + public void headersWrittenAtBeginningOfRequest() throws Exception { + HeaderWriterFilter filter = new HeaderWriterFilter( + Collections.singletonList(this.writer1)); + filter.setShouldWriteHeadersEagerly(true); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request, response, new FilterChain() { + @Override + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + verify(HeaderWriterFilterTests.this.writer1).writeHeaders( + any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + }); + + verifyNoMoreInteractions(this.writer1); + } }