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.
This commit is contained in:
Marten Deinum 2012-12-21 14:56:18 +01:00 committed by Rob Winch
parent f5a30e55a3
commit 0adf5aea91
13 changed files with 573 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"));
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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"));
}
}