2
0
Эх сурвалжийг харах

SEC-2098, SEC-2099: Created HeadersFilter

Created HeadersFilter for setting security headers added including a
bean definition parser for easy configuration of the headers. Enables
easy configuration for the X-Frame-Options, X-XSS-Protection and
X-Content-Type-Options headers. Also allows for additional headers to
be added.
Marten Deinum 12 жил өмнө
parent
commit
0adf5aea91

+ 1 - 0
config/src/main/java/org/springframework/security/config/Elements.java

@@ -54,4 +54,5 @@ public abstract class Elements {
     public static final String LDAP_PASSWORD_COMPARE = "password-compare";
     public static final String DEBUG = "debug";
     public static final String HTTP_FIREWALL = "http-firewall";
+    public static final String ADD_HEADERS = "add-headers";
 }

+ 124 - 0
config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java

@@ -0,0 +1,124 @@
+/*
+ * Copyright 2002-2012 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.http;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.security.web.headers.HeadersFilter;
+import org.springframework.util.StringUtils;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Element;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Parser for the {@code HeadersFilter}.
+ *
+ * @author Marten Deinum
+ * @since 3.2
+ */
+public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
+
+    private static final String ATT_ENABLED = "enabled";
+    private static final String ATT_BLOCK = "block";
+
+    private static final String ATT_POLICY = "policy";
+    private static final String ATT_ORIGIN = "origin";
+
+    private static final String ATT_NAME = "name";
+    private static final String ATT_VALUE = "value";
+
+    private static final String XSS_ELEMENT = "xss-protection";
+    private static final String CONTENT_TYPE_ELEMENT = "content-type-options";
+    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 FRAME_OPTIONS_HEADER = "X-Frame-Options";
+    private static final String CONTENT_TYPE_OPTIONS_HEADER = "X-Content-Type-Options";
+
+    private static final String ALLOW_FROM = "ALLOW-FROM";
+
+    public BeanDefinition parse(Element element, ParserContext parserContext) {
+        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeadersFilter.class);
+        final Map<String, String> headers = new HashMap<String, String>();
+
+        parseXssElement(element, headers);
+        parseFrameOptionsElement(element, parserContext, headers);
+        parseContentTypeOptionsElement(element, headers);
+
+        parseHeaderElements(element, headers);
+
+        builder.addPropertyValue("headers", headers);
+        return builder.getBeanDefinition();
+    }
+
+    private void parseHeaderElements(Element element, Map<String, String> headers) {
+        List<Element> headerEtls = DomUtils.getChildElementsByTagName(element, GENERIC_HEADER_ELEMENT);
+        for (Element headerEtl : headerEtls) {
+            headers.put(headerEtl.getAttribute(ATT_NAME), headerEtl.getAttribute(ATT_VALUE));
+        }
+    }
+
+    private void parseContentTypeOptionsElement(Element element, Map<String, String> headers) {
+        Element contentTypeElt = DomUtils.getChildElementByTagName(element, CONTENT_TYPE_ELEMENT);
+        if (contentTypeElt != null) {
+            headers.put(CONTENT_TYPE_OPTIONS_HEADER, "nosniff");
+        }
+    }
+
+    private void parseFrameOptionsElement(Element element, ParserContext parserContext, Map<String, String> headers) {
+        Element frameElt = DomUtils.getChildElementByTagName(element, FRAME_OPTIONS_ELEMENT);
+        if (frameElt != null) {
+            String header = getAttribute(frameElt, ATT_POLICY, "DENY");
+            if (ALLOW_FROM.equals(header) ) {
+                String origin = frameElt.getAttribute(ATT_ORIGIN);
+                if (!StringUtils.hasText(origin) ) {
+                    parserContext.getReaderContext().error("Frame options header value ALLOW-FROM required an origin to be specified.", frameElt);
+                }
+                header += " " + origin;
+            }
+            headers.put(FRAME_OPTIONS_HEADER, header);
+        }
+    }
+
+    private void parseXssElement(Element element, Map<String, String> headers) {
+        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, "true"));
+
+            String value = enabled ? "1" : "0";
+            if (enabled && block) {
+                value += "; mode=block";
+            }
+            headers.put(XSS_PROTECTION_HEADER, value);
+        }
+    }
+
+    private String getAttribute(Element element, String name, String defaultValue) {
+        String value = element.getAttribute(name);
+        if (StringUtils.hasText(value)) {
+            return value;
+        } else {
+            return defaultValue;
+        }
+    }
+}

+ 14 - 0
config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java

@@ -117,6 +117,7 @@ class HttpConfigurationBuilder {
     private final BeanReference portResolver;
     private BeanReference fsi;
     private BeanReference requestCache;
+    private BeanDefinition addHeadersFilter;
 
     public HttpConfigurationBuilder(Element element, ParserContext pc,
             BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
@@ -151,6 +152,7 @@ class HttpConfigurationBuilder {
         createJaasApiFilter();
         createChannelProcessingFilter();
         createFilterSecurityInterceptor(authenticationManager);
+        createAddHeadersFilter();
     }
 
     @SuppressWarnings("rawtypes")
@@ -554,6 +556,14 @@ class HttpConfigurationBuilder {
         this.fsi = new RuntimeBeanReference(fsiId);
     }
 
+    private void createAddHeadersFilter() {
+        Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.ADD_HEADERS);
+        if (elmt != null) {
+            this.addHeadersFilter = new HeadersBeanDefinitionParser().parse(elmt, pc);
+        }
+
+    }
+
     BeanReference getSessionStrategy() {
         return sessionStrategyRef;
     }
@@ -601,6 +611,10 @@ class HttpConfigurationBuilder {
             filters.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER));
         }
 
+        if (addHeadersFilter != null) {
+            filters.add(new OrderDecorator(addHeadersFilter, HEADERS_FILTER));
+        }
+
         return filters;
     }
 }

+ 1 - 0
config/src/main/java/org/springframework/security/config/http/SecurityFilters.java

@@ -29,6 +29,7 @@ enum SecurityFilters {
     CONCURRENT_SESSION_FILTER,
     /** {@link WebAsyncManagerIntegrationFilter} */
     WEB_ASYNC_MANAGER_FILTER,
+    HEADERS_FILTER,
     LOGOUT_FILTER,
     X509_FILTER,
     PRE_AUTH_FILTER,

+ 38 - 1
config/src/main/resources/org/springframework/security/config/spring-security-3.2.rnc

@@ -281,7 +281,7 @@ http-firewall =
 
 http =
     ## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "secured" attribute to "false".
-    element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler?) }
+    element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers?) }
 http.attlist &=
     ## The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.
     attribute pattern {xsd:token}?
@@ -718,6 +718,43 @@ jdbc-user-service.attlist &=
 jdbc-user-service.attlist &=
     role-prefix?
 
+headers =
+ ## Element for configuration of the AddHeadersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
+ element headers {xss-protection? & frame-options? & content-type-options? & header*}
+
+frame-options =
+	## Enable basic clickjacking support for newer browsers (IE8+), will set the X-Frame-Options header.
+	element frame-options {frame-options.attlist,empty}
+frame-options.attlist &=
+	## Specify the policy to use for the X-Frame-Options-Header.
+	attribute policy {"DENY","SAMEORIGIN","ALLOW-FROM"}?
+frame-options.attlist &=
+	## Specify the origin to use when ALLOW-FROM is chosen.
+	attribute origin {xsd:token}?
+
+xss-protection =
+	## Enable basic XSS browser protection, supported by newer browsers (IE8+), will set the X-XSS-Protection header.
+	element xss-protection {xss-protection.attlist,empty}
+xss-protection.attlist &=
+	## enable or disable the X-XSS-Protection header. Default is 'true' meaning it is enabled.
+	attribute enabled {xsd:boolean}?
+xss-protection.attlist &=
+	## Add mode=block to the header or not, default is on.
+	attribute block {xsd:boolean}?
+
+content-type-options =
+	## Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'.
+	element content-type-options {empty}
+
+header=
+	## Add additional headers to the response.
+	element header {header.attlist}
+header.attlist &=
+	## The name of the header to add.
+	attribute name {xsd:token}
+header.attlist &=
+	## The value for the header.
+	attribute value {xsd:token}
 
 any-user-service = user-service | jdbc-user-service | ldap-user-service
 

+ 101 - 0
config/src/main/resources/org/springframework/security/config/spring-security-3.2.xsd

@@ -1024,6 +1024,7 @@
                   <xs:attributeGroup ref="security:ref"/>
                </xs:complexType>
             </xs:element>
+            <xs:element ref="security:headers"/>
          </xs:choice>
          <xs:attributeGroup ref="security:http.attlist"/>
       </xs:complexType>
@@ -2231,6 +2232,106 @@
          </xs:annotation>
       </xs:attribute>
   </xs:attributeGroup>
+  <xs:element name="headers">
+      <xs:annotation>
+         <xs:documentation>Element for configuration of the AddHeadersFilter. Enables easy setting for the
+                X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element ref="security:xss-protection"/>
+            <xs:element ref="security:frame-options"/>
+            <xs:element ref="security:content-type-options"/>
+            <xs:element ref="security:header"/>
+         </xs:choice>
+      </xs:complexType>
+   </xs:element>
+  <xs:element name="frame-options">
+      <xs:annotation>
+         <xs:documentation>Enable basic clickjacking support for newer browsers (IE8+), will set the X-Frame-Options
+                header.
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:frame-options.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="frame-options.attlist">
+      <xs:attribute name="policy">
+         <xs:annotation>
+            <xs:documentation>Specify the policy to use for the X-Frame-Options-Header.
+                </xs:documentation>
+         </xs:annotation>
+         <xs:simpleType>
+            <xs:restriction base="xs:token">
+               <xs:enumeration value="DENY"/>
+               <xs:enumeration value="SAMEORIGIN"/>
+               <xs:enumeration value="ALLOW-FROM"/>
+            </xs:restriction>
+         </xs:simpleType>
+      </xs:attribute>
+      <xs:attribute name="origin" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Specify the origin to use when ALLOW-FROM is chosen.
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:element name="xss-protection">
+      <xs:annotation>
+         <xs:documentation>Enable basic XSS browser protection, supported by newer browsers (IE8+), will set the
+                X-XSS-Protection header.
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:xss-protection.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="xss-protection.attlist">
+      <xs:attribute name="enabled" type="xs:boolean">
+         <xs:annotation>
+            <xs:documentation>enable or disable the X-XSS-Protection header. Default is 'true' meaning it is enabled.
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="block" type="xs:boolean">
+         <xs:annotation>
+            <xs:documentation>Add mode=block to the header or not, default is on.
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:element name="content-type-options">
+      <xs:annotation>
+         <xs:documentation>Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'.
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType/>
+   </xs:element>
+  <xs:element name="header">
+      <xs:annotation>
+         <xs:documentation>Add additional headers to the response.
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:header.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="header.attlist">
+      <xs:attribute name="name" use="required" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>The name of the header to add.
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="value" use="required" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>The value for the header.
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
   <xs:element name="any-user-service" abstract="true"/>
   <xs:element name="custom-filter">
       <xs:annotation>

+ 1 - 1
config/src/main/resources/org/springframework/security/config/spring-security.xsl

@@ -9,7 +9,7 @@
     <xsl:output method="xml"  indent="yes"/>
 
     <xsl:variable name="elts-to-inline">
-        <xsl:text>,access-denied-handler,anonymous,session-management,concurrency-control,after-invocation-provider,authentication-provider,ldap-authentication-provider,user,port-mapping,openid-login,expression-handler,form-login,http-basic,intercept-url,logout,password-encoder,port-mappings,port-mapper,password-compare,protect,protect-pointcut,pre-post-annotation-handling,pre-invocation-advice,post-invocation-advice,invocation-attribute-factory,remember-me,salt-source,x509,</xsl:text>
+        <xsl:text>,access-denied-handler,anonymous,session-management,concurrency-control,after-invocation-provider,authentication-provider,ldap-authentication-provider,user,port-mapping,openid-login,expression-handler,form-login,http-basic,intercept-url,logout,password-encoder,port-mappings,port-mapper,password-compare,protect,protect-pointcut,pre-post-annotation-handling,pre-invocation-advice,post-invocation-advice,invocation-attribute-factory,remember-me,salt-source,x509,add-headers,</xsl:text>
     </xsl:variable>
 
     <xsl:template match="xs:element">

+ 2 - 2
config/src/test/java/org/springframework/security/config/SecurityNamespaceHandlerTests.java

@@ -48,7 +48,7 @@ public class SecurityNamespaceHandlerTests {
     }
 
     @Test
-    public void pre31SchemaAreNotSupported() throws Exception {
+    public void pre32SchemaAreNotSupported() throws Exception {
         try {
             new InMemoryXmlApplicationContext(
                     "<user-service id='us'>" +
@@ -57,7 +57,7 @@ public class SecurityNamespaceHandlerTests {
             );
             fail("Expected BeanDefinitionParsingException");
         } catch (BeanDefinitionParsingException expected) {
-            assertTrue(expected.getMessage().contains("You cannot use a spring-security-2.0.xsd or"));
+            assertTrue(expected.getMessage().contains("You cannot use a spring-security-2.0.xsd"));
         }
     }
 

+ 128 - 1
docs/manual/src/docbook/appendix-namespace.xml

@@ -15,7 +15,7 @@
         explaining their purpose. The namespace is written in <link
         xlink:href="http://www.relaxng.org/">RELAX NG</link> Compact format and later converted into
         an XSD schema. If you are familiar with this format, you may wish to examine the <link
-        xlink:href="https://fisheye.springsource.org/browse/spring-security/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc"
+        xlink:href="https://fisheye.springsource.org/browse/spring-security/config/src/main/resources/org/springframework/security/config/spring-security-3.2.rnc"
         >schema file</link> directly.</para>
     <section xml:id="nsa-web">
         <title>Web Application Security</title>
@@ -210,6 +210,7 @@
                 <title>Child Elements of &lt;http&gt;</title>
                 <itemizedlist>
                     <listitem><link xlink:href="#nsa-access-denied-handler">access-denied-handler</link></listitem>
+                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
                     <listitem><link xlink:href="#nsa-anonymous">anonymous</link></listitem>
                     <listitem><link xlink:href="#nsa-custom-filter">custom-filter</link></listitem>
                     <listitem><link xlink:href="#nsa-expression-handler">expression-handler</link></listitem>
@@ -224,6 +225,7 @@
                     <listitem><link xlink:href="#nsa-request-cache">request-cache</link></listitem>
                     <listitem><link xlink:href="#nsa-session-management">session-management</link></listitem>
                     <listitem><link xlink:href="#nsa-x509">x509</link></listitem>
+                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
                 </itemizedlist>
             </section>
         </section>
@@ -257,6 +259,131 @@
                 </section>
             </section>
         </section>
+        <section xml:id="nsa-headers">
+            <title><literal>&lt;headers&gt;</literal></title>
+            <para>This element allows for configuring additional (security) headers to be send with the response.
+                It enables easy configuration for several headers and also allows for setting custom headers through
+                the <link xlink:href="#nsa-header">header</link> element.
+                <itemizedlist>
+                    <listitem><literal>X-Frame-Options</literal> - Can be set using the
+                        <link xlink:href="#nsa-frame-options">frame-options</link> element. The
+                        <link xlink:href="http://en.wikipedia.org/wiki/Clickjacking#X-Frame-Options">X-Frame-Options
+                        </link> header can be used to prevent clickjacking attacks.</listitem>
+                    <listitem><literal>X-XSS-Protection</literal> - Can be set using the
+                        <link xlink:href="#nsa-xss-protection">xss-protection</link> element.
+                        The <link xlink:href="http://en.wikipedia.org/wiki/Cross-site_scripting">X-XSS-Protection
+                        </link> header can be used by browser to do basic control.</listitem>
+                    <listitem><literal>X-Content-Type-Options</literal> - Can be set using the
+                        <link xlink:href="#nsa-content-type-options">content-type-options</link> element. The
+                        <link xlink:href="">X-Content-Type-Options</link> header prevents Internet Explorer from
+                        MIME-sniffing a response away from the declared content-type. This also applies to Google
+                        Chrome, when downloading extensions. </listitem>
+                </itemizedlist>
+            </para>
+            <section xml:id="nsa-headers-parents">
+                <title>Parent Elements of <literal>&lt;headers&gt;</literal></title>
+                <itemizedlist>
+                    <listitem><link xlink:href="#nsa-http">http</link></listitem>
+                </itemizedlist>
+            </section>
+            <section xml:id="nsa-headers-children">
+                <title>Child Elements of <literal>&lt;headers&gt;</literal></title>
+                <itemizedlist>
+                    <listitem><link xlink:href="#nsa-content-type-options">content-type-options</link></listitem>
+                    <listitem><link xlink:href="#nsa-frame-options">frame-options</link></listitem>
+                    <listitem><link xlink:href="#nsa-header">header</link></listitem>
+                    <listitem><link xlink:href="#nsa-xss-protection">xss-protection</link></listitem>
+                </itemizedlist>
+            </section>
+        </section>
+        <section xml:id="nsa-frame-options">
+            <title><literal>&lt;frame-options&gt;</literal></title>
+            <para>When enabled adds the <link xlink:href="http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-01">X-Frame-Options header</link> to the response, this allows newer browsers to do some security
+                 checks and prevent clickjacking attacks.</para>
+            <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>
+                    <para>
+                        <itemizedlist>
+                            <listitem><literal>DENY</literal> The page cannot be displayed in a frame, regardless of
+                                the site attempting to do so. </listitem>
+                            <listitem><literal>SAMEORIGIN</literal> The page can only be displayed in a frame on the
+                                same origin as the page itself</listitem>
+                            <listitem><literal>ALLOW-FROM <link xlink:href="#nsa-frame-options-origin">origin</link></literal>
+                                The page can only be displayed in a frame on the specified origin.
+                            </listitem>
+                        </itemizedlist>
+                        In other words, if you specify DENY, not only will attempts to load the page in a frame fail
+                        when loaded from other sites, attempts to do so will fail when loaded from the same site. On the
+                        other hand, if you specify SAMEORIGIN, you can still use the page in a frame as long as the site
+                        including it in a frame it is the same as the one serving the page.
+                    </para>
+                </section>
+                <section xml:id="nsa-frame-options-origin">
+                    <title><literal>frame-options-origin</literal></title>
+                    <para>The origin</para>
+                </section>
+            </section>
+             <section xml:id="nsa-frame-options-parents">
+                <title>Parent Elements of <literal>&lt;frame-options&gt;</literal></title>
+                <itemizedlist>
+                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
+                </itemizedlist>
+            </section>
+        </section>
+        <section xml:id="nsa-xss-protection">
+            <title><literal>&lt;xss-protection&gt;</literal></title>
+            <para>Adds the X-XSS-Protection header to the response. This is in no-way a full protection to XSS attacks!</para>
+            <section xml:id="nsa-xss-protection-attributes">
+                <section xml:id="nsa-xss-protection-enabled">
+                    <title><literal>xss-protection-enabled</literal></title>
+                    <para>Enable or Disable xss-protection.</para>
+                </section>
+                <section xml:id="nsa-xss-protection-block">
+                    <title><literal>xss-protection-block</literal></title>
+                    <para>When enabled adds mode=block to the header. Which indicates to the browser that loading should be blocked.</para>
+                </section>
+            </section>
+            <section xml:id="nsa-xss-protection-parents">
+                <title>Parent Elements of <literal>&lt;xss-protection&gt;</literal></title>
+                <itemizedlist>
+                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
+                </itemizedlist>
+            </section>
+        </section>
+        <section xml:id="nsa-content-type-options">
+            <title><literal>&lt;content-type-options&gt;</literal></title>
+            <para>Add the X-Content-Type-Options header to the response. Indicates the browser (IE8+) to enable detection
+            for MIME-sniffing.</para>
+            <section xml:id="nsa-content-type-options-parents">
+                <title>Parent Elements of <literal>&lt;content-type-options&gt;</literal></title>
+                <itemizedlist>
+                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
+                </itemizedlist>
+            </section>
+        </section>
+        <section xml:id="nsa-header">
+            <title><literal>&lt;header&gt;</literal></title>
+            <para>Add additional headers to the response, both the name and value need to be specified.</para>
+            <section xml:id="nsa-header-attributes">
+                <title><literal>&lt;header-attributes&gt;</literal> Attributes</title>
+                <section xml:id="nsa-header-name">
+                    <title><literal>header-name</literal></title>
+                    <para>The <literal>name</literal> of the header.</para>
+                </section>
+                <section xml:id="nsa-header-value">
+                    <title><literal>header-value</literal></title>
+                    <para>The <literal>value</literal> of the header to add.</para>
+                </section>
+            </section>
+            <section xml:id="nsa-header-parents">
+                <title>Parent Elements of <literal>&lt;header&gt;</literal></title>
+                <itemizedlist>
+                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
+                </itemizedlist>
+            </section>
+        </section>
         <section xml:id="nsa-anonymous">
             <title><literal>&lt;anonymous&gt;</literal></title>
             <para>Adds an <classname>AnonymousAuthenticationFilter</classname> to the stack and an

+ 22 - 0
docs/manual/src/docbook/namespace-config.xml

@@ -609,6 +609,23 @@ List&lt;OpenIDAttribute> attributes = token.getAttributes();</programlisting>The
                     MyOpenID providers.</para>
             </section>
         </section>
+        <section xml:id="ns-headers">
+            <title>Response Headers</title>
+            <para>A lot of different attacks to hijack content, sessions or connections are available and lately
+                browsers (optionally) can help to prevent those attacks. To enable these features we need to send some
+                additional headers to the client. Spring Security allows for easy configuration for several headers.
+                <progamlisting language="xml">
+                    <![CDATA[
+                            <headers>
+                                <xss-protection/>
+                                <frame-options/>
+                                <content-type-options/>
+                                <header name="foo" value="bar"/>
+                            </headers>
+                    ]]>
+                </progamlisting>
+            </para>
+        </section>
         <section xml:id="ns-custom-filters">
             <title>Adding in Your Own Filters</title>
             <para>If you've used Spring Security before, you'll know that the framework maintains a
@@ -663,6 +680,11 @@ List&lt;OpenIDAttribute> attributes = token.getAttributes();</programlisting>The
                             <entry><literal>ConcurrentSessionFilter</literal> </entry>
                             <entry><literal>session-management/concurrency-control</literal></entry>
                         </row>
+                        <row>
+                            <entry>HEADERS_FILTER</entry>
+                            <entry><literal>HeadersFilter</literal> </entry>
+                            <entry><literal>http/headers</literal></entry>
+                        </row>
                         <row>
                             <entry> LOGOUT_FILTER </entry>
                             <entry><literal>LogoutFilter</literal></entry>

+ 6 - 0
samples/contacts/src/main/resources/applicationContext-security.xml

@@ -30,6 +30,12 @@
         <http-basic/>
         <logout logout-success-url="/index.jsp"/>
         <remember-me />
+        <headers>
+            <xss-protection />
+            <frame-options policy="DENY" />
+            <content-type-options/>
+            <header name="foo" value="bar"/>
+        </headers>
         <custom-filter ref="switchUserProcessingFilter" position="SWITCH_USER_FILTER"/>
     </http>
 

+ 62 - 0
web/src/main/java/org/springframework/security/web/headers/HeadersFilter.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2012 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.headers;
+
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Filter implementation to add headers to the current request. Can be useful to add certain headers which enable
+ * browser protection. Like X-Frame-Options, X-XSS-Protection and X-Content-Type-Options.
+ *
+ * @author Marten Deinum
+ * @since 3.2
+ *
+ */
+public class HeadersFilter extends OncePerRequestFilter {
+
+    /** Map of headers to add to a response */
+    private final Map<String, String> headers = new HashMap<String, String>();
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        for (Map.Entry<String, String> header : headers.entrySet()) {
+            String name = header.getKey();
+            String value = header.getValue();
+            if (logger.isTraceEnabled()) {
+                logger.trace("Adding header '" + name + "' with value '"+value +"'");
+            }
+            response.setHeader(header.getKey(), header.getValue());
+        }
+        filterChain.doFilter(request, response);
+    }
+
+    public void setHeaders(Map<String, String> headers) {
+        this.headers.clear();
+        this.headers.putAll(headers);
+    }
+
+    public void addHeader(String name, String value) {
+        headers.put(name, value);
+    }
+}

+ 73 - 0
web/src/test/java/org/springframework/security/web/headers/HeadersFilterTest.java

@@ -0,0 +1,73 @@
+/*
+ * 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.headers;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+
+/**
+ * Tests for the {@code HeadersFilter}
+ *
+ * @author Marten Deinum
+ * @since 3.2
+ */
+public class HeadersFilterTest {
+
+    @Test
+    public void noHeadersConfigured() throws Exception {
+        HeadersFilter filter = new HeadersFilter();
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain filterChain = new MockFilterChain();
+
+        filter.doFilter(request, response, filterChain);
+
+        assertTrue(response.getHeaderNames().isEmpty());
+    }
+
+    @Test
+    public void additionalHeadersShouldBeAddedToTheResponse() throws Exception {
+        HeadersFilter filter = new HeadersFilter();
+        Map<String, String> headers = new HashMap<String, String>();
+        headers.put("X-Header1", "foo");
+        headers.put("X-Header2", "bar");
+        filter.setHeaders(headers);
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain filterChain = new MockFilterChain();
+
+        filter.doFilter(request, response, filterChain);
+
+        Collection<String> headerNames = response.getHeaderNames();
+        assertThat(headerNames.size(), is(2));
+        assertThat(headerNames, hasItems("X-Header1", "X-Header2"));
+        assertThat(response.getHeader("X-Header1"), is("foo"));
+        assertThat(response.getHeader("X-Header2"), is("bar"));
+
+    }
+}