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:
@@ -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,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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user