SEC-2283: Update headers documentation and tests

This commit is contained in:
Rob Winch 2013-08-28 12:35:40 -05:00
parent 4761614c9f
commit d89cf6db29
5 changed files with 434 additions and 20 deletions

View File

@ -239,6 +239,75 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
return getOrApply(new OpenIDLoginConfigurer<HttpSecurity>());
}
/**
* Adds the Security headers to the response. This is activated by default
* when using {@link WebSecurityConfigurerAdapter}'s default constructor.
* Only invoking the {@link #headers()} without invoking additional methods
* on it, or accepting the default provided by
* {@link WebSecurityConfigurerAdapter}, is the equivalent of:
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .headers()
* .contentTypeOptions();
* .xssProtection()
* .cacheControl()
* .httpStrictTransportSecurity()
* .frameOptions()
* .and()
* ...;
* }
* }
* </pre>
*
* You can disable the headers using the following:
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .headers().disable()
* ...;
* }
* }
* </pre>
*
* You can enable only a few of the headers by invoking the appropriate
* methods on {@link #headers()} result. For example, the following will
* enable {@link HeadersConfigurer#cacheControl()} and
* {@link HeadersConfigurer#frameOptions()} only.
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .headers()
* .cacheControl()
* .frameOptions()
* .and()
* ...;
* }
* }
* </pre>
*
* @return
* @throws Exception
* @see {@link HeadersConfigurer}
*/
public HeadersConfigurer<HttpSecurity> headers() throws Exception {
return getOrApply(new HeadersConfigurer<HttpSecurity>());
}
@ -664,7 +733,23 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
/**
* Adds CSRF support
* Adds CSRF support. This is activated by default when using
* {@link WebSecurityConfigurerAdapter}'s default constructor. You can
* disable it using:
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .csrf().disable()
* ...;
* }
* }
* </pre>
*
* @return the {@link ServletApiConfigurer} for further customizations
* @throws Exception

View File

@ -20,6 +20,7 @@ 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
@ -30,15 +31,80 @@ import org.springframework.security.web.header.writers.frameoptions.XFrameOption
import org.springframework.util.Assert;
/**
* Adds the Security headers to the response. This is activated by default when
* using {@link WebSecurityConfigurerAdapter}'s default constructor. Only
* invoking the {@link #headers()} without invoking additional methods on it, or
* accepting the default provided by {@link WebSecurityConfigurerAdapter}, is
* the equivalent of:
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .headers()
* .contentTypeOptions();
* .xssProtection()
* .cacheControl()
* .httpStrictTransportSecurity()
* .frameOptions()
* .and()
* ...;
* }
* }
* </pre>
*
* You can disable the headers using the following:
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .headers().disable()
* ...;
* }
* }
* </pre>
*
* You can enable only a few of the headers by invoking the appropriate methods
* on {@link #headers()} result. For example, the following will enable
* {@link HeadersConfigurer#cacheControl()} and
* {@link HeadersConfigurer#frameOptions()} only.
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .headers()
* .cacheControl()
* .frameOptions()
* .and()
* ...;
* }
* }
* </pre>
*
* @author Rob Winch
* @since 3.2
* @see RememberMeConfigurer
*/
public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<HeadersConfigurer<H>,H> {
public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractHttpConfigurer<HeadersConfigurer<H>, H> {
private List<HeaderWriter> headerWriters = new ArrayList<HeaderWriter>();
/**
* Creates a new instance
*
* @see HttpSecurity#headers()
*/
public HeadersConfigurer() {
@ -46,7 +112,9 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends A
/**
* Adds a {@link HeaderWriter} instance
* @param headerWriter the {@link HeaderWriter} instance to add
*
* @param headerWriter
* the {@link HeaderWriter} instance to add
* @return the {@link HeadersConfigurer} for additional customizations
*/
public HeadersConfigurer<H> addHeaderWriter(HeaderWriter headerWriter) {
@ -56,7 +124,13 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends A
}
/**
* Adds {@link XContentTypeOptionsHeaderWriter}
* Adds {@link XContentTypeOptionsHeaderWriter} which inserts the <a href=
* "http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx"
* >X-Content-Type-Options</a>:
*
* <pre>
* X-Content-Type-Options: nosniff
* </pre>
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
@ -65,8 +139,11 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends A
}
/**
* Adds {@link XXssProtectionHeaderWriter}. Note this is not comprehensive
* XSS protection!
* <strong>Note this is not comprehensive XSS protection!</strong>
*
* <para>Adds {@link XXssProtectionHeaderWriter} which adds the <a href=
* "http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx"
* >X-XSS-Protection header</a>
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
@ -75,7 +152,12 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends A
}
/**
* Adds {@link CacheControlHeadersWriter}.
* Adds {@link CacheControlHeadersWriter}. Specifically it adds the
* following headers:
* <ul>
* <li>Cache-Control: no-cache, no-store, max-age=0, must-revalidate</li>
* <li>Pragma: no-cache</li>
* </ul>
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
@ -84,7 +166,15 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends A
}
/**
* Adds {@link HstsHeaderWriter}.
* Adds {@link HstsHeaderWriter} which provides support for <a
* href="http://tools.ietf.org/html/rfc6797">HTTP Strict Transport Security
* (HSTS)</a>.
*
* <p>
* For additional configuration options, use
* {@link #addHeaderWriter(HeaderWriter)} and {@link HstsHeaderWriter}
* directly.
* </p>
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
@ -93,7 +183,10 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends A
}
/**
* Adds {@link XFrameOptionsHeaderWriter} with all the default settings.
* Adds {@link XFrameOptionsHeaderWriter} with all the default settings. For
* additional configuration options, use
* {@link #addHeaderWriter(HeaderWriter)} and
* {@link XFrameOptionsHeaderWriter} directly.
*
* @return the {@link HeadersConfigurer} for additional customizations
*/
@ -109,20 +202,24 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends A
/**
* Creates the {@link HeaderWriter}
*
* @return the {@link HeaderWriter}
*/
private HeaderWriterFilter createHeaderWriterFilter() {
HeaderWriterFilter headersFilter = new HeaderWriterFilter(getHeaderWriters());
HeaderWriterFilter headersFilter = new HeaderWriterFilter(
getHeaderWriters());
headersFilter = postProcess(headersFilter);
return headersFilter;
}
/**
* Gets the {@link HeaderWriter} instances and possibly initializes with the defaults.
* Gets the {@link HeaderWriter} instances and possibly initializes with the
* defaults.
*
* @return
*/
private List<HeaderWriter> getHeaderWriters() {
if(headerWriters.isEmpty()) {
if (headerWriters.isEmpty()) {
addDefaultHeaderWriters();
}
return headerWriters;

View File

@ -0,0 +1,162 @@
/*
* 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 javax.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import spock.lang.Unroll;
/**
*
* @author Rob Winch
*/
class HeadersConfigurerTests extends BaseSpringSpec {
def "headers"() {
setup:
loadConfig(HeadersConfig)
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
@EnableWebSecurity
static class HeadersConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
}
}
def "headers.contentType"() {
setup:
loadConfig(ContentTypeOptionsConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['X-Content-Type-Options':'nosniff']
}
@Configuration
@EnableWebSecurity
static class ContentTypeOptionsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().contentTypeOptions()
}
}
def "headers.frameOptions"() {
setup:
loadConfig(FrameOptionsConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['X-Frame-Options':'DENY']
}
@Configuration
@EnableWebSecurity
static class FrameOptionsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions()
}
}
def "headers.hsts"() {
setup:
loadConfig(HstsConfig)
request.secure = true
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains']
}
@Configuration
@EnableWebSecurity
static class HstsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().httpStrictTransportSecurity()
}
}
def "headers.cacheControl"() {
setup:
loadConfig(CacheControlConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
'Pragma':'no-cache']
}
@Configuration
@EnableWebSecurity
static class CacheControlConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl()
}
}
def "headers.xssProtection"() {
setup:
loadConfig(XssProtectionConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['X-XSS-Protection' : '1; mode=block']
}
@Configuration
@EnableWebSecurity
static class XssProtectionConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().xssProtection()
}
}
}

View File

@ -354,7 +354,7 @@
<section xml:id="nsa-frame-options-attributes">
<title><literal>&lt;frame-options&gt;</literal> Attributes</title>
<section xml:id="nsa-frame-options-policy">
<title><literal>frame-options-policy</literal></title>
<title><literal>policy</literal></title>
<para>
<itemizedlist>
<listitem><literal>DENY</literal> The page cannot be displayed in a frame, regardless of
@ -372,7 +372,7 @@
</para>
</section>
<section xml:id="nsa-frame-options-strategy">
<title><literal>frame-options-strategy</literal></title>
<title><literal>strategy</literal></title>
<para>
Select the <classname>AllowFromStrategy</classname> to use when using the ALLOW-FROM policy.
<itemizedlist>
@ -393,18 +393,18 @@
</para>
</section>
<section xml:id="nsa-frame-options-ref">
<title><literal>frame-options-ref</literal></title>
<title><literal>ref</literal></title>
<para>
Instead of using one of the predefined strategies it is also possible to use a custom <classname>AllowFromStrategy</classname>.
The reference to this bean can be specified through this ref attribute.
</para>
</section>
<section xml:id="nsa-frame-options-value">
<title><literal>frame-options-value</literal></title>
<title><literal>value</literal></title>
<para>The value to use when ALLOW-FROM is used a <link linkend="nsa-frame-options-strategy">strategy</link>.</para>
</section>
<section xml:id="nsa-frame-options-from-parameter">
<title><literal>frame-options-from-parameter</literal></title>
<title><literal>from-parameter</literal></title>
<para>
Specify the name of the request parameter to use when using regexp or whitelist for the ALLOW-FROM
strategy.

View File

@ -205,7 +205,8 @@ public class WebSecurityConfig extends
<note>
<para>Another modern approach to dealing with clickjacking is using a <link xlink:href="http://www.w3.org/TR/CSP/">Content
Security Policy</link>. Spring Security does not provide
support for this as the specification is not released and it is quite a bit more complicated. To stay up to date with this
support for this as the specification is not released and it is quite a bit more complicated. However, you could use the
<link linkend="headers-static">static headers</link> feature to implement this. To stay up to date with this
issue and to see how you can implement it with Spring Security refer to
<link xlink:href="https://jira.springsource.org/browse/SEC-2117">SEC-2117</link> </para>
</note>
@ -242,7 +243,7 @@ public class WebSecurityConfig extends
}
}]]></programlisting>
</section>
<section xml:id="xss-protection">
<section xml:id="headers-xss-protection">
<title>X-XSS-Protection</title>
<para>Some browsers have built in support for filtering out
<link xlink:href="https://www.owasp.org/index.php/Testing_for_Reflected_Cross_site_scripting_(OWASP-DV-001)">reflected
@ -276,6 +277,75 @@ public class WebSecurityConfig extends
.and()
...;
}
}]]></programlisting>
</section>
<section xml:id="headers-static">
<title>Static Headers</title>
<para>There may be times you wish to inject custom security headers into your application that are not supported out of the box. For example, perhaps
you wish to have early support for <link xlink:href="http://www.w3.org/TR/CSP/">Content Security Policy</link> in order to ensure that resources
are only loaded from the same origin. Since support for Content Security Policy has not been finalized, browsers use one of two common extension headers
to implement the feature. This means we will need to inject the policy twice. An example of the headers can be seen below:</para>
<programlisting><![CDATA[X-Content-Security-Policy: default-src 'self'
X-WebKit-CSP: default-src 'self']]></programlisting>
<para>When using the XML namespace, these headers can be added to the response using the <link linkend="nsa-header">&lt;header&gt;</link> element as
shown below:</para>
<programlisting language="xml"><![CDATA[<http ...>
...
<headers>
<header name="X-Content-Security-Policy" value="default-src 'self'"/>
<header name="X-WebKit-CSP" value="default-src 'self'"/>
</headers>
</http>]]></programlisting>
<para>Similarly, the headers could be added to the response using Java Configuration as shown in the following:</para>
<programlisting language="java"><![CDATA[@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.addHeaderWriter(new StaticHeaderWriter("X-Content-Security-Policy","default-src 'self'"))
.addHeaderWriter(new StaticHeaderWriter("X-WebKit-CSP","default-src 'self'"))
.and()
...;
}
}]]></programlisting>
</section>
<section xml:id="headers-writer">
<title>Headers Writer</title>
<para>When the namespace or Java configuration does not support the headers you want, you can create a custom <interfacename>HeadersWriter</interfacename> instance
or even provide a custom implementation of the <interfacename>HeadersWriter</interfacename>.</para>
<para>Let's take a look at an example of using an custom instance of <classname>XFrameOptionsHeaderWriter</classname>. Perhaps you want to allow framing of content
for the same origin. This is easily supported by setting the <link linkend="nsa-frame-options-policy">policy</link>
attribute to "SAMEORIGIN", but let's take a look at a more explicit example.</para>
<programlisting language="xml"><![CDATA[<http ...>
...
<headers>
<header header-ref="frameOptionsWriter"/>
</headers>
</http>
<!-- Requires the c-namespace.
See http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/beans.html#beans-c-namespace
-->
<bean:bean id="frameOptionsWriter"
class="org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter"
c:frameOptionsMode="SAMEORIGIN"/>]]></programlisting>
<para>We could also restrict framing of content to the same origin with Java configuration:</para>
<programlisting language="java"><![CDATA[@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN))
.and()
...;
}
}]]></programlisting>
</section>