Skip to content

Commit 649921d

Browse files
committed
Reactive: one Time Token login registers the default login page
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
1 parent 87297d9 commit 649921d

File tree

2 files changed

+94
-11
lines changed

2 files changed

+94
-11
lines changed

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

+46-11
Original file line numberDiff line numberDiff line change
@@ -3035,7 +3035,8 @@ protected void configure(ServerHttpSecurity http) {
30353035
return;
30363036
}
30373037
if (http.formLogin != null && http.formLogin.isEntryPointExplicit
3038-
|| http.oauth2Login != null && StringUtils.hasText(http.oauth2Login.loginPage)) {
3038+
|| http.oauth2Login != null && StringUtils.hasText(http.oauth2Login.loginPage)
3039+
|| http.oneTimeTokenLogin != null && StringUtils.hasText(http.oneTimeTokenLogin.loginPage)) {
30393040
return;
30403041
}
30413042
LoginPageGeneratingWebFilter loginPage = null;
@@ -3050,6 +3051,13 @@ protected void configure(ServerHttpSecurity http) {
30503051
}
30513052
loginPage.setOauth2AuthenticationUrlToClientName(urlToText);
30523053
}
3054+
if (http.oneTimeTokenLogin != null) {
3055+
if (loginPage == null) {
3056+
loginPage = new LoginPageGeneratingWebFilter();
3057+
}
3058+
loginPage.setOneTimeTokenEnabled(true);
3059+
loginPage.setGenerateOneTimeTokenUrl(http.oneTimeTokenLogin.tokenGeneratingUrl);
3060+
}
30533061
if (loginPage != null) {
30543062
http.addFilterAt(loginPage, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);
30553063
http.addFilterBefore(DefaultResourcesWebFilter.css(), SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);
@@ -5948,11 +5956,13 @@ public final class OneTimeTokenLoginSpec {
59485956

59495957
private boolean submitPageEnabled = true;
59505958

5959+
private String loginPage;
5960+
59515961
protected void configure(ServerHttpSecurity http) {
59525962
configureSubmitPage(http);
59535963
configureOttGenerateFilter(http);
59545964
configureOttAuthenticationFilter(http);
5955-
configureDefaultLoginPage(http);
5965+
configureDefaultEntryPoint(http);
59565966
}
59575967

59585968
private void configureOttAuthenticationFilter(ServerHttpSecurity http) {
@@ -5988,17 +5998,29 @@ private void configureOttGenerateFilter(ServerHttpSecurity http) {
59885998
http.addFilterAt(generateFilter, SecurityWebFiltersOrder.ONE_TIME_TOKEN);
59895999
}
59906000

5991-
private void configureDefaultLoginPage(ServerHttpSecurity http) {
5992-
if (http.formLogin != null) {
5993-
for (WebFilter webFilter : http.webFilters) {
5994-
OrderedWebFilter orderedWebFilter = (OrderedWebFilter) webFilter;
5995-
if (orderedWebFilter.webFilter instanceof LoginPageGeneratingWebFilter loginPageGeneratingFilter) {
5996-
loginPageGeneratingFilter.setOneTimeTokenEnabled(true);
5997-
loginPageGeneratingFilter.setGenerateOneTimeTokenUrl(this.tokenGeneratingUrl);
5998-
break;
5999-
}
6001+
private void configureDefaultEntryPoint(ServerHttpSecurity http) {
6002+
MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(
6003+
MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"), MediaType.TEXT_HTML,
6004+
MediaType.TEXT_PLAIN);
6005+
htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
6006+
ServerWebExchangeMatcher xhrMatcher = (exchange) -> {
6007+
if (exchange.getRequest().getHeaders().getOrEmpty("X-Requested-With").contains("XMLHttpRequest")) {
6008+
return ServerWebExchangeMatcher.MatchResult.match();
60006009
}
6010+
return ServerWebExchangeMatcher.MatchResult.notMatch();
6011+
};
6012+
ServerWebExchangeMatcher notXhrMatcher = new NegatedServerWebExchangeMatcher(xhrMatcher);
6013+
ServerWebExchangeMatcher defaultEntryPointMatcher = new AndServerWebExchangeMatcher(notXhrMatcher,
6014+
htmlMatcher);
6015+
String loginPage = "/login";
6016+
if (this.loginPage != null) {
6017+
loginPage = this.loginPage;
60016018
}
6019+
RedirectServerAuthenticationEntryPoint defaultEntryPoint = new RedirectServerAuthenticationEntryPoint(
6020+
loginPage);
6021+
defaultEntryPoint.setRequestCache(http.requestCache.requestCache);
6022+
http.defaultEntryPoints.add(new DelegateEntry(defaultEntryPointMatcher, defaultEntryPoint));
6023+
60026024
}
60036025

60046026
/**
@@ -6200,6 +6222,19 @@ Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
62006222
return this.tokenGenerationSuccessHandler;
62016223
}
62026224

6225+
/**
6226+
* Specifies the URL to send users to if login is required. A default login page
6227+
* will be generated when this attribute is not specified.
6228+
* @param loginPage the URL to send users to if login is required
6229+
* @return the {@link OAuth2LoginSpec} for further configuration
6230+
* @since 6.5
6231+
*/
6232+
public OneTimeTokenLoginSpec loginPage(String loginPage) {
6233+
Assert.hasText(loginPage, "loginPage cannot be empty");
6234+
this.loginPage = loginPage;
6235+
return this;
6236+
}
6237+
62036238
}
62046239

62056240
}

config/src/test/java/org/springframework/security/config/web/server/OneTimeTokenLoginSpecTests.java

+48
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,41 @@ void oneTimeTokenWhenWrongTokenThenAuthenticationFail() {
244244
// @formatter:on
245245
}
246246

247+
@Test
248+
void oneTimeTokenWhenConfiguredThenRendersRequestTokenForm() {
249+
this.spring.register(OneTimeTokenDefaultConfig.class).autowire();
250+
251+
//@formatter:off
252+
byte[] responseByteArray = this.client.mutateWith(SecurityMockServerConfigurers.csrf())
253+
.get()
254+
.uri((uriBuilder) -> uriBuilder
255+
.path("/login")
256+
.build()
257+
)
258+
.exchange()
259+
.expectBody()
260+
.returnResult()
261+
.getResponseBody();
262+
// @formatter:on
263+
264+
String response = new String(responseByteArray);
265+
266+
assertThat(response.contains(EXPECTED_HTML_HEAD)).isTrue();
267+
assertThat(response.contains(GENERATE_OTT_PART)).isTrue();
268+
}
269+
270+
@Test
271+
void oneTimeTokenWhenConfiguredThenRedirectsToLoginPage() {
272+
this.spring.register(OneTimeTokenDefaultConfig.class).autowire();
273+
274+
this.client.mutateWith(SecurityMockServerConfigurers.csrf())
275+
.get()
276+
.uri((uriBuilder) -> uriBuilder.path("/").build())
277+
.exchange()
278+
.expectHeader()
279+
.location("/login");
280+
}
281+
247282
@Test
248283
void oneTimeTokenWhenFormLoginConfiguredThenRendersRequestTokenForm() {
249284
this.spring.register(OneTimeTokenFormLoginConfig.class).autowire();
@@ -268,6 +303,18 @@ void oneTimeTokenWhenFormLoginConfiguredThenRendersRequestTokenForm() {
268303
assertThat(response.contains(GENERATE_OTT_PART)).isTrue();
269304
}
270305

306+
@Test
307+
void oneTimeTokenWhenCustomLoginPageThenRedirects() {
308+
this.spring.register(OneTimeTokenDifferentUrlsConfig.class).autowire();
309+
310+
this.client.mutateWith(SecurityMockServerConfigurers.csrf())
311+
.get()
312+
.uri((uriBuilder) -> uriBuilder.path("/login").build())
313+
.exchange()
314+
.expectHeader()
315+
.location("/custom-login");
316+
}
317+
271318
@Test
272319
void oneTimeTokenWhenNoOneTimeTokenGenerationSuccessHandlerThenException() {
273320
assertThatException()
@@ -318,6 +365,7 @@ SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
318365
.authenticated()
319366
)
320367
.oneTimeTokenLogin((ott) -> ott
368+
.loginPage("/custom-login")
321369
.tokenGeneratingUrl("/generateurl")
322370
.tokenGenerationSuccessHandler(new TestServerOneTimeTokenGenerationSuccessHandler("/redirected"))
323371
.loginProcessingUrl("/loginprocessingurl")

0 commit comments

Comments
 (0)