diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java index 6d683c4899a..f4646fe6f53 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2023 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. diff --git a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java index 9a4e3b041e1..c7f0590bc1b 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2022 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. diff --git a/docs/modules/ROOT/pages/migration-7/web.adoc b/docs/modules/ROOT/pages/migration-7/web.adoc new file mode 100644 index 00000000000..024d5604494 --- /dev/null +++ b/docs/modules/ROOT/pages/migration-7/web.adoc @@ -0,0 +1,104 @@ += Web Migrations + +== Favor Relative URIs + +When redirecting to a login endpoint, Spring Security has favored absolute URIs in the past. +For example, if you set your login page like so: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +http + // ... + .formLogin((form) -> form.loginPage("/my-login")) + // ... +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +http { + formLogin { + loginPage = "/my-login" + } +} +---- + +Xml:: ++ +[source,kotlin,role="secondary"] +---- + + + +---- +====== + +then when redirecting to `/my-login` Spring Security would use a `Location:` like the following: + +[source] +---- +302 Found +// ... +Location: https://myapp.example.org/my-login +---- + +However, this is no longer necessary given that the RFC is was based on is now obsolete. + +In Spring Security 7, this is changed to use a relative URI like so: + +[source] +---- +302 Found +// ... +Location: /my-login +---- + +Most applications will not notice a difference. +However, in the event that this change causes problems, you can switch back to the Spring Security 6 behavior by setting the `favorRelativeUrls` value: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint("/my-login"); +entryPoint.setFavorRelativeUris(false); +http + // ... + .exceptionHandling((exceptions) -> exceptions.authenticaitonEntryPoint(entryPoint)) + // ... +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +LoginUrlAuthenticationEntryPoint entryPoint = LoginUrlAuthenticationEntryPoint("/my-login") +entryPoint.setFavorRelativeUris(false) + +http { + exceptionHandling { + authenticationEntryPoint = entryPoint + } +} +---- + +Xml:: ++ +[source,kotlin,role="secondary"] +---- + + + + + + + +---- +====== diff --git a/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java index 3bf2c6f0dbf..5e62d2ebeb6 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java @@ -61,6 +61,7 @@ * @author colin sampaleanu * @author Omri Spector * @author Luke Taylor + * @author Michal Okosy * @since 3.0 */ public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean { @@ -77,6 +78,8 @@ public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoin private boolean useForward = false; + private boolean favorRelativeUris = false; + private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); /** @@ -146,27 +149,38 @@ protected String buildRedirectUrlToLoginPage(HttpServletRequest request, HttpSer if (UrlUtils.isAbsoluteUrl(loginForm)) { return loginForm; } + if (requiresRewrite(request)) { + return httpsUri(request, loginForm); + } + return this.favorRelativeUris ? loginForm : absoluteUri(request, loginForm).getUrl(); + } + + private boolean requiresRewrite(HttpServletRequest request) { + return this.forceHttps && "http".equals(request.getScheme()); + } + + private String httpsUri(HttpServletRequest request, String path) { int serverPort = this.portResolver.getServerPort(request); - String scheme = request.getScheme(); + Integer httpsPort = this.portMapper.lookupHttpsPort(serverPort); + if (httpsPort == null) { + logger.warn(LogMessage.format("Unable to redirect to HTTPS as no port mapping found for HTTP port %s", + serverPort)); + return this.favorRelativeUris ? path : absoluteUri(request, path).getUrl(); + } + RedirectUrlBuilder builder = absoluteUri(request, path); + builder.setScheme("https"); + builder.setPort(httpsPort); + return builder.getUrl(); + } + + private RedirectUrlBuilder absoluteUri(HttpServletRequest request, String path) { RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); - urlBuilder.setScheme(scheme); + urlBuilder.setScheme(request.getScheme()); urlBuilder.setServerName(request.getServerName()); - urlBuilder.setPort(serverPort); + urlBuilder.setPort(this.portResolver.getServerPort(request)); urlBuilder.setContextPath(request.getContextPath()); - urlBuilder.setPathInfo(loginForm); - if (this.forceHttps && "http".equals(scheme)) { - Integer httpsPort = this.portMapper.lookupHttpsPort(serverPort); - if (httpsPort != null) { - // Overwrite scheme and port in the redirect URL - urlBuilder.setScheme("https"); - urlBuilder.setPort(httpsPort); - } - else { - logger.warn(LogMessage.format("Unable to redirect to HTTPS as no port mapping found for HTTP port %s", - serverPort)); - } - } - return urlBuilder.getUrl(); + urlBuilder.setPathInfo(path); + return urlBuilder; } /** @@ -244,4 +258,18 @@ protected boolean isUseForward() { return this.useForward; } + /** + * Favor using relative URIs when formulating a redirect. + * + *

+ * Note that a relative redirect is not always possible. For example, when redirecting + * from {@code http} to {@code https}, the URL needs to be absolute. + *

+ * @param favorRelativeUris whether to favor relative URIs or not + * @since 6.5 + */ + public void setFavorRelativeUris(boolean favorRelativeUris) { + this.favorRelativeUris = favorRelativeUris; + } + } diff --git a/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java b/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java index 77b49be1a16..91e2d93cdfa 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPointTests.java @@ -135,6 +135,12 @@ public void testHttpsOperationFromOriginalHttpsUrl() throws Exception { ep.setPortResolver(new MockPortResolver(8080, 8443)); ep.commence(request, response, null); assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com:8443/bigWebApp/hello"); + // access to https via http port + request.setServerPort(8080); + response = new MockHttpServletResponse(); + ep.setPortResolver(new MockPortResolver(8080, 8443)); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com:8443/bigWebApp/hello"); } @Test @@ -231,4 +237,54 @@ public void absoluteLoginFormUrlCantBeUsedWithForwarding() throws Exception { assertThatIllegalArgumentException().isThrownBy(ep::afterPropertiesSet); } + @Test + public void commenceWhenFavorRelativeUrisThenHttpsSchemeNotIncluded() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/some_path"); + request.setScheme("https"); + request.setServerName("www.example.com"); + request.setContextPath("/bigWebApp"); + request.setServerPort(443); + MockHttpServletResponse response = new MockHttpServletResponse(); + LoginUrlAuthenticationEntryPoint ep = new LoginUrlAuthenticationEntryPoint("/hello"); + ep.setFavorRelativeUris(true); + ep.setPortMapper(new PortMapperImpl()); + ep.setForceHttps(true); + ep.setPortMapper(new PortMapperImpl()); + ep.setPortResolver(new MockPortResolver(80, 443)); + ep.afterPropertiesSet(); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + request.setServerPort(8443); + response = new MockHttpServletResponse(); + ep.setPortResolver(new MockPortResolver(8080, 8443)); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + // access to https via http port + request.setServerPort(8080); + response = new MockHttpServletResponse(); + ep.setPortResolver(new MockPortResolver(8080, 8443)); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + } + + @Test + public void commenceWhenFavorRelativeUrisThenHttpSchemeNotIncluded() throws Exception { + LoginUrlAuthenticationEntryPoint ep = new LoginUrlAuthenticationEntryPoint("/hello"); + ep.setFavorRelativeUris(true); + ep.setPortMapper(new PortMapperImpl()); + ep.setPortResolver(new MockPortResolver(80, 443)); + ep.afterPropertiesSet(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/some_path"); + request.setContextPath("/bigWebApp"); + request.setScheme("http"); + request.setServerName("localhost"); + request.setContextPath("/bigWebApp"); + request.setServerPort(80); + MockHttpServletResponse response = new MockHttpServletResponse(); + ep.commence(request, response, null); + assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp/hello"); + } + }