diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java index 9ee49a828c8..03b75e87eeb 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java @@ -20,8 +20,6 @@ import java.util.Map; import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; @@ -59,8 +57,6 @@ public final class OneTimeTokenLoginConfigurer> extends AbstractHttpConfigurer, H> { - private final Log logger = LogFactory.getLog(getClass()); - private final ApplicationContext context; private OneTimeTokenService oneTimeTokenService; 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 be2273e48f8..8e63b4a7297 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.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 @@ void oneTimeTokenWhenWrongTokenThenAuthenticationFail() throws Exception { .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 + + """ + +
+ + + + +
+ + """.formatted(token.getToken(), token.getToken())); + }); + //@formatter:on + } + @Test void oneTimeTokenWhenNoGeneratedOneTimeTokenHandlerThenException() { assertThatException() @@ -167,6 +357,28 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { } + @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) diff --git a/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java index 86681958ae0..e486ab6362e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java @@ -66,6 +66,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse private String generateHtml(HttpServletRequest request) { String token = request.getParameter("token"); String tokenValue = StringUtils.hasText(token) ? token : ""; + String contextPath = request.getContextPath(); String hiddenInputs = this.resolveHiddenInputs.apply(request) .entrySet() @@ -76,7 +77,7 @@ private String generateHtml(HttpServletRequest request) { return HtmlTemplates.fromTemplate(ONE_TIME_TOKEN_SUBMIT_PAGE_TEMPLATE) .withRawHtml("cssStyle", CssUtils.getCssStyleBlock().indent(4)) .withValue("tokenValue", tokenValue) - .withValue("loginProcessingUrl", this.loginProcessingUrl) + .withValue("loginProcessingUrl", contextPath + this.loginProcessingUrl) .withRawHtml("hiddenInputs", hiddenInputs) .render(); } @@ -115,15 +116,9 @@ public void setLoginProcessingUrl(String loginProcessingUrl) { One-Time Token Login - {{cssStyle}} -