From e6008b6067e97f78823541511d3458ea280b16fb Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:25:29 -0600 Subject: [PATCH] Add RedirectToHttps to XML Closes gh-16775 --- .../config/http/HttpConfigurationBuilder.java | 24 ++++++++++- .../HttpSecurityBeanDefinitionParser.java | 4 +- .../security/config/http/SecurityFilters.java | 5 ++- .../security/config/spring-security-6.5.rnc | 2 + .../security/config/spring-security-6.5.xsd | 1 + .../config/http/MiscHttpConfigTests.java | 18 ++++++++- ...gTests-RedirectToHttpsRequiresHttpsAny.xml | 40 +++++++++++++++++++ .../servlet/appendix/namespace/http.adoc | 4 ++ 8 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-RedirectToHttpsRequiresHttpsAny.xml diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index db915da867..d15c8ba65a 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -82,6 +82,7 @@ import org.springframework.security.web.session.ForceEagerSessionCreationFilter; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy; +import org.springframework.security.web.transport.HttpsRedirectFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -176,6 +177,8 @@ class HttpConfigurationBuilder { private BeanDefinition cpf; + private BeanDefinition httpsRedirectFilter; + private BeanDefinition securityContextPersistenceFilter; private BeanDefinition forceEagerSessionCreationFilter; @@ -252,6 +255,7 @@ class HttpConfigurationBuilder { createServletApiFilter(authenticationManager); createJaasApiFilter(); createChannelProcessingFilter(); + createHttpsRedirectFilter(); createFilterSecurity(authenticationManager); createAddHeadersFilter(); createCorsFilter(); @@ -656,6 +660,19 @@ class HttpConfigurationBuilder { } } + private void createHttpsRedirectFilter() { + String ref = this.httpElt + .getAttribute(HttpSecurityBeanDefinitionParser.ATT_REDIRECT_TO_HTTPS_REQUEST_MATCHER_REF); + if (!StringUtils.hasText(ref)) { + return; + } + RootBeanDefinition channelFilter = new RootBeanDefinition(HttpsRedirectFilter.class); + channelFilter.getPropertyValues().addPropertyValue("requestMatcher", new RuntimeBeanReference(ref)); + channelFilter.getPropertyValues().addPropertyValue("portMapper", this.portMapper); + this.httpsRedirectFilter = channelFilter; + } + + @Deprecated private void createChannelProcessingFilter() { ManagedMap channelRequestMap = parseInterceptUrlsForChannelSecurity(); if (channelRequestMap.isEmpty()) { @@ -691,7 +708,9 @@ class HttpConfigurationBuilder { * Parses the intercept-url elements to obtain the map used by channel security. This * will be empty unless the requires-channel attribute has been used on a URL * path. + * @deprecated please use {@link #createHttpsRedirectFilter} instead */ + @Deprecated private ManagedMap parseInterceptUrlsForChannelSecurity() { ManagedMap channelRequestMap = new ManagedMap<>(); for (Element urlElt : this.interceptUrls) { @@ -897,6 +916,9 @@ class HttpConfigurationBuilder { if (this.disableUrlRewriteFilter != null) { filters.add(new OrderDecorator(this.disableUrlRewriteFilter, SecurityFilters.DISABLE_ENCODE_URL_FILTER)); } + if (this.httpsRedirectFilter != null) { + filters.add(new OrderDecorator(this.httpsRedirectFilter, SecurityFilters.HTTPS_REDIRECT_FILTER)); + } if (this.cpf != null) { filters.add(new OrderDecorator(this.cpf, SecurityFilters.CHANNEL_FILTER)); } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java index 333601385e..ca4a2c8b0d 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -81,6 +81,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { static final String ATT_REQUEST_MATCHER_REF = "request-matcher-ref"; + static final String ATT_REDIRECT_TO_HTTPS_REQUEST_MATCHER_REF = "redirect-to-https-request-matcher-ref"; + static final String ATT_PATH_PATTERN = "pattern"; static final String ATT_HTTP_METHOD = "method"; diff --git a/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java b/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java index 0a399228b4..6ff1171578 100644 --- a/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java +++ b/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -33,8 +33,11 @@ enum SecurityFilters { FORCE_EAGER_SESSION_FILTER, + @Deprecated CHANNEL_FILTER, + HTTPS_REDIRECT_FILTER, + SECURITY_CONTEXT_FILTER, CONCURRENT_SESSION_FILTER, diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc index 9dcb730571..66ef1055e5 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc @@ -345,6 +345,8 @@ http.attlist &= http.attlist &= ## Allows a RequestMatcher instance to be used, as an alternative to pattern-matching. attribute request-matcher-ref { xsd:token }? +http.attlist &= + attribute redirect-to-https-request-matcher-ref { xsd:token }? http.attlist &= ## A legacy attribute which automatically registers a login form, BASIC authentication and a logout URL and logout services. If unspecified, defaults to "false". We'd recommend you avoid using this and instead explicitly configure the services you require. attribute auto-config {xsd:boolean}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd index 03a00f3665..2a5111dedf 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd @@ -1242,6 +1242,7 @@ + A legacy attribute which automatically registers a login form, BASIC authentication and a diff --git a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java index 180bd2ec53..5221350cde 100644 --- a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -109,6 +109,7 @@ import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.session.DisableEncodeUrlFilter; +import org.springframework.security.web.transport.HttpsRedirectFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -349,6 +350,21 @@ public class MiscHttpConfigTests { assertThat(getFilter(ChannelProcessingFilter.class)).isNotNull(); } + @Test + public void configureWhenRedirectToHttpsThenFilterAdded() { + this.spring.configLocations(xml("RedirectToHttpsRequiresHttpsAny")).autowire(); + assertThat(getFilter(HttpsRedirectFilter.class)).isNotNull(); + } + + @Test + public void getWhenRedirectToHttpsAnyThenRedirects() throws Exception { + this.spring.configLocations(xml("RedirectToHttpsRequiresHttpsAny")).autowire(); + // @formatter:off + this.mvc.perform(get("http://localhost")) + .andExpect(redirectedUrl("https://localhost")); + // @formatter:on + } + @Test public void getWhenPortsMappedThenRedirectedAccordingly() throws Exception { this.spring.configLocations(xml("PortsMappedInterceptUrlMethodRequiresAny")).autowire(); diff --git a/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-RedirectToHttpsRequiresHttpsAny.xml b/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-RedirectToHttpsRequiresHttpsAny.xml new file mode 100644 index 0000000000..2a098bf466 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-RedirectToHttpsRequiresHttpsAny.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index d49c2f12db..d2b063deaa 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -118,6 +118,10 @@ If no pattern is defined, all requests will be matched, so the most specific pat Sets the realm name used for basic authentication (if enabled). Corresponds to the `realmName` property on `BasicAuthenticationEntryPoint`. +[[nsa-redirect-to-https-request-matcher-ref]] +* **redirect-to-https-request-matcher-ref** +A reference to a bean that implements `RequestMatcher` that will determine which requests must redirect to HTTPS. +This is helpful when, for example, wanting to run HTTP locally and HTTPS in production using a request header. [[nsa-http-request-matcher]] * **request-matcher**