diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
index c5316b865e..fae05d41da 100644
--- a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
+++ b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
@@ -134,8 +134,11 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,
SaveContextOnUpdateOrErrorResponseWrapper.class);
- Assert.state(responseWrapper != null, () -> "Cannot invoke saveContext on response " + response
- + ". You must use the HttpRequestResponseHolder.response after invoking loadContext");
+ if (responseWrapper == null) {
+ boolean httpSessionExists = request.getSession(false) != null;
+ SecurityContext initialContext = SecurityContextHolder.createEmptyContext();
+ responseWrapper = new SaveToSessionResponseWrapper(response, request, httpSessionExists, initialContext);
+ }
responseWrapper.saveContext(context);
}
diff --git a/web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java
index 506dda8e38..3a6806bdc9 100644
--- a/web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java
+++ b/web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java
@@ -48,9 +48,12 @@ public interface SecurityContextRepository {
* to return wrapped versions of the request or response (or both), allowing them to
* access implementation-specific state for the request. The values obtained from the
* holder will be passed on to the filter chain and also to the saveContext
- * method when it is finally called. Implementations may wish to return a subclass of
+ * method when it is finally called to allow implicit saves of the
+ * SecurityContext. Implementations may wish to return a subclass of
* {@link SaveContextOnUpdateOrErrorResponseWrapper} as the response object, which
* guarantees that the context is persisted when an error or redirect occurs.
+ * Implementations may allow passing in the original request response to allow
+ * explicit saves.
* @param requestResponseHolder holder for the current request and response for which
* the context should be loaded.
* @return The security context which should be used for the current request, never
diff --git a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java
index e8856f59a1..d0b0209131 100644
--- a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java
+++ b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java
@@ -58,7 +58,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -581,13 +580,16 @@ public class HttpSessionSecurityContextRepositoryTests {
}
@Test
- public void failsWithStandardResponse() {
+ public void standardResponseWorks() {
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(this.testToken);
- assertThatIllegalStateException().isThrownBy(() -> repo.saveContext(context, request, response));
+ repo.saveContext(context, request, response);
+ assertThat(request.getSession(false)).isNotNull();
+ assertThat(request.getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY))
+ .isEqualTo(context);
}
@Test