diff --git a/web/src/main/java/org/springframework/security/web/DefaultRedirectStrategy.java b/web/src/main/java/org/springframework/security/web/DefaultRedirectStrategy.java index 8e9f0def763..ff74b38346f 100644 --- a/web/src/main/java/org/springframework/security/web/DefaultRedirectStrategy.java +++ b/web/src/main/java/org/springframework/security/web/DefaultRedirectStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 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. @@ -24,6 +24,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.security.web.util.UrlUtils; import org.springframework.util.Assert; @@ -32,6 +34,7 @@ * the framework. * * @author Luke Taylor + * @author Mark Chesney * @since 3.0 */ public class DefaultRedirectStrategy implements RedirectStrategy { @@ -40,6 +43,8 @@ public class DefaultRedirectStrategy implements RedirectStrategy { private boolean contextRelative; + private HttpStatus statusCode = HttpStatus.FOUND; + /** * Redirects the response to the supplied URL. *

@@ -55,7 +60,14 @@ public void sendRedirect(HttpServletRequest request, HttpServletResponse respons if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Redirecting to %s", redirectUrl)); } - response.sendRedirect(redirectUrl); + if (this.statusCode == HttpStatus.FOUND) { + response.sendRedirect(redirectUrl); + } + else { + response.setHeader(HttpHeaders.LOCATION, redirectUrl); + response.setStatus(this.statusCode.value()); + response.getWriter().flush(); + } } protected String calculateRedirectUrl(String contextPath, String url) { @@ -96,4 +108,18 @@ protected boolean isContextRelative() { return this.contextRelative; } + /** + * Sets the HTTP status code to use. The default is {@link HttpStatus#FOUND}. + *

+ * Note that according to RFC 7231, with {@link HttpStatus#FOUND}, a user agent MAY + * change the request method from POST to GET for the subsequent request. If this + * behavior is undesired, {@link HttpStatus#TEMPORARY_REDIRECT} can be used instead. + * @param statusCode the HTTP status code to use. + * @since 6.2 + */ + public void setStatusCode(HttpStatus statusCode) { + Assert.notNull(statusCode, "statusCode cannot be null"); + this.statusCode = statusCode; + } + } diff --git a/web/src/test/java/org/springframework/security/web/DefaultRedirectStrategyTests.java b/web/src/test/java/org/springframework/security/web/DefaultRedirectStrategyTests.java index 098309fd978..ca8adbcd6e0 100644 --- a/web/src/test/java/org/springframework/security/web/DefaultRedirectStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/DefaultRedirectStrategyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 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. @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -26,6 +27,7 @@ /** * @author Luke Taylor + * @author Mark Chesney * @since 3.0 */ public class DefaultRedirectStrategyTests { @@ -64,4 +66,21 @@ public void contextRelativeShouldThrowExceptionIfURLDoesNotContainContextPath() .isThrownBy(() -> rds.sendRedirect(request, response, "https://redirectme.somewhere.else")); } + @Test + public void statusCodeIsHandledCorrectly() throws Exception { + // given + DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + redirectStrategy.setStatusCode(HttpStatus.TEMPORARY_REDIRECT); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // when + redirectStrategy.sendRedirect(request, response, "/requested"); + + // then + assertThat(response.isCommitted()).isTrue(); + assertThat(response.getRedirectedUrl()).isEqualTo("/requested"); + assertThat(response.getStatus()).isEqualTo(307); + } + } diff --git a/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java b/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java index 1570bf734c7..bbd095631fb 100644 --- a/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -210,6 +211,37 @@ public void responseIsRedirectedToRequestedUrlIfContextPathIsSetAndSessionIsInva assertThat(response.getStatus()).isEqualTo(302); } + @Test + public void responseIsRedirectedToRequestedUrlIfStatusCodeIsSetAndSessionIsInvalid() throws Exception { + // given + DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + redirectStrategy.setStatusCode(HttpStatus.TEMPORARY_REDIRECT); + RequestedUrlRedirectInvalidSessionStrategy invalidSessionStrategy = new RequestedUrlRedirectInvalidSessionStrategy(); + invalidSessionStrategy.setCreateNewSession(true); + invalidSessionStrategy.setRedirectStrategy(redirectStrategy); + SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class); + SessionAuthenticationStrategy sessionAuthenticationStrategy = mock(SessionAuthenticationStrategy.class); + SessionManagementFilter filter = new SessionManagementFilter(securityContextRepository, + sessionAuthenticationStrategy); + filter.setInvalidSessionStrategy(invalidSessionStrategy); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestedSessionId("xxx"); + request.setRequestedSessionIdValid(false); + request.setRequestURI("/requested"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = mock(FilterChain.class); + + // when + filter.doFilter(request, response, chain); + + // then + verify(securityContextRepository).containsContext(request); + verifyNoMoreInteractions(securityContextRepository, sessionAuthenticationStrategy, chain); + assertThat(response.isCommitted()).isTrue(); + assertThat(response.getRedirectedUrl()).isEqualTo("/requested"); + assertThat(response.getStatus()).isEqualTo(307); + } + @Test public void customAuthenticationTrustResolver() throws Exception { AuthenticationTrustResolver trustResolver = mock(AuthenticationTrustResolver.class);