diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java index be2273e48f..8e63b4a729 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java @@ -41,12 +41,17 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.authentication.ott.GeneratedOneTimeTokenHandler; import org.springframework.security.web.authentication.ott.RedirectGeneratedOneTimeTokenHandler; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.DefaultCsrfToken; +import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.test.web.servlet.MockMvc; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatException; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -59,6 +64,143 @@ public class OneTimeTokenLoginConfigurerTests { @Autowired(required = false) MockMvc mvc; + public static final String EXPECTED_HTML_HEAD = """ + + + + + + + + Please sign in + + + """; + @Test void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() throws Exception { this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); @@ -110,6 +252,54 @@ public class OneTimeTokenLoginConfigurerTests { .andExpectAll(status().isFound(), redirectedUrl("/login?error"), unauthenticated()); } + @Test + void oneTimeTokenWhenFormLoginConfiguredThenRendersRequestTokenForm() throws Exception { + this.spring.register(OneTimeTokenFormLoginConfig.class).autowire(); + CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); + String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); + //@formatter:off + this.mvc.perform(get("/login").sessionAttr(csrfAttributeName, csrfToken)) + .andExpect((result) -> { + CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); + assertThat(result.getResponse().getContentAsString()).isEqualTo( + EXPECTED_HTML_HEAD + + """ + +
+
+

Please sign in

+ \s +

+ + +

+

+ + +

+ + + +
+
+

Request a One-Time Token

+ \s +

+ + +

+ + +
+ + +
+ + """.formatted(token.getToken(), token.getToken())); + }); + //@formatter:on + } + @Test void oneTimeTokenWhenNoGeneratedOneTimeTokenHandlerThenException() { assertThatException() @@ -167,6 +357,28 @@ public class OneTimeTokenLoginConfigurerTests { } + @Configuration(proxyBeanMethods = false) + @EnableWebSecurity + @Import(UserDetailsServiceConfig.class) + static class OneTimeTokenFormLoginConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .formLogin(Customizer.withDefaults()) + .oneTimeTokenLogin((ott) -> ott + .generatedOneTimeTokenHandler(new TestGeneratedOneTimeTokenHandler()) + ); + // @formatter:on + return http.build(); + } + + } + @Configuration(proxyBeanMethods = false) @EnableWebSecurity @Import(UserDetailsServiceConfig.class)