diff --git a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java index 36844c6f2b..03676e718b 100644 --- a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java +++ b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java @@ -166,7 +166,6 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { loadParsers(); } - @SuppressWarnings("deprecation") private void loadParsers() { // Parsers parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser()); diff --git a/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java index 7e04935a65..7300a42f7b 100644 --- a/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java @@ -218,11 +218,11 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { hash = "sha256"; } - Node pinValueNode = pinElement.getFirstChild(); - if (pinValueNode == null) { - context.getReaderContext().warning("Missing value for pin entry.", hpkpElement); - continue; - } + Node pinValueNode = pinElement.getFirstChild(); + if (pinValueNode == null) { + context.getReaderContext().warning("Missing value for pin entry.", hpkpElement); + continue; + } String fingerprint = pinElement.getFirstChild().getTextContent(); diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.groovy index c15123ad81..1c924f7874 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.groovy @@ -35,12 +35,12 @@ class HeadersConfigurerTests extends BaseSpringSpec { springSecurityFilterChain.doFilter(request,response,chain) then: responseHeaders == ['X-Content-Type-Options':'nosniff', - 'X-Frame-Options':'DENY', - 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', - 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - 'Expires' : '0', - 'Pragma':'no-cache', - 'X-XSS-Protection' : '1; mode=block'] + 'X-Frame-Options':'DENY', + 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', + 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', + 'Expires' : '0', + 'Pragma':'no-cache', + 'X-XSS-Protection' : '1; mode=block'] } @EnableWebSecurity @@ -123,8 +123,8 @@ class HeadersConfigurerTests extends BaseSpringSpec { springSecurityFilterChain.doFilter(request,response,chain) then: responseHeaders == ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - 'Expires' : '0', - 'Pragma':'no-cache'] + 'Expires' : '0', + 'Pragma':'no-cache'] } @EnableWebSecurity @@ -168,12 +168,12 @@ class HeadersConfigurerTests extends BaseSpringSpec { springSecurityFilterChain.doFilter(request,response,chain) then: responseHeaders == ['X-Content-Type-Options':'nosniff', - 'X-Frame-Options':'SAMEORIGIN', - 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', - 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - 'Expires' : '0', - 'Pragma':'no-cache', - 'X-XSS-Protection' : '1; mode=block'] + 'X-Frame-Options':'SAMEORIGIN', + 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', + 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', + 'Expires' : '0', + 'Pragma':'no-cache', + 'X-XSS-Protection' : '1; mode=block'] } @EnableWebSecurity diff --git a/config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy index b5aff2905d..c692ad83b3 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy @@ -30,12 +30,12 @@ import org.springframework.security.web.util.matcher.AnyRequestMatcher */ class HttpHeadersConfigTests extends AbstractHttpConfigTests { def defaultHeaders = ['X-Content-Type-Options':'nosniff', - 'X-Frame-Options':'DENY', - 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', - 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - 'Expires' : '0', - 'Pragma':'no-cache', - 'X-XSS-Protection' : '1; mode=block'] + 'X-Frame-Options':'DENY', + 'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', + 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', + 'Expires' : '0', + 'Pragma':'no-cache', + 'X-XSS-Protection' : '1; mode=block'] def 'headers disabled'() { setup: httpAutoConfig { @@ -294,7 +294,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { MockHttpServletResponse response = new MockHttpServletResponse() hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) then: - assertHeaders(response, ['abc':'def']) + assertHeaders(response, ['abc':'def']) } def 'http headers header no name produces error'() { @@ -404,8 +404,8 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) then: assertHeaders(response, ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - 'Expires' : '0', - 'Pragma':'no-cache']) + 'Expires' : '0', + 'Pragma':'no-cache']) } def 'http headers hsts'() { @@ -458,35 +458,35 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { assertHeaders(response, ['Strict-Transport-Security': 'max-age=1']) } - def 'http headers hpkp no pins'() { - setup: - httpAutoConfig { - 'headers'('defaults-disabled':true) { - 'hpkp'() - } - } - when: - createAppContext() - then: - XmlBeanDefinitionStoreException expected = thrown() - expected.message.contains 'The content of element \'hpkp\' is not complete' - } + def 'http headers hpkp no pins'() { + setup: + httpAutoConfig { + 'headers'('defaults-disabled':true) { + 'hpkp'() + } + } + when: + createAppContext() + then: + XmlBeanDefinitionStoreException expected = thrown() + expected.message.contains 'The content of element \'hpkp\' is not complete' + } - def 'http headers hpkp no pin'() { - setup: - httpAutoConfig { - 'headers'('defaults-disabled':true) { - 'hpkp'() { - 'pins'() - } - } - } - when: - createAppContext() - then: - XmlBeanDefinitionStoreException expected = thrown() - expected.message.contains 'The content of element \'pins\' is not complete' - } + def 'http headers hpkp no pin'() { + setup: + httpAutoConfig { + 'headers'('defaults-disabled':true) { + 'hpkp'() { + 'pins'() + } + } + } + when: + createAppContext() + then: + XmlBeanDefinitionStoreException expected = thrown() + expected.message.contains 'The content of element \'pins\' is not complete' + } def 'http headers hpkp'() { setup: @@ -508,125 +508,125 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']) } - def 'http headers hpkp with default algorithm'() { - setup: - httpAutoConfig { - 'headers'('defaults-disabled':true) { - 'hpkp'() { - 'pins'() { - 'pin'('d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=') - } - } - } - } - createAppContext() - def springSecurityFilterChain = appContext.getBean(FilterChainProxy) - MockHttpServletResponse response = new MockHttpServletResponse() - when: - springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) - then: - assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']) - } + def 'http headers hpkp with default algorithm'() { + setup: + httpAutoConfig { + 'headers'('defaults-disabled':true) { + 'hpkp'() { + 'pins'() { + 'pin'('d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=') + } + } + } + } + createAppContext() + def springSecurityFilterChain = appContext.getBean(FilterChainProxy) + MockHttpServletResponse response = new MockHttpServletResponse() + when: + springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) + then: + assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']) + } def 'http headers hpkp only invokes on HttpServletRequest.isSecure = true'() { setup: - httpAutoConfig { - 'headers'('defaults-disabled':true) { - 'hpkp'() { - 'pins'() { - 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') - } - } - } - } - createAppContext() - def springSecurityFilterChain = appContext.getBean(FilterChainProxy) - MockHttpServletResponse response = new MockHttpServletResponse() + httpAutoConfig { + 'headers'('defaults-disabled':true) { + 'hpkp'() { + 'pins'() { + 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') + } + } + } + } + createAppContext() + def springSecurityFilterChain = appContext.getBean(FilterChainProxy) + MockHttpServletResponse response = new MockHttpServletResponse() when: - springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) + springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain()) then: - response.headerNames.empty + response.headerNames.empty } - def 'http headers hpkp with custom max age'() { - setup: - httpAutoConfig { - 'headers'('defaults-disabled':true) { - 'hpkp'('max-age-seconds':'604800') { - 'pins'() { - 'pin'('algorithm':'sha256', 'd6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=') - } - } - } - } - createAppContext() - def springSecurityFilterChain = appContext.getBean(FilterChainProxy) - MockHttpServletResponse response = new MockHttpServletResponse() - when: - springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) - then: - assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=604800 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']) - } + def 'http headers hpkp with custom max age'() { + setup: + httpAutoConfig { + 'headers'('defaults-disabled':true) { + 'hpkp'('max-age-seconds':'604800') { + 'pins'() { + 'pin'('algorithm':'sha256', 'd6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=') + } + } + } + } + createAppContext() + def springSecurityFilterChain = appContext.getBean(FilterChainProxy) + MockHttpServletResponse response = new MockHttpServletResponse() + when: + springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) + then: + assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=604800 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']) + } - def 'http headers hpkp@reportOnly=false'() { - setup: - httpAutoConfig { - 'headers'('defaults-disabled':true) { - 'hpkp'('report-only':'false') { - 'pins'() { - 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') - } - } - } - } - createAppContext() - def springSecurityFilterChain = appContext.getBean(FilterChainProxy) - MockHttpServletResponse response = new MockHttpServletResponse() - when: - springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) - then: - assertHeaders(response, ['Public-Key-Pins': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="']) - } + def 'http headers hpkp@reportOnly=false'() { + setup: + httpAutoConfig { + 'headers'('defaults-disabled':true) { + 'hpkp'('report-only':'false') { + 'pins'() { + 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') + } + } + } + } + createAppContext() + def springSecurityFilterChain = appContext.getBean(FilterChainProxy) + MockHttpServletResponse response = new MockHttpServletResponse() + when: + springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) + then: + assertHeaders(response, ['Public-Key-Pins': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="']) + } - def 'http headers hpkp@includeSubDomains=true'() { - setup: - httpAutoConfig { - 'headers'('defaults-disabled':true) { - 'hpkp'('include-subdomains':'true') { - 'pins'() { - 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') - } - } - } - } - createAppContext() - def springSecurityFilterChain = appContext.getBean(FilterChainProxy) - MockHttpServletResponse response = new MockHttpServletResponse() - when: - springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) - then: - assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; includeSubDomains']) - } + def 'http headers hpkp@includeSubDomains=true'() { + setup: + httpAutoConfig { + 'headers'('defaults-disabled':true) { + 'hpkp'('include-subdomains':'true') { + 'pins'() { + 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') + } + } + } + } + createAppContext() + def springSecurityFilterChain = appContext.getBean(FilterChainProxy) + MockHttpServletResponse response = new MockHttpServletResponse() + when: + springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) + then: + assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; includeSubDomains']) + } - def 'http headers hpkp with report-uri'() { - setup: - httpAutoConfig { - 'headers'('defaults-disabled':true) { - 'hpkp'('report-uri':'http://example.net/pkp-report') { - 'pins'() { - 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') - } - } - } - } - createAppContext() - def springSecurityFilterChain = appContext.getBean(FilterChainProxy) - MockHttpServletResponse response = new MockHttpServletResponse() - when: - springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) - then: - assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; report-uri="http://example.net/pkp-report"']) - } + def 'http headers hpkp with report-uri'() { + setup: + httpAutoConfig { + 'headers'('defaults-disabled':true) { + 'hpkp'('report-uri':'http://example.net/pkp-report') { + 'pins'() { + 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') + } + } + } + } + createAppContext() + def springSecurityFilterChain = appContext.getBean(FilterChainProxy) + MockHttpServletResponse response = new MockHttpServletResponse() + when: + springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain()) + then: + assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; report-uri="http://example.net/pkp-report"']) + } // --- disable single default header --- @@ -688,23 +688,23 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests { def 'http headers hpkp@disabled=true'() { setup: - httpAutoConfig { - 'headers'() { - 'hpkp'(disabled:true) { - 'pins'() { - 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') - } - } - } - } - createAppContext() - def springSecurityFilterChain = appContext.getBean(FilterChainProxy) - MockHttpServletResponse response = new MockHttpServletResponse() - def expectedHeaders = [:] << defaultHeaders + httpAutoConfig { + 'headers'() { + 'hpkp'(disabled:true) { + 'pins'() { + 'pin'('algorithm':'sha256', 'E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=') + } + } + } + } + createAppContext() + def springSecurityFilterChain = appContext.getBean(FilterChainProxy) + MockHttpServletResponse response = new MockHttpServletResponse() + def expectedHeaders = [:] << defaultHeaders when: - springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) + springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain()) then: - assertHeaders(response, expectedHeaders) + assertHeaders(response, expectedHeaders) } def 'http headers frame-options@disabled=true'() { diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 16ae218aec..06f888c439 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -3844,8 +3844,8 @@ Opposed to the other headers, Spring Security does not add HPKP by default. You include-subdomains="true" report-uri="http://example.net/pkp-report"> - d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM= - E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g= + d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM= + E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g= @@ -3860,16 +3860,16 @@ Similarly, you can enable HPKP headers with Java Configuration: public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http - // ... - .headers() - .httpPublicKeyPinning() - .includeSubdomains(true) - .reportUri("http://example.net/pkp-report") - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; - } + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // ... + .headers() + .httpPublicKeyPinning() + .includeSubdomains(true) + .reportUri("http://example.net/pkp-report") + .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; + } } ---- diff --git a/web/src/main/java/org/springframework/security/web/header/writers/HpkpHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/HpkpHeaderWriter.java index 141cf890fd..3b2be5f52d 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/HpkpHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/HpkpHeaderWriter.java @@ -162,7 +162,7 @@ public final class HpkpHeaderWriter implements HeaderWriter { /* * (non-Javadoc) - * + * * @see org.springframework.security.web.headers.HeaderWriter#writeHeaders(javax * .servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @@ -199,9 +199,11 @@ public final class HpkpHeaderWriter implements HeaderWriter { * * Use * - * Map pins = new HashMap(); + * + * Map<String, String> pins = new HashMap<String, String>(); * pins.put("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"); * pins.put("E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=", "sha256"); + * *

* * @param pins the map of base64-encoded SPKI fingerprint & cryptographic hash algorithm pairs.