From 606bddf598dec3d2985d046d6f4f00920df469b2 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 30 Jul 2013 16:56:46 -0500 Subject: [PATCH] SEC-2230: Add Header JavaConfig Added JavaConfig for Headers. In the process, more HeaderWriter instances were added so that we can reuse logic between the XML and JavaConfig. This also prompted repackaging the writers. --- .../web/builders/FilterComparator.java | 3 + .../annotation/web/builders/HttpSecurity.java | 5 + .../WebSecurityConfigurerAdapter.java | 1 + .../web/configurers/HeadersConfigurer.java | 98 +++++++ .../http/HeadersBeanDefinitionParser.java | 74 ++---- .../config/annotation/BaseSpringSpec.groovy | 8 + .../WebSecurityConfigurerAdapterTests.groovy | 33 +++ .../configurers/DefaultFiltersTests.groovy | 17 +- .../FormLoginConfigurerTests.groovy | 3 +- .../NamespaceHttpHeadersTests.groovy | 248 ++++++++++++++++++ .../config/http/HttpHeadersConfigTests.groovy | 61 ++--- .../web/{headers => header}/Header.java | 2 +- .../web/{headers => header}/HeaderWriter.java | 4 +- .../HeaderWriterFilter.java} | 6 +- .../writers/CacheControlHeadersWriter.java | 49 ++++ .../DelegatingRequestMatcherHeaderWriter.java | 3 +- .../writers}/HstsHeaderWriter.java | 58 +++- .../writers}/StaticHeadersWriter.java | 4 +- .../XContentTypeOptionsHeaderWriter.java | 37 +++ .../writers/XXssProtectionHeaderWriter.java | 118 +++++++++ ...ractRequestParameterAllowFromStrategy.java | 2 +- .../frameoptions/AllowFromStrategy.java | 2 +- .../frameoptions/RegExpAllowFromStrategy.java | 2 +- .../frameoptions/StaticAllowFromStrategy.java | 2 +- .../WhiteListedAllowFromStrategy.java | 2 +- .../XFrameOptionsHeaderWriter.java | 11 +- .../HeaderWriterFilterTests.java} | 12 +- .../CacheControlHeadersWriterTests.java | 54 ++++ ...gatingRequestMatcherHeaderWriterTests.java | 4 +- .../header/writers/HstsHeaderWriterTests.java | 145 ++++++++++ .../writers}/StaticHeaderWriterTests.java | 4 +- .../XContentTypeOptionsHeaderWriterTests.java | 53 ++++ .../XXssProtectionHeaderWriterTests.java} | 51 ++-- ...equestParameterAllowFromStrategyTests.java | 3 +- .../FrameOptionsHeaderWriterTests.java | 6 +- .../RegExpAllowFromStrategyTests.java | 3 +- .../StaticAllowFromStrategyTests.java | 3 +- .../WhiteListedAllowFromStrategyTests.java | 3 +- 38 files changed, 1038 insertions(+), 156 deletions(-) create mode 100644 config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java create mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.groovy rename web/src/main/java/org/springframework/security/web/{headers => header}/Header.java (96%) rename web/src/main/java/org/springframework/security/web/{headers => header}/HeaderWriter.java (93%) rename web/src/main/java/org/springframework/security/web/{headers/HeadersFilter.java => header/HeaderWriterFilter.java} (91%) create mode 100644 web/src/main/java/org/springframework/security/web/header/writers/CacheControlHeadersWriter.java rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/DelegatingRequestMatcherHeaderWriter.java (95%) rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/HstsHeaderWriter.java (75%) rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/StaticHeadersWriter.java (89%) create mode 100644 web/src/main/java/org/springframework/security/web/header/writers/XContentTypeOptionsHeaderWriter.java create mode 100644 web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/frameoptions/AbstractRequestParameterAllowFromStrategy.java (96%) rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/frameoptions/AllowFromStrategy.java (90%) rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/frameoptions/RegExpAllowFromStrategy.java (93%) rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/frameoptions/StaticAllowFromStrategy.java (85%) rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/frameoptions/WhiteListedAllowFromStrategy.java (91%) rename web/src/main/java/org/springframework/security/web/{headers => header/writers}/frameoptions/XFrameOptionsHeaderWriter.java (92%) rename web/src/test/java/org/springframework/security/web/{headers/HeadersFilterTests.java => header/HeaderWriterFilterTests.java} (86%) create mode 100644 web/src/test/java/org/springframework/security/web/header/writers/CacheControlHeadersWriterTests.java rename web/src/test/java/org/springframework/security/web/{headers => header/writers}/DelegatingRequestMatcherHeaderWriterTests.java (95%) create mode 100644 web/src/test/java/org/springframework/security/web/header/writers/HstsHeaderWriterTests.java rename web/src/test/java/org/springframework/security/web/{headers => header/writers}/StaticHeaderWriterTests.java (94%) create mode 100644 web/src/test/java/org/springframework/security/web/header/writers/XContentTypeOptionsHeaderWriterTests.java rename web/src/test/java/org/springframework/security/web/{headers/HstsHeaderWriterTests.java => header/writers/XXssProtectionHeaderWriterTests.java} (56%) rename web/src/test/java/org/springframework/security/web/{headers => header/writers}/frameoptions/AbstractRequestParameterAllowFromStrategyTests.java (94%) rename web/src/test/java/org/springframework/security/web/{headers => header/writers}/frameoptions/FrameOptionsHeaderWriterTests.java (90%) rename web/src/test/java/org/springframework/security/web/{headers => header/writers}/frameoptions/RegExpAllowFromStrategyTests.java (91%) rename web/src/test/java/org/springframework/security/web/{headers => header/writers}/frameoptions/StaticAllowFromStrategyTests.java (77%) rename web/src/test/java/org/springframework/security/web/{headers => header/writers}/frameoptions/WhiteListedAllowFromStrategyTests.java (94%) 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;