mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-30 16:52:13 +00:00
SEC-2098, SEC-2099: Polishing
This commit is contained in:
parent
0adf5aea91
commit
a63baa8391
@ -54,5 +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";
|
||||
public static final String HEADERS = "headers";
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeadersFilter.class);
|
||||
final Map<String, String> headers = new HashMap<String, String>();
|
||||
|
||||
parseXssElement(element, headers);
|
||||
parseXssElement(element, parserContext, headers);
|
||||
parseFrameOptionsElement(element, parserContext, headers);
|
||||
parseContentTypeOptionsElement(element, headers);
|
||||
|
||||
@ -91,7 +91,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
||||
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);
|
||||
parserContext.getReaderContext().error("<frame-options policy=\"ALLOW-FROM\"/> requires a non-empty string value for the origin attribute to be specified.", frameElt);
|
||||
}
|
||||
header += " " + origin;
|
||||
}
|
||||
@ -99,15 +99,17 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void parseXssElement(Element element, Map<String, String> headers) {
|
||||
private void parseXssElement(Element element, ParserContext parserContext, 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"));
|
||||
boolean block = Boolean.valueOf(getAttribute(xssElt, ATT_BLOCK, enabled ? "true" : "false"));
|
||||
|
||||
String value = enabled ? "1" : "0";
|
||||
if (enabled && block) {
|
||||
value += "; mode=block";
|
||||
} else if (!enabled && block) {
|
||||
parserContext.getReaderContext().error("<xss-protection enabled=\"false\"/> does not allow for the block=\"true\".", xssElt);
|
||||
}
|
||||
headers.put(XSS_PROTECTION_HEADER, value);
|
||||
}
|
||||
|
@ -557,7 +557,7 @@ class HttpConfigurationBuilder {
|
||||
}
|
||||
|
||||
private void createAddHeadersFilter() {
|
||||
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.ADD_HEADERS);
|
||||
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.HEADERS);
|
||||
if (elmt != null) {
|
||||
this.addHeadersFilter = new HeadersBeanDefinitionParser().parse(elmt, pc);
|
||||
}
|
||||
|
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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 javax.servlet.Filter
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
|
||||
import org.springframework.mock.web.MockFilterChain
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.security.config.BeanIds
|
||||
import org.springframework.security.openid.OpenIDAuthenticationFilter
|
||||
import org.springframework.security.openid.OpenIDAuthenticationToken
|
||||
import org.springframework.security.openid.OpenIDConsumer
|
||||
import org.springframework.security.openid.OpenIDConsumerException
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter
|
||||
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
|
||||
import org.springframework.security.web.headers.HeadersFilter
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
||||
|
||||
def 'no http headers filter'() {
|
||||
httpAutoConfig {
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
expect:
|
||||
!hf
|
||||
}
|
||||
|
||||
def 'http headers with empty headers'() {
|
||||
httpAutoConfig {
|
||||
'headers'()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
expect:
|
||||
hf
|
||||
hf.headers.isEmpty()
|
||||
}
|
||||
|
||||
def 'http headers content-type-options'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'content-type-options'()
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
expect:
|
||||
hf
|
||||
hf.headers == ['X-Content-Type-Options':'nosniff']
|
||||
}
|
||||
|
||||
def 'http headers frame-options defaults to DENY'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'()
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
expect:
|
||||
hf
|
||||
hf.headers == ['X-Frame-Options':'DENY']
|
||||
}
|
||||
|
||||
def 'http headers frame-options DENY'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'(policy : 'DENY')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
expect:
|
||||
hf
|
||||
hf.headers == ['X-Frame-Options':'DENY']
|
||||
}
|
||||
|
||||
def 'http headers frame-options SAMEORIGIN'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'(policy : 'SAMEORIGIN')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
expect:
|
||||
hf
|
||||
hf.headers == ['X-Frame-Options':'SAMEORIGIN']
|
||||
}
|
||||
|
||||
def 'http headers frame-options ALLOW-FROM no origin reports error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'(policy : 'ALLOW-FROM')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
BeanDefinitionParsingException e = thrown()
|
||||
e.message.contains '<frame-options policy="ALLOW-FROM"/> requires a non-empty string value for the origin attribute to be specified.'
|
||||
}
|
||||
|
||||
def 'http headers frame-options ALLOW-FROM spaces only origin reports error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'(policy : 'ALLOW-FROM', origin : ' ')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
BeanDefinitionParsingException e = thrown()
|
||||
e.message.contains '<frame-options policy="ALLOW-FROM"/> requires a non-empty string value for the origin attribute to be specified.'
|
||||
}
|
||||
|
||||
def 'http headers frame-options ALLOW-FROM'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'(policy : 'ALLOW-FROM', origin : 'https://example.com')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
hf
|
||||
hf.headers == ['X-Frame-Options':'ALLOW-FROM https://example.com']
|
||||
}
|
||||
|
||||
def 'http headers header a=b'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'header'(name : 'a', value: 'b')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
hf
|
||||
hf.headers == ['a':'b']
|
||||
}
|
||||
|
||||
def 'http headers header a=b and c=d'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'header'(name : 'a', value: 'b')
|
||||
'header'(name : 'c', value: 'd')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
hf
|
||||
hf.headers.sort() == ['a':'b', 'c':'d'].sort()
|
||||
}
|
||||
|
||||
def 'http headers header no name produces error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'header'(value: 'b')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
thrown(XmlBeanDefinitionStoreException)
|
||||
}
|
||||
|
||||
def 'http headers header no value produces error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'header'(name: 'a')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
thrown(XmlBeanDefinitionStoreException)
|
||||
}
|
||||
|
||||
def 'http headers xss-protection defaults'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'xss-protection'()
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
hf
|
||||
hf.headers == ['X-XSS-Protection':'1; mode=block']
|
||||
}
|
||||
|
||||
def 'http headers xss-protection enabled=true'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'xss-protection'(enabled:'true')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
hf
|
||||
hf.headers == ['X-XSS-Protection':'1; mode=block']
|
||||
}
|
||||
|
||||
def 'http headers xss-protection enabled=false'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'xss-protection'(enabled:'false')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
hf
|
||||
hf.headers == ['X-XSS-Protection':'0']
|
||||
}
|
||||
|
||||
def 'http headers xss-protection enabled=false and block=true produces error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'xss-protection'(enabled:'false', block:'true')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeadersFilter)
|
||||
|
||||
then:
|
||||
BeanDefinitionParsingException e = thrown()
|
||||
e.message.contains '<xss-protection enabled="false"/> does not allow for the block="true".'
|
||||
}
|
||||
}
|
@ -210,11 +210,11 @@
|
||||
<title>Child Elements of <http></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>
|
||||
<listitem><link xlink:href="#nsa-form-login">form-login</link></listitem>
|
||||
<listitem><link xlink:href="#nsa-headers">headers</link></listitem>
|
||||
<listitem><link xlink:href="#nsa-http-basic">http-basic</link></listitem>
|
||||
<listitem><link xlink:href="#nsa-intercept-url">intercept-url</link></listitem>
|
||||
<listitem><link xlink:href="#nsa-jee">jee</link></listitem>
|
||||
@ -225,7 +225,6 @@
|
||||
<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>
|
||||
@ -307,7 +306,7 @@
|
||||
<para>
|
||||
<itemizedlist>
|
||||
<listitem><literal>DENY</literal> The page cannot be displayed in a frame, regardless of
|
||||
the site attempting to do so. </listitem>
|
||||
the site attempting to do so. This is the default when frame-options-policy is specified.</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>
|
||||
@ -334,15 +333,20 @@
|
||||
</section>
|
||||
<section xml:id="nsa-xss-protection">
|
||||
<title><literal><xss-protection></literal></title>
|
||||
<para>Adds the X-XSS-Protection header to the response. This is in no-way a full protection to XSS attacks!</para>
|
||||
<para>Adds the <a href="http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx">X-XSS-Protection header</a>
|
||||
to the response to assist in protecting against <a href="http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent">reflected / “Type-1” Cross-Site Scripting (XSS)</a>
|
||||
attacks. 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>
|
||||
<para>Enable or Disable <a href="http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent">reflected / “Type-1” Cross-Site Scripting (XSS)</a> 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>
|
||||
<para>When true and xss-protection-enabled is true, adds mode=block to the header. This indicates to the browser that the
|
||||
page should not be loaded at all. When false and xss-protection-enabled is true, the page will still be rendered when
|
||||
an reflected attack is detected but the response will be modified to protect against the attack. Note that there are
|
||||
sometimes ways of bypassing this mode which can often times make blocking the page more desirable.</para>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="nsa-xss-protection-parents">
|
||||
@ -354,8 +358,9 @@
|
||||
</section>
|
||||
<section xml:id="nsa-content-type-options">
|
||||
<title><literal><content-type-options></literal></title>
|
||||
<para>Add the X-Content-Type-Options header to the response. Indicates the browser (IE8+) to enable detection
|
||||
for MIME-sniffing.</para>
|
||||
<para>Add the X-Content-Type-Options header with the value of nosniff to the response. This
|
||||
<a href="http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">disables MIME-sniffing</a>
|
||||
for IE8+ and Chrome extensions.</para>
|
||||
<section xml:id="nsa-content-type-options-parents">
|
||||
<title>Parent Elements of <literal><content-type-options></literal></title>
|
||||
<itemizedlist>
|
||||
|
Loading…
x
Reference in New Issue
Block a user