Browse Source

SEC-2283: Update headers documentation and tests

Rob Winch 12 years ago
parent
commit
d89cf6db29

+ 86 - 1
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -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

+ 109 - 12
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java

@@ -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;

+ 162 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.groovy

@@ -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()
+        }
+    }
+}

+ 5 - 5
docs/manual/src/docbook/appendix-namespace.xml

@@ -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.

+ 72 - 2
docs/manual/src/docbook/headers.xml

@@ -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>