diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java index 3a31312cda..787a7bc16a 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java @@ -36,6 +36,7 @@ import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFi import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.authentication.www.DigestAuthenticationFilter; import org.springframework.security.web.context.SecurityContextPersistenceFilter; +import org.springframework.security.web.header.HeaderWriterFilter; import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; @@ -63,6 +64,8 @@ final class FilterComparator implements Comparator, Serializable { order += STEP; put(SecurityContextPersistenceFilter.class, order); order += STEP; + put(HeaderWriterFilter.class, order); + order += STEP; put(LogoutFilter.class, order); order += STEP; put(X509AuthenticationFilter.class, order); diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 2b7fe7469e..3e236fc034 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -41,6 +41,7 @@ import org.springframework.security.config.annotation.web.configurers.ChannelSec import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; import org.springframework.security.config.annotation.web.configurers.JeeConfigurer; import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; @@ -239,6 +240,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder()); } + public HeadersConfigurer headers() throws Exception { + return getOrApply(new HeadersConfigurer()); + } + /** * Allows configuring of Session Management. * diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java index 61ba36ac07..67f6997b12 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java @@ -155,6 +155,7 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer if(!disableDefaults) { http .exceptionHandling().and() + .headers().and() .sessionManagement().and() .securityContext().and() .requestCache().and() diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java new file mode 100644 index 0000000000..2c69f0465a --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2013 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.web.configurers; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.header.HeaderWriter; +import org.springframework.security.web.header.HeaderWriterFilter; +import org.springframework.security.web.header.writers.CacheControlHeadersWriter; +import org.springframework.security.web.header.writers.HstsHeaderWriter; +import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter; +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; +import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; +import org.springframework.util.Assert; + +/** + * @author Rob Winch + * @since 3.2 + * @see RememberMeConfigurer + */ +public final class HeadersConfigurer> extends AbstractHttpConfigurer { + private List headerWriters = new ArrayList(); + + /** + * Creates a new instance + * @see HttpSecurity#headers() + */ + public HeadersConfigurer() { + } + + /** + * Adds a {@link HeaderWriter} instance + * @param headerWriter the {@link HeaderWriter} instance to add + * @return the {@link HeadersConfigurer} for additional customizations + */ + public HeadersConfigurer addHeaderWriter(HeaderWriter headerWriter) { + Assert.notNull(headerWriter, "headerWriter cannot be null"); + this.headerWriters.add(headerWriter); + return this; + } + + @Override + public void configure(H http) throws Exception { + HeaderWriterFilter headersFilter = createHeaderWriterFilter(); + http.addFilter(headersFilter); + } + + /** + * Creates the {@link HeaderWriter} + * @return the {@link HeaderWriter} + */ + private HeaderWriterFilter createHeaderWriterFilter() { + HeaderWriterFilter headersFilter = new HeaderWriterFilter(getHeaderWriters()); + headersFilter = postProcess(headersFilter); + return headersFilter; + } + + /** + * Gets the {@link HeaderWriter} instances and possibly initializes with the defaults. + * @return + */ + private List getHeaderWriters() { + if(headerWriters.isEmpty()) { + addDefaultHeaderWriters(); + } + return headerWriters; + } + + /** + * Explicitly adds the default {@link HeaderWriter} instances. If no, + * {@link HeaderWriter} instances have been added this is automatically + * invoked. + * + */ + private void addDefaultHeaderWriters() { + headerWriters.add(new XContentTypeOptionsHeaderWriter()); + headerWriters.add(new XXssProtectionHeaderWriter()); + headerWriters.add(new CacheControlHeadersWriter()); + headerWriters.add(new HstsHeaderWriter()); + headerWriters.add(new XFrameOptionsHeaderWriter()); + } +} \ No newline at end of file diff --git a/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java index c24a0b948c..e31cd6fb46 100644 --- a/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java @@ -27,15 +27,17 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.security.web.headers.Header; -import org.springframework.security.web.headers.HeadersFilter; -import org.springframework.security.web.headers.HstsHeaderWriter; -import org.springframework.security.web.headers.StaticHeadersWriter; -import org.springframework.security.web.headers.frameoptions.AbstractRequestParameterAllowFromStrategy; -import org.springframework.security.web.headers.frameoptions.RegExpAllowFromStrategy; -import org.springframework.security.web.headers.frameoptions.StaticAllowFromStrategy; -import org.springframework.security.web.headers.frameoptions.WhiteListedAllowFromStrategy; -import org.springframework.security.web.headers.frameoptions.XFrameOptionsHeaderWriter; +import org.springframework.security.web.header.HeaderWriterFilter; +import org.springframework.security.web.header.writers.CacheControlHeadersWriter; +import org.springframework.security.web.header.writers.HstsHeaderWriter; +import org.springframework.security.web.header.writers.StaticHeadersWriter; +import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter; +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; +import org.springframework.security.web.header.writers.frameoptions.AbstractRequestParameterAllowFromStrategy; +import org.springframework.security.web.header.writers.frameoptions.RegExpAllowFromStrategy; +import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy; +import org.springframework.security.web.header.writers.frameoptions.WhiteListedAllowFromStrategy; +import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; @@ -72,16 +74,13 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { private static final String FRAME_OPTIONS_ELEMENT = "frame-options"; private static final String GENERIC_HEADER_ELEMENT = "header"; - private static final String XSS_PROTECTION_HEADER = "X-XSS-Protection"; - private static final String CONTENT_TYPE_OPTIONS_HEADER = "X-Content-Type-Options"; - private static final String ALLOW_FROM = "ALLOW-FROM"; private ManagedList headerWriters; public BeanDefinition parse(Element element, ParserContext parserContext) { headerWriters = new ManagedList(); - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeadersFilter.class); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeaderWriterFilter.class); parseCacheControlElement(element); parseHstsElement(element); @@ -100,9 +99,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { frameOptions.addConstructorArgValue("DENY"); headerWriters.add(frameOptions.getBeanDefinition()); - BeanDefinitionBuilder xss = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class); - xss.addConstructorArgValue(XSS_PROTECTION_HEADER); - xss.addConstructorArgValue("1; mode=block"); + BeanDefinitionBuilder xss = BeanDefinitionBuilder.genericBeanDefinition(XXssProtectionHeaderWriter.class); headerWriters.add(xss.getBeanDefinition()); } builder.addConstructorArgValue(headerWriters); @@ -117,28 +114,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { } private void addCacheControl() { - ManagedList headers = new ManagedList(); - - BeanDefinitionBuilder pragmaHeader = BeanDefinitionBuilder.genericBeanDefinition(Header.class); - pragmaHeader.addConstructorArgValue("Pragma"); - ManagedList pragmaValues = new ManagedList(); - pragmaValues.add("no-cache"); - pragmaHeader.addConstructorArgValue(pragmaValues); - headers.add(pragmaHeader.getBeanDefinition()); - - BeanDefinitionBuilder cacheControlHeader = BeanDefinitionBuilder.genericBeanDefinition(Header.class); - cacheControlHeader.addConstructorArgValue("Cache-Control"); - ManagedList cacheControlValues = new ManagedList(); - cacheControlValues.add("no-cache"); - cacheControlValues.add("no-store"); - cacheControlValues.add("max-age=0"); - cacheControlValues.add("must-revalidate"); - cacheControlHeader.addConstructorArgValue(cacheControlValues); - headers.add(cacheControlHeader.getBeanDefinition()); - - BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class); - headersWriter.addConstructorArgValue(headers); - + BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder.genericBeanDefinition(CacheControlHeadersWriter.class); headerWriters.add(headersWriter.getBeanDefinition()); } @@ -191,9 +167,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { } private void addContentTypeOptions() { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class); - builder.addConstructorArgValue(CONTENT_TYPE_OPTIONS_HEADER); - builder.addConstructorArgValue("nosniff"); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XContentTypeOptionsHeaderWriter.class); headerWriters.add(builder.getBeanDefinition()); } @@ -256,18 +230,16 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { private void parseXssElement(Element element, ParserContext parserContext) { Element xssElt = DomUtils.getChildElementByTagName(element, XSS_ELEMENT); if (xssElt != null) { - boolean enabled = Boolean.valueOf(getAttribute(xssElt, ATT_ENABLED, "true")); - boolean block = Boolean.valueOf(getAttribute(xssElt, ATT_BLOCK, enabled ? "true" : "false")); + String enabled = xssElt.getAttribute(ATT_ENABLED); + String block = xssElt.getAttribute(ATT_BLOCK); - String value = enabled ? "1" : "0"; - if (enabled && block) { - value += "; mode=block"; - } else if (!enabled && block) { - parserContext.getReaderContext().error(" does not allow block=\"true\".", xssElt); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XXssProtectionHeaderWriter.class); + if(StringUtils.hasText(enabled)) { + builder.addPropertyValue("enabled", enabled); + } + if(StringUtils.hasText(block)) { + builder.addPropertyValue("block", block); } - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class); - builder.addConstructorArgValue(XSS_PROTECTION_HEADER); - builder.addConstructorArgValue(value); headerWriters.add(builder.getBeanDefinition()); } } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy index 93789459b7..1978d67631 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy @@ -84,6 +84,14 @@ abstract class BaseSpringSpec extends Specification { context.getBean("springSecurityFilterChain",Filter.class) } + def getResponseHeaders() { + def headers = [:] + response.headerNames.each { name -> + headers.put(name, response.getHeaderValues(name).join(',')) + } + return headers + } + AuthenticationManager authenticationManager() { context.getBean(AuthenticationManager) } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy index 85ee6e215e..348c8dc6ff 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy @@ -66,6 +66,39 @@ class WebSecurityConfigurerAdapterTests extends BaseSpringSpec { authenticationManager.messages.messageSource instanceof ApplicationContext } + def "headers are populated by default"() { + setup: "load config that overrides http and accepts defaults" + loadConfig(HeadersArePopulatedByDefaultConfig) + request.secure = true + when: "invoke the springSecurityFilterChain" + springSecurityFilterChain.doFilter(request, response, chain) + then: "the default headers are added" + responseHeaders == ['X-Content-Type-Options':'nosniff', + 'X-Frame-Options':'DENY', + 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', + 'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate', + 'Pragma':'no-cache', + 'X-XSS-Protection' : '1; mode=block'] + } + + @EnableWebSecurity + @Configuration + static class HeadersArePopulatedByDefaultConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void registerAuthentication(AuthenticationManagerBuilder auth) + throws Exception { + auth + .inMemoryAuthentication() + .withUser("user").password("password").roles("USER") + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + + } + } + def "AuthenticationEventPublisher is registered for Web registerAuthentication"() { when: loadConfig(InMemoryAuthWithWebSecurityConfigurerAdapter) diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy index 615a1d5d8a..3d37671115 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy @@ -23,12 +23,12 @@ import org.springframework.mock.web.MockFilterChain import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletResponse import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.web.WebSecurityConfigurer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.BaseWebConfig; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.WebSecurityConfigurer +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.builders.WebSecurity +import org.springframework.security.config.annotation.web.configuration.BaseWebConfig +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter import org.springframework.security.web.DefaultSecurityFilterChain import org.springframework.security.web.FilterChainProxy import org.springframework.security.web.access.ExceptionTranslationFilter @@ -37,9 +37,10 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.logout.LogoutFilter import org.springframework.security.web.context.SecurityContextPersistenceFilter +import org.springframework.security.web.header.HeaderWriterFilter import org.springframework.security.web.savedrequest.RequestCacheAwareFilter import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter -import org.springframework.security.web.session.SessionManagementFilter; +import org.springframework.security.web.session.SessionManagementFilter import org.springframework.security.web.util.AnyRequestMatcher /** @@ -113,7 +114,7 @@ class DefaultFiltersTests extends BaseSpringSpec { filterChains[0].filters.empty filterChains[1].requestMatcher instanceof AnyRequestMatcher filterChains[1].filters.collect { it.class } == - [SecurityContextPersistenceFilter, LogoutFilter, RequestCacheAwareFilter, + [SecurityContextPersistenceFilter, HeaderWriterFilter, LogoutFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor ] } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy index 51eec00507..70e30473bf 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy @@ -40,6 +40,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic import org.springframework.security.web.authentication.logout.LogoutFilter import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy import org.springframework.security.web.context.SecurityContextPersistenceFilter +import org.springframework.security.web.header.HeaderWriterFilter import org.springframework.security.web.savedrequest.RequestCacheAwareFilter import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter import org.springframework.security.web.session.SessionManagementFilter @@ -62,7 +63,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec { filterChains[0].filters.empty filterChains[1].requestMatcher instanceof AnyRequestMatcher filterChains[1].filters.collect { it.class.name.contains('$') ? it.class.superclass : it.class } == - [SecurityContextPersistenceFilter, LogoutFilter, UsernamePasswordAuthenticationFilter, + [SecurityContextPersistenceFilter, HeaderWriterFilter, LogoutFilter, UsernamePasswordAuthenticationFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor ] diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.groovy new file mode 100644 index 0000000000..91eeffa3bd --- /dev/null +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.groovy @@ -0,0 +1,248 @@ +/* + * Copyright 2002-2013 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.web.configurers; + +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.BaseSpringSpec +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.BaseWebConfig +import org.springframework.security.web.header.writers.CacheControlHeadersWriter +import org.springframework.security.web.header.writers.HstsHeaderWriter +import org.springframework.security.web.header.writers.StaticHeadersWriter +import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter +import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy +import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter +import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode +import org.springframework.security.web.util.AnyRequestMatcher + +/** + * Tests to verify that all the functionality of attributes is present + * + * @author Rob Winch + * + */ +public class NamespaceHttpHeadersTests extends BaseSpringSpec { + def "http/headers"() { + setup: + loadConfig(HeadersDefaultConfig) + request.secure = true + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['X-Content-Type-Options':'nosniff', + 'X-Frame-Options':'DENY', + 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', + 'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate', + 'Pragma':'no-cache', + 'X-XSS-Protection' : '1; mode=block'] + } + + @Configuration + static class HeadersDefaultConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + } + } + + def "http/headers/cache-control"() { + setup: + loadConfig(HeadersCacheControlConfig) + request.secure = true + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate', + 'Pragma':'no-cache'] + } + + @Configuration + static class HeadersCacheControlConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + .addHeaderWriter(new CacheControlHeadersWriter()) + } + } + + def "http/headers/hsts"() { + setup: + loadConfig(HstsConfig) + request.secure = true + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains'] + } + + @Configuration + static class HstsConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + .addHeaderWriter(new HstsHeaderWriter()) + } + } + + def "http/headers/hsts custom"() { + setup: + loadConfig(HstsCustomConfig) + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['Strict-Transport-Security': 'max-age=15768000'] + } + + @Configuration + static class HstsCustomConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + // hsts@request-matcher-ref, hsts@max-age-seconds, hsts@include-subdomains + // Additional Constructors are provided to leverage default values + .addHeaderWriter(new HstsHeaderWriter(new AnyRequestMatcher(), 15768000, false)) + } + } + + def "http/headers/frame-options@policy=SAMEORIGIN"() { + setup: + loadConfig(FrameOptionsSameOriginConfig) + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['X-Frame-Options': 'SAMEORIGIN'] + } + + @Configuration + static class FrameOptionsSameOriginConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + // frame-options@policy=SAMEORIGIN + .addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN)) + } + } + + // frame-options@strategy, frame-options@value, frame-options@parameter are not provided instead use frame-options@ref + + def "http/headers/frame-options"() { + setup: + loadConfig(FrameOptionsAllowFromConfig) + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['X-Frame-Options': 'ALLOW-FROM https://example.com'] + } + + + @Configuration + static class FrameOptionsAllowFromConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + // frame-options@ref + .addHeaderWriter(new XFrameOptionsHeaderWriter(new StaticAllowFromStrategy(new URI("https://example.com")))) + } + } + + def "http/headers/xss-protection"() { + setup: + loadConfig(XssProtectionConfig) + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['X-XSS-Protection': '1; mode=block'] + } + + @Configuration + static class XssProtectionConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + // xss-protection + .addHeaderWriter(new XXssProtectionHeaderWriter()) + } + } + + def "http/headers/xss-protection custom"() { + setup: + loadConfig(XssProtectionCustomConfig) + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['X-XSS-Protection': '1'] + } + + @Configuration + static class XssProtectionCustomConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + // xss-protection@enabled and xss-protection@block + .addHeaderWriter(new XXssProtectionHeaderWriter(enabled:true,block:false)) + } + } + + def "http/headers/content-type-options"() { + setup: + loadConfig(ContentTypeOptionsConfig) + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['X-Content-Type-Options': 'nosniff'] + } + + @Configuration + static class ContentTypeOptionsConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + // content-type-options + .addHeaderWriter(new XContentTypeOptionsHeaderWriter()) + } + } + + // header@name / header@value are not provided instead use header@ref + + def "http/headers/header@ref"() { + setup: + loadConfig(HeaderRefConfig) + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + responseHeaders == ['customHeaderName': 'customHeaderValue'] + } + + @Configuration + static class HeaderRefConfig extends BaseWebConfig { + @Override + protected void configure(HttpSecurity http) { + http + .headers() + .addHeaderWriter(new StaticHeadersWriter("customHeaderName", "customHeaderValue")) + } + } +} diff --git a/config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy index 2cf22879ec..0ce1766eef 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy @@ -12,30 +12,15 @@ */ package org.springframework.security.config.http -import org.springframework.security.util.FieldUtils - -import javax.servlet.Filter -import javax.servlet.http.HttpServletRequest - -import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCreationException import org.springframework.beans.factory.parsing.BeanDefinitionParsingException -import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException; import org.springframework.mock.web.MockFilterChain import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletResponse -import org.springframework.security.config.BeanIds -import org.springframework.security.openid.OpenIDAuthenticationFilter -import org.springframework.security.openid.OpenIDAuthenticationToken -import org.springframework.security.openid.OpenIDConsumer -import org.springframework.security.openid.OpenIDConsumerException -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.access.ExceptionTranslationFilter -import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices -import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter -import org.springframework.security.web.headers.HeadersFilter -import org.springframework.security.web.headers.StaticHeadersWriter; -import org.springframework.security.web.headers.frameoptions.StaticAllowFromStrategy; -import org.springframework.security.web.util.AnyRequestMatcher; +import org.springframework.security.web.FilterChainProxy +import org.springframework.security.web.header.HeaderWriterFilter +import org.springframework.security.web.header.writers.StaticHeadersWriter +import org.springframework.security.web.util.AnyRequestMatcher /** * @@ -48,7 +33,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) expect: !hf @@ -61,7 +46,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() when: - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) then: @@ -81,7 +66,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -97,7 +82,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -113,7 +98,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -129,7 +114,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -146,7 +131,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) then: BeanDefinitionParsingException e = thrown() @@ -162,7 +147,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) then: BeanDefinitionParsingException e = thrown() @@ -178,7 +163,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -195,7 +180,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -213,7 +198,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -234,7 +219,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() when: - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) then: @@ -276,7 +261,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -293,7 +278,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -310,7 +295,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) @@ -327,11 +312,11 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { } createAppContext() - def hf = getFilter(HeadersFilter) + def hf = getFilter(HeaderWriterFilter) then: - BeanDefinitionParsingException e = thrown() - e.message.contains ' does not allow block="true".' + BeanCreationException e = thrown() + e.message.contains 'Cannot set block to true with enabled false' } def 'http headers cache-control'() { diff --git a/web/src/main/java/org/springframework/security/web/headers/Header.java b/web/src/main/java/org/springframework/security/web/header/Header.java similarity index 96% rename from web/src/main/java/org/springframework/security/web/headers/Header.java rename to web/src/main/java/org/springframework/security/web/header/Header.java index d007643446..7f1b6397de 100644 --- a/web/src/main/java/org/springframework/security/web/headers/Header.java +++ b/web/src/main/java/org/springframework/security/web/header/Header.java @@ -1,4 +1,4 @@ -package org.springframework.security.web.headers; +package org.springframework.security.web.header; import java.util.Arrays; import java.util.List; diff --git a/web/src/main/java/org/springframework/security/web/headers/HeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/HeaderWriter.java similarity index 93% rename from web/src/main/java/org/springframework/security/web/headers/HeaderWriter.java rename to web/src/main/java/org/springframework/security/web/header/HeaderWriter.java index aa672686ee..6282d98b91 100644 --- a/web/src/main/java/org/springframework/security/web/headers/HeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/HeaderWriter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers; +package org.springframework.security.web.header; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -21,7 +21,7 @@ import javax.servlet.http.HttpServletResponse; /** * Contract for writing headers to a {@link HttpServletResponse} * - * @see HeadersFilter + * @see HeaderWriterFilter * * @author Marten Deinum * @author Rob Winch diff --git a/web/src/main/java/org/springframework/security/web/headers/HeadersFilter.java b/web/src/main/java/org/springframework/security/web/header/HeaderWriterFilter.java similarity index 91% rename from web/src/main/java/org/springframework/security/web/headers/HeadersFilter.java rename to web/src/main/java/org/springframework/security/web/header/HeaderWriterFilter.java index 27e4f072fe..1695e61b10 100644 --- a/web/src/main/java/org/springframework/security/web/headers/HeadersFilter.java +++ b/web/src/main/java/org/springframework/security/web/header/HeaderWriterFilter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers; +package org.springframework.security.web.header; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; @@ -33,7 +33,7 @@ import java.util.*; * @since 3.2 * */ -public class HeadersFilter extends OncePerRequestFilter { +public class HeaderWriterFilter extends OncePerRequestFilter { /** Collection of {@link HeaderWriter} instances to write out the headers to the response . */ private final List headerWriters; @@ -43,7 +43,7 @@ public class HeadersFilter extends OncePerRequestFilter { * * @param headerWriters the {@link HeaderWriter} instances to write out headers to the {@link HttpServletResponse}. */ - public HeadersFilter(List headerWriters) { + public HeaderWriterFilter(List headerWriters) { Assert.notEmpty(headerWriters, "headerWriters cannot be null"); this.headerWriters = headerWriters; } diff --git a/web/src/main/java/org/springframework/security/web/header/writers/CacheControlHeadersWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/CacheControlHeadersWriter.java new file mode 100644 index 0000000000..a8c82a8218 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/writers/CacheControlHeadersWriter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2013 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.header.writers; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.security.web.header.Header; + +/** + * A {@link StaticHeadersWriter} that inserts headers to prevent caching. + * Specifically it adds the following headers: + *
    + *
  • Cache-Control: no-cache, no-store, max-age=0, must-revalidate
  • + *
  • Pragma: no-cache
  • + *
+ * + * @author Rob Winch + * @since 3.2 + */ +public final class CacheControlHeadersWriter extends StaticHeadersWriter { + + /** + * Creates a new instance + */ + public CacheControlHeadersWriter() { + super(createHeaders()); + } + + private static List
createHeaders() { + List
headers = new ArrayList
(2); + headers.add(new Header("Cache-Control","no-cache","no-store","max-age=0","must-revalidate")); + headers.add(new Header("Pragma","no-cache")); + return headers; + } +} diff --git a/web/src/main/java/org/springframework/security/web/headers/DelegatingRequestMatcherHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/DelegatingRequestMatcherHeaderWriter.java similarity index 95% rename from web/src/main/java/org/springframework/security/web/headers/DelegatingRequestMatcherHeaderWriter.java rename to web/src/main/java/org/springframework/security/web/header/writers/DelegatingRequestMatcherHeaderWriter.java index b96383534b..35fe949f29 100644 --- a/web/src/main/java/org/springframework/security/web/headers/DelegatingRequestMatcherHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/DelegatingRequestMatcherHeaderWriter.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers; +package org.springframework.security.web.header.writers; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.security.web.header.HeaderWriter; import org.springframework.security.web.util.RequestMatcher; import org.springframework.util.Assert; diff --git a/web/src/main/java/org/springframework/security/web/headers/HstsHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/HstsHeaderWriter.java similarity index 75% rename from web/src/main/java/org/springframework/security/web/headers/HstsHeaderWriter.java rename to web/src/main/java/org/springframework/security/web/header/writers/HstsHeaderWriter.java index 2e79049536..f1df6419c6 100644 --- a/web/src/main/java/org/springframework/security/web/headers/HstsHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/HstsHeaderWriter.java @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers; +package org.springframework.security.web.header.writers; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.security.web.header.HeaderWriter; import org.springframework.security.web.util.RequestMatcher; import org.springframework.util.Assert; @@ -48,11 +49,13 @@ import org.springframework.util.Assert; * @since 3.2 */ public final class HstsHeaderWriter implements HeaderWriter { + private static final long DEFAULT_MAX_AGE_SECONDS = 31536000; + private static final String HSTS_HEADER_NAME = "Strict-Transport-Security"; private final Log logger = LogFactory.getLog(getClass()); - private RequestMatcher requestMatcher = new SecureRequestMatcher(); + private RequestMatcher requestMatcher; private long maxAgeInSeconds; @@ -60,12 +63,57 @@ public final class HstsHeaderWriter implements HeaderWriter { private String hstsHeaderValue; - public HstsHeaderWriter() { - this.maxAgeInSeconds = 31536000; - this.includeSubDomains = true; + /** + * Creates a new instance + * + * @param requestMatcher maps to {@link #setRequestMatcher(RequestMatcher)} + * @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)} + * @param includeSubDomains maps to {@link #setIncludeSubDomains(boolean)} + */ + public HstsHeaderWriter(RequestMatcher requestMatcher, + long maxAgeInSeconds, boolean includeSubDomains) { + super(); + this.requestMatcher = requestMatcher; + this.maxAgeInSeconds = maxAgeInSeconds; + this.includeSubDomains = includeSubDomains; updateHstsHeaderValue(); } + /** + * Creates a new instance + * + * @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)} + * @param includeSubDomains maps to {@link #setIncludeSubDomains(boolean)} + */ + public HstsHeaderWriter(long maxAgeInSeconds, boolean includeSubDomains) { + this(new SecureRequestMatcher(),maxAgeInSeconds,includeSubDomains); + } + + /** + * Creates a new instance + * + * @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)} + */ + public HstsHeaderWriter(long maxAgeInSeconds) { + this(new SecureRequestMatcher(),maxAgeInSeconds,true); + } + + /** + * Creates a new instance + * + * @param includeSubDomains maps to {@link #setIncludeSubDomains(boolean)} + */ + public HstsHeaderWriter(boolean includeSubDomains) { + this(new SecureRequestMatcher(),DEFAULT_MAX_AGE_SECONDS,includeSubDomains); + } + + /** + * Creates a new instance + */ + public HstsHeaderWriter() { + this(DEFAULT_MAX_AGE_SECONDS); + } + /* * (non-Javadoc) * diff --git a/web/src/main/java/org/springframework/security/web/headers/StaticHeadersWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/StaticHeadersWriter.java similarity index 89% rename from web/src/main/java/org/springframework/security/web/headers/StaticHeadersWriter.java rename to web/src/main/java/org/springframework/security/web/header/writers/StaticHeadersWriter.java index 4201e44cca..d06c6f9630 100644 --- a/web/src/main/java/org/springframework/security/web/headers/StaticHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/StaticHeadersWriter.java @@ -1,4 +1,4 @@ -package org.springframework.security.web.headers; +package org.springframework.security.web.header.writers; import java.util.Collections; import java.util.List; @@ -6,6 +6,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.security.web.header.Header; +import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; /** diff --git a/web/src/main/java/org/springframework/security/web/header/writers/XContentTypeOptionsHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/XContentTypeOptionsHeaderWriter.java new file mode 100644 index 0000000000..9ac2ecb260 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/writers/XContentTypeOptionsHeaderWriter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2013 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.header.writers; + + +/** + * A {@link StaticHeadersWriter} that inserts headers to prevent content + * sniffing. Specifically the following headers are set: + *
    + *
  • X-Content-Type-Options: nosniff
  • + *
+ * + * @author Rob Winch + * @since 3.2 + */ +public final class XContentTypeOptionsHeaderWriter extends StaticHeadersWriter { + + /** + * Creates a new instance + */ + public XContentTypeOptionsHeaderWriter() { + super("X-Content-Type-Options","nosniff"); + } +} diff --git a/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java new file mode 100644 index 0000000000..7b3b76f1e5 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2013 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.header.writers; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.web.header.HeaderWriter; + +/** + * Renders the X-XSS-Protection header. + * + * @author Rob Winch + * @since 3.2 + */ +public final class XXssProtectionHeaderWriter implements HeaderWriter { + private static final String XSS_PROTECTION_HEADER = "X-XSS-Protection"; + + private boolean enabled; + + private boolean block; + + private String headerValue; + + /** + * Create a new instance + */ + public XXssProtectionHeaderWriter() { + this.enabled = true; + this.block = true; + updateHeaderValue(); + } + + @Override + public void writeHeaders(HttpServletRequest request, + HttpServletResponse response) { + response.setHeader(XSS_PROTECTION_HEADER, headerValue); + } + + /** + * If true, will contain a value of 1. For example: + * + *
+     * X-XSS-Protection: 1
+     * 
+ * + * or if {@link #setBlock(boolean)} is true + * + * + *
+     * X-XSS-Protection: 1; mode=block
+     * 
+ * + * If false, will explicitly disable specify that X-XSS-Protection is + * disabled. For example: + * + *
+     * X-XSS-Protection: 0
+     * 
+ * + * @param enabled the new value + */ + public void setEnabled(boolean enabled) { + if(!enabled) { + setBlock(false); + } + this.enabled = enabled; + updateHeaderValue(); + } + + + /** + * If false, will not specify the mode as blocked. In this instance, any + * content will be attempted to be fixed. If true, the content will be + * replaced with "#". + * + * @param enabled + * the new value + */ + public void setBlock(boolean block) { + if(!enabled && block) { + throw new IllegalArgumentException("Cannot set block to true with enabled false"); + } + this.block = block; + updateHeaderValue(); + } + + private void updateHeaderValue() { + if(!enabled) { + this.headerValue = "0"; + return; + } + this.headerValue = "1"; + if(block) { + this.headerValue += "; mode=block"; + } + } + + @Override + public String toString() { + return getClass().getName() + " [headerValue=" + headerValue + "]"; + } +} diff --git a/web/src/main/java/org/springframework/security/web/headers/frameoptions/AbstractRequestParameterAllowFromStrategy.java b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/AbstractRequestParameterAllowFromStrategy.java similarity index 96% rename from web/src/main/java/org/springframework/security/web/headers/frameoptions/AbstractRequestParameterAllowFromStrategy.java rename to web/src/main/java/org/springframework/security/web/header/writers/frameoptions/AbstractRequestParameterAllowFromStrategy.java index 91cfb64e6b..07244f1b0b 100644 --- a/web/src/main/java/org/springframework/security/web/headers/frameoptions/AbstractRequestParameterAllowFromStrategy.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/AbstractRequestParameterAllowFromStrategy.java @@ -1,4 +1,4 @@ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/web/src/main/java/org/springframework/security/web/headers/frameoptions/AllowFromStrategy.java b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/AllowFromStrategy.java similarity index 90% rename from web/src/main/java/org/springframework/security/web/headers/frameoptions/AllowFromStrategy.java rename to web/src/main/java/org/springframework/security/web/header/writers/frameoptions/AllowFromStrategy.java index be62b49be2..db0868a6e5 100644 --- a/web/src/main/java/org/springframework/security/web/headers/frameoptions/AllowFromStrategy.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/AllowFromStrategy.java @@ -1,4 +1,4 @@ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import javax.servlet.http.HttpServletRequest; diff --git a/web/src/main/java/org/springframework/security/web/headers/frameoptions/RegExpAllowFromStrategy.java b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/RegExpAllowFromStrategy.java similarity index 93% rename from web/src/main/java/org/springframework/security/web/headers/frameoptions/RegExpAllowFromStrategy.java rename to web/src/main/java/org/springframework/security/web/header/writers/frameoptions/RegExpAllowFromStrategy.java index dd93c20fa4..739de0e144 100644 --- a/web/src/main/java/org/springframework/security/web/headers/frameoptions/RegExpAllowFromStrategy.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/RegExpAllowFromStrategy.java @@ -1,4 +1,4 @@ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import org.springframework.util.Assert; diff --git a/web/src/main/java/org/springframework/security/web/headers/frameoptions/StaticAllowFromStrategy.java b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/StaticAllowFromStrategy.java similarity index 85% rename from web/src/main/java/org/springframework/security/web/headers/frameoptions/StaticAllowFromStrategy.java rename to web/src/main/java/org/springframework/security/web/header/writers/frameoptions/StaticAllowFromStrategy.java index 8cfc4e5dfb..1c9257b400 100644 --- a/web/src/main/java/org/springframework/security/web/headers/frameoptions/StaticAllowFromStrategy.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/StaticAllowFromStrategy.java @@ -1,4 +1,4 @@ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import javax.servlet.http.HttpServletRequest; import java.net.URI; diff --git a/web/src/main/java/org/springframework/security/web/headers/frameoptions/WhiteListedAllowFromStrategy.java b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/WhiteListedAllowFromStrategy.java similarity index 91% rename from web/src/main/java/org/springframework/security/web/headers/frameoptions/WhiteListedAllowFromStrategy.java rename to web/src/main/java/org/springframework/security/web/header/writers/frameoptions/WhiteListedAllowFromStrategy.java index 5289e752f1..ae477f67ae 100644 --- a/web/src/main/java/org/springframework/security/web/headers/frameoptions/WhiteListedAllowFromStrategy.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/WhiteListedAllowFromStrategy.java @@ -1,4 +1,4 @@ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import java.util.Collection; diff --git a/web/src/main/java/org/springframework/security/web/headers/frameoptions/XFrameOptionsHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/XFrameOptionsHeaderWriter.java similarity index 92% rename from web/src/main/java/org/springframework/security/web/headers/frameoptions/XFrameOptionsHeaderWriter.java rename to web/src/main/java/org/springframework/security/web/header/writers/frameoptions/XFrameOptionsHeaderWriter.java index 326135f60c..5fdc0db037 100644 --- a/web/src/main/java/org/springframework/security/web/headers/frameoptions/XFrameOptionsHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/XFrameOptionsHeaderWriter.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; -import org.springframework.security.web.headers.HeaderWriter; +import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; import javax.servlet.http.HttpServletRequest; @@ -38,6 +38,13 @@ public class XFrameOptionsHeaderWriter implements HeaderWriter { private final AllowFromStrategy allowFromStrategy; private final XFrameOptionsMode frameOptionsMode; + /** + * Creates an instance with {@link XFrameOptionsMode#DENY} + */ + public XFrameOptionsHeaderWriter() { + this(XFrameOptionsMode.DENY); + } + /** * Creates a new instance * diff --git a/web/src/test/java/org/springframework/security/web/headers/HeadersFilterTests.java b/web/src/test/java/org/springframework/security/web/header/HeaderWriterFilterTests.java similarity index 86% rename from web/src/test/java/org/springframework/security/web/headers/HeadersFilterTests.java rename to web/src/test/java/org/springframework/security/web/header/HeaderWriterFilterTests.java index c0411bf3c1..d38710b135 100644 --- a/web/src/test/java/org/springframework/security/web/headers/HeadersFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/header/HeaderWriterFilterTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers; +package org.springframework.security.web.header; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.verify; @@ -28,6 +28,8 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.web.header.HeaderWriter; +import org.springframework.security.web.header.HeaderWriterFilter; /** * Tests for the {@code HeadersFilter} @@ -37,7 +39,7 @@ import org.springframework.mock.web.MockHttpServletResponse; * @since 3.2 */ @RunWith(MockitoJUnitRunner.class) -public class HeadersFilterTests { +public class HeaderWriterFilterTests { @Mock private HeaderWriter writer1; @@ -47,12 +49,12 @@ public class HeadersFilterTests { @Test(expected = IllegalArgumentException.class) public void noHeadersConfigured() throws Exception { List headerWriters = new ArrayList(); - new HeadersFilter(headerWriters); + new HeaderWriterFilter(headerWriters); } @Test(expected = IllegalArgumentException.class) public void constructorNullWriters() throws Exception { - new HeadersFilter(null); + new HeaderWriterFilter(null); } @Test @@ -61,7 +63,7 @@ public class HeadersFilterTests { headerWriters.add(writer1); headerWriters.add(writer2); - HeadersFilter filter = new HeadersFilter(headerWriters); + HeaderWriterFilter filter = new HeaderWriterFilter(headerWriters); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); diff --git a/web/src/test/java/org/springframework/security/web/header/writers/CacheControlHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/CacheControlHeadersWriterTests.java new file mode 100644 index 0000000000..0ca54df29a --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/header/writers/CacheControlHeadersWriterTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2013 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.header.writers; + +import static org.fest.assertions.Assertions.assertThat; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * @author Rob Winch + * + */ +public class CacheControlHeadersWriterTests { + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private CacheControlHeadersWriter writer; + + @Before + public void setup() { + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + writer = new CacheControlHeadersWriter(); + } + + @Test + public void writeHeaders() { + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(2); + assertThat(response.getHeaderValues("Cache-Control")).isEqualTo(Arrays.asList("no-cache","no-store","max-age=0","must-revalidate")); + assertThat(response.getHeaderValues("Pragma")).isEqualTo(Arrays.asList("no-cache")); + } +} diff --git a/web/src/test/java/org/springframework/security/web/headers/DelegatingRequestMatcherHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/DelegatingRequestMatcherHeaderWriterTests.java similarity index 95% rename from web/src/test/java/org/springframework/security/web/headers/DelegatingRequestMatcherHeaderWriterTests.java rename to web/src/test/java/org/springframework/security/web/header/writers/DelegatingRequestMatcherHeaderWriterTests.java index 77f4a6e93a..0d186beec2 100644 --- a/web/src/test/java/org/springframework/security/web/headers/DelegatingRequestMatcherHeaderWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/DelegatingRequestMatcherHeaderWriterTests.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers; +package org.springframework.security.web.header.writers; -import static org.junit.Assert.fail; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -27,6 +26,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.web.header.HeaderWriter; import org.springframework.security.web.util.RequestMatcher; /** diff --git a/web/src/test/java/org/springframework/security/web/header/writers/HstsHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/HstsHeaderWriterTests.java new file mode 100644 index 0000000000..3ffb9e8ee7 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/header/writers/HstsHeaderWriterTests.java @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2013 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.header.writers; + +import static org.fest.assertions.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.web.header.writers.HstsHeaderWriter; +import org.springframework.security.web.util.AnyRequestMatcher; + +/** + * @author Rob Winch + * + */ +public class HstsHeaderWriterTests { + private MockHttpServletRequest request; + private MockHttpServletResponse response; + + private HstsHeaderWriter writer; + + @Before + public void setup() { + request = new MockHttpServletRequest(); + request.setSecure(true); + response = new MockHttpServletResponse(); + + writer = new HstsHeaderWriter(); + } + + @Test + public void allArgsCustomConstructorWriteHeaders() { + request.setSecure(false); + writer = new HstsHeaderWriter(new AnyRequestMatcher(), 15768000, false); + + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=15768000"); + } + + @Test + public void maxAgeAndIncludeSubdomainsCustomConstructorWriteHeaders() { + request.setSecure(false); + writer = new HstsHeaderWriter(new AnyRequestMatcher(), 15768000, false); + + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=15768000"); + } + + @Test + public void maxAgeCustomConstructorWriteHeaders() { + writer = new HstsHeaderWriter(15768000); + + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=15768000 ; includeSubDomains"); + } + + @Test + public void includeSubDomainsCustomConstructorWriteHeaders() { + writer = new HstsHeaderWriter(false); + + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=31536000"); + } + + @Test + public void writeHeadersDefaultValues() { + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=31536000 ; includeSubDomains"); + } + + @Test + public void writeHeadersIncludeSubDomainsFalse() { + writer.setIncludeSubDomains(false); + + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=31536000"); + } + + @Test + public void writeHeadersCustomMaxAgeInSeconds() { + writer.setMaxAgeInSeconds(1); + + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=1 ; includeSubDomains"); + } + + @Test + public void writeHeadersInsecureRequestDoesNotWriteHeader() { + request.setSecure(false); + + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().isEmpty()).isTrue(); + } + + @Test + public void writeHeadersAnyRequestMatcher() { + writer.setRequestMatcher(new AnyRequestMatcher()); + request.setSecure(false); + + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=31536000 ; includeSubDomains"); + } + + @Test(expected = IllegalArgumentException.class) + public void setMaxAgeInSecondsToNegative() { + writer.setMaxAgeInSeconds(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void setRequestMatcherToNull() { + writer.setRequestMatcher(null); + } +} diff --git a/web/src/test/java/org/springframework/security/web/headers/StaticHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/StaticHeaderWriterTests.java similarity index 94% rename from web/src/test/java/org/springframework/security/web/headers/StaticHeaderWriterTests.java rename to web/src/test/java/org/springframework/security/web/header/writers/StaticHeaderWriterTests.java index 67a7a105b3..a09a6efe3d 100644 --- a/web/src/test/java/org/springframework/security/web/headers/StaticHeaderWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/StaticHeaderWriterTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers; +package org.springframework.security.web.header.writers; import static org.fest.assertions.Assertions.assertThat; @@ -24,6 +24,8 @@ import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.web.header.Header; +import org.springframework.security.web.header.writers.StaticHeadersWriter; /** * Test for the {@code StaticHeadersWriter} diff --git a/web/src/test/java/org/springframework/security/web/header/writers/XContentTypeOptionsHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/XContentTypeOptionsHeaderWriterTests.java new file mode 100644 index 0000000000..eb4be1e090 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/header/writers/XContentTypeOptionsHeaderWriterTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2013 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.header.writers; + +import static org.fest.assertions.Assertions.assertThat; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * @author Rob Winch + * + */ +public class XContentTypeOptionsHeaderWriterTests { + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private XContentTypeOptionsHeaderWriter writer; + + @Before + public void setup() { + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + writer = new XContentTypeOptionsHeaderWriter(); + } + + @Test + public void writeHeaders() { + writer.writeHeaders(request, response); + + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeaderValues("X-Content-Type-Options")).isEqualTo(Arrays.asList("nosniff")); + } +} diff --git a/web/src/test/java/org/springframework/security/web/headers/HstsHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriterTests.java similarity index 56% rename from web/src/test/java/org/springframework/security/web/headers/HstsHeaderWriterTests.java rename to web/src/test/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriterTests.java index 8182396e1e..fd3b6a6a5e 100644 --- a/web/src/test/java/org/springframework/security/web/headers/HstsHeaderWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriterTests.java @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers; +package org.springframework.security.web.header.writers; import static org.fest.assertions.Assertions.assertThat; +import java.util.Arrays; + import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; @@ -26,65 +28,66 @@ import org.springframework.mock.web.MockHttpServletResponse; * @author Rob Winch * */ -public class HstsHeaderWriterTests { +public class XXssProtectionHeaderWriterTests { + private MockHttpServletRequest request; + private MockHttpServletResponse response; - private HstsHeaderWriter writer; + private XXssProtectionHeaderWriter writer; @Before public void setup() { request = new MockHttpServletRequest(); - request.setSecure(true); response = new MockHttpServletResponse(); - - writer = new HstsHeaderWriter(); + writer = new XXssProtectionHeaderWriter(); } @Test - public void writeHeadersDefaultValues() { + public void writeHeaders() { writer.writeHeaders(request, response); assertThat(response.getHeaderNames().size()).isEqualTo(1); - assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=31536000 ; includeSubDomains"); + assertThat(response.getHeaderValues("X-XSS-Protection")).isEqualTo(Arrays.asList("1; mode=block")); } @Test - public void writeHeadersIncludeSubDomainsFalse() { - writer.setIncludeSubDomains(false); + public void writeHeadersNoBlock() { + writer.setBlock(false); writer.writeHeaders(request, response); assertThat(response.getHeaderNames().size()).isEqualTo(1); - assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=31536000"); + assertThat(response.getHeaderValues("X-XSS-Protection")).isEqualTo(Arrays.asList("1")); } @Test - public void writeHeadersCustomMaxAgeInSeconds() { - writer.setMaxAgeInSeconds(1); + public void writeHeadersDisabled() { + writer.setBlock(false); + writer.setEnabled(false); writer.writeHeaders(request, response); assertThat(response.getHeaderNames().size()).isEqualTo(1); - assertThat(response.getHeader("Strict-Transport-Security")).isEqualTo("max-age=1 ; includeSubDomains"); + assertThat(response.getHeaderValues("X-XSS-Protection")).isEqualTo(Arrays.asList("0")); } @Test - public void writeHeadersInsecureRequestDoesNotWriteHeader() { - request.setSecure(false); + public void setEnabledFalseWithBlockTrue() { + writer.setEnabled(false); writer.writeHeaders(request, response); - assertThat(response.getHeaderNames().isEmpty()).isTrue(); + assertThat(response.getHeaderNames().size()).isEqualTo(1); + assertThat(response.getHeaderValues("X-XSS-Protection")).isEqualTo(Arrays.asList("0")); } - @Test(expected = IllegalArgumentException.class) - public void setMaxAgeInSecondsToNegative() { - writer.setMaxAgeInSeconds(-1); - } - @Test(expected = IllegalArgumentException.class) - public void setRequestMatcherToNull() { - writer.setRequestMatcher(null); + @Test(expected=IllegalArgumentException.class) + public void setBlockTrueWithEnabledFalse() { + writer.setBlock(false); + writer.setEnabled(false); + + writer.setBlock(true); } } diff --git a/web/src/test/java/org/springframework/security/web/headers/frameoptions/AbstractRequestParameterAllowFromStrategyTests.java b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/AbstractRequestParameterAllowFromStrategyTests.java similarity index 94% rename from web/src/test/java/org/springframework/security/web/headers/frameoptions/AbstractRequestParameterAllowFromStrategyTests.java rename to web/src/test/java/org/springframework/security/web/header/writers/frameoptions/AbstractRequestParameterAllowFromStrategyTests.java index 8c541c1ee7..7aa9fc0184 100644 --- a/web/src/test/java/org/springframework/security/web/headers/frameoptions/AbstractRequestParameterAllowFromStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/AbstractRequestParameterAllowFromStrategyTests.java @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import static org.fest.assertions.Assertions.assertThat; import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.web.header.writers.frameoptions.AbstractRequestParameterAllowFromStrategy; /** * @author Rob Winch diff --git a/web/src/test/java/org/springframework/security/web/headers/frameoptions/FrameOptionsHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/FrameOptionsHeaderWriterTests.java similarity index 90% rename from web/src/test/java/org/springframework/security/web/headers/frameoptions/FrameOptionsHeaderWriterTests.java rename to web/src/test/java/org/springframework/security/web/header/writers/frameoptions/FrameOptionsHeaderWriterTests.java index ae6e704fdc..429922c024 100644 --- a/web/src/test/java/org/springframework/security/web/headers/frameoptions/FrameOptionsHeaderWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/FrameOptionsHeaderWriterTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -27,7 +27,9 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.web.headers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode; +import org.springframework.security.web.header.writers.frameoptions.AllowFromStrategy; +import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; +import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode; /** * @author Rob Winch diff --git a/web/src/test/java/org/springframework/security/web/headers/frameoptions/RegExpAllowFromStrategyTests.java b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/RegExpAllowFromStrategyTests.java similarity index 91% rename from web/src/test/java/org/springframework/security/web/headers/frameoptions/RegExpAllowFromStrategyTests.java rename to web/src/test/java/org/springframework/security/web/header/writers/frameoptions/RegExpAllowFromStrategyTests.java index 45119b9e50..9830c9d571 100644 --- a/web/src/test/java/org/springframework/security/web/headers/frameoptions/RegExpAllowFromStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/RegExpAllowFromStrategyTests.java @@ -1,4 +1,4 @@ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -7,6 +7,7 @@ import java.util.regex.PatternSyntaxException; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.web.header.writers.frameoptions.RegExpAllowFromStrategy; /** * diff --git a/web/src/test/java/org/springframework/security/web/headers/frameoptions/StaticAllowFromStrategyTests.java b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/StaticAllowFromStrategyTests.java similarity index 77% rename from web/src/test/java/org/springframework/security/web/headers/frameoptions/StaticAllowFromStrategyTests.java rename to web/src/test/java/org/springframework/security/web/header/writers/frameoptions/StaticAllowFromStrategyTests.java index bb941db474..2a14ee7e86 100644 --- a/web/src/test/java/org/springframework/security/web/headers/frameoptions/StaticAllowFromStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/StaticAllowFromStrategyTests.java @@ -1,7 +1,8 @@ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy; import java.net.URI; diff --git a/web/src/test/java/org/springframework/security/web/headers/frameoptions/WhiteListedAllowFromStrategyTests.java b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/WhiteListedAllowFromStrategyTests.java similarity index 94% rename from web/src/test/java/org/springframework/security/web/headers/frameoptions/WhiteListedAllowFromStrategyTests.java rename to web/src/test/java/org/springframework/security/web/header/writers/frameoptions/WhiteListedAllowFromStrategyTests.java index 4f3f93db9c..e4312f18cc 100644 --- a/web/src/test/java/org/springframework/security/web/headers/frameoptions/WhiteListedAllowFromStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/header/writers/frameoptions/WhiteListedAllowFromStrategyTests.java @@ -1,7 +1,8 @@ -package org.springframework.security.web.headers.frameoptions; +package org.springframework.security.web.header.writers.frameoptions; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.web.header.writers.frameoptions.WhiteListedAllowFromStrategy; import java.util.ArrayList; import java.util.List;