Skip to content

Commit 72b8a7d

Browse files
authored
fix: add default success URL configuration for authentication (#22979) (#22992)
Adds defaultSuccessUrl(String) and defaultSuccessUrl(String, boolean) methods to VaadinSecurityConfigurer. Similar API as with Spring FormLoginConfigurer. Fixes: #22928
1 parent 900d21f commit 72b8a7d

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed

vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurer.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ public final class VaadinSecurityConfigurer
147147

148148
private String oauth2LoginPage;
149149

150+
private String defaultSuccessUrl;
151+
152+
private boolean alwaysUseDefaultSuccessUrl;
153+
150154
private String logoutSuccessUrl;
151155

152156
private String postLogoutRedirectUri;
@@ -308,6 +312,43 @@ public VaadinSecurityConfigurer oauth2LoginPage(String oauth2LoginPage,
308312
return this;
309313
}
310314

315+
/**
316+
* Sets the default success URL after authentication. Redirected only when
317+
* no protected page was previously accessed. Works only together with
318+
* {@link #loginView(String)} or {@link #oauth2LoginPage(String)} and their
319+
* variants.
320+
*
321+
* @param defaultSuccessUrl
322+
* the default success url
323+
* @return the current configurer instance for method chaining
324+
* @see #defaultSuccessUrl(String, boolean)
325+
*/
326+
public VaadinSecurityConfigurer defaultSuccessUrl(
327+
String defaultSuccessUrl) {
328+
return defaultSuccessUrl(defaultSuccessUrl, false);
329+
}
330+
331+
/**
332+
* Sets the default success URL after authentication. If alwaysUse is true,
333+
* always redirects to this URL; otherwise, only when no protected page was
334+
* previously accessed. Works only together with {@link #loginView(String)}
335+
* or {@link #oauth2LoginPage(String)} and their variants.
336+
*
337+
* @param defaultSuccessUrl
338+
* the default success url
339+
* @param alwaysUse
340+
* if true, always redirect to {@code defaultSuccessUrl} after
341+
* authentication, even when a protected page was previously
342+
* accessed
343+
* @return the current configurer instance for method chaining
344+
*/
345+
public VaadinSecurityConfigurer defaultSuccessUrl(String defaultSuccessUrl,
346+
boolean alwaysUse) {
347+
this.defaultSuccessUrl = defaultSuccessUrl;
348+
this.alwaysUseDefaultSuccessUrl = alwaysUse;
349+
return this;
350+
}
351+
311352
/**
312353
* Configures the handler for a successful logout.
313354
* <p>
@@ -626,7 +667,12 @@ private VaadinSavedRequestAwareAuthenticationSuccessHandler getAuthenticationSuc
626667

627668
private VaadinSavedRequestAwareAuthenticationSuccessHandler createAuthenticationSuccessHandler() {
628669
var handler = new VaadinSavedRequestAwareAuthenticationSuccessHandler();
629-
handler.setDefaultTargetUrl(getRequestUtil().applyUrlMapping(""));
670+
if (defaultSuccessUrl != null) {
671+
handler.setDefaultTargetUrl(defaultSuccessUrl);
672+
handler.setAlwaysUseDefaultTargetUrl(alwaysUseDefaultSuccessUrl);
673+
} else {
674+
handler.setDefaultTargetUrl(getRequestUtil().applyUrlMapping(""));
675+
}
630676
getSharedObject(RequestCache.class).ifPresent(handler::setRequestCache);
631677
getBuilder().setSharedObject(
632678
VaadinSavedRequestAwareAuthenticationSuccessHandler.class,

vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurerTest.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import com.vaadin.flow.spring.SpringSecurityAutoConfiguration;
1010
import jakarta.servlet.FilterChain;
1111
import jakarta.servlet.http.HttpServletResponse;
12+
13+
import java.lang.reflect.Method;
14+
import java.util.List;
15+
import java.util.Map;
16+
1217
import org.junit.jupiter.api.AfterEach;
1318
import org.junit.jupiter.api.BeforeEach;
1419
import org.junit.jupiter.api.Test;
@@ -43,6 +48,7 @@
4348
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
4449
import org.springframework.security.web.access.ExceptionTranslationFilter;
4550
import org.springframework.security.web.access.intercept.AuthorizationFilter;
51+
import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler;
4652
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
4753
import org.springframework.security.web.authentication.logout.LogoutFilter;
4854
import org.springframework.security.web.authentication.logout.LogoutHandler;
@@ -370,6 +376,110 @@ void hilla_checkAllowedRoutes() throws Exception {
370376
}
371377
}
372378

379+
@Test
380+
void defaultSuccessUrl_withLoginView_successHandlerIsConfigured()
381+
throws Exception {
382+
http.with(configurer,
383+
c -> c.loginView("/login").defaultSuccessUrl("/dashboard"))
384+
.build();
385+
386+
var handler = http.getSharedObject(
387+
VaadinSavedRequestAwareAuthenticationSuccessHandler.class);
388+
389+
assertThat(handler).isNotNull();
390+
assertThat(getDefaultTargetUrl(handler)).isEqualTo("/dashboard");
391+
assertThat(isAlwaysUseDefaultTargetUrl(handler)).isFalse();
392+
}
393+
394+
@Test
395+
void defaultSuccessUrl_withLoginViewClass_successHandlerIsConfigured()
396+
throws Exception {
397+
http.with(configurer, c -> c.loginView(TestLoginView.class)
398+
.defaultSuccessUrl("/home")).build();
399+
400+
var handler = http.getSharedObject(
401+
VaadinSavedRequestAwareAuthenticationSuccessHandler.class);
402+
403+
assertThat(handler).isNotNull();
404+
assertThat(getDefaultTargetUrl(handler)).isEqualTo("/home");
405+
assertThat(isAlwaysUseDefaultTargetUrl(handler)).isFalse();
406+
}
407+
408+
@Test
409+
void defaultSuccessUrl_withOAuth2LoginPage_successHandlerIsConfigured()
410+
throws Exception {
411+
http.with(configurer,
412+
c -> c.oauth2LoginPage("/oauth2/authorization/google")
413+
.defaultSuccessUrl("/main"))
414+
.build();
415+
416+
var handler = http.getSharedObject(
417+
VaadinSavedRequestAwareAuthenticationSuccessHandler.class);
418+
419+
assertThat(handler).isNotNull();
420+
assertThat(getDefaultTargetUrl(handler)).isEqualTo("/main");
421+
assertThat(isAlwaysUseDefaultTargetUrl(handler)).isFalse();
422+
}
423+
424+
@Test
425+
void defaultSuccessUrl_withAlwaysUseTrue_alwaysRedirectsToDefaultUrl()
426+
throws Exception {
427+
http.with(configurer, c -> c.loginView("/login")
428+
.defaultSuccessUrl("/dashboard", true)).build();
429+
430+
var handler = http.getSharedObject(
431+
VaadinSavedRequestAwareAuthenticationSuccessHandler.class);
432+
433+
assertThat(handler).isNotNull();
434+
assertThat(getDefaultTargetUrl(handler)).isEqualTo("/dashboard");
435+
assertThat(isAlwaysUseDefaultTargetUrl(handler)).isTrue();
436+
}
437+
438+
@Test
439+
void defaultSuccessUrl_withAlwaysUseFalse_redirectsToSavedRequest()
440+
throws Exception {
441+
http.with(configurer, c -> c.loginView("/login")
442+
.defaultSuccessUrl("/dashboard", false)).build();
443+
444+
var handler = http.getSharedObject(
445+
VaadinSavedRequestAwareAuthenticationSuccessHandler.class);
446+
447+
assertThat(handler).isNotNull();
448+
assertThat(getDefaultTargetUrl(handler)).isEqualTo("/dashboard");
449+
assertThat(isAlwaysUseDefaultTargetUrl(handler)).isFalse();
450+
}
451+
452+
@Test
453+
void defaultSuccessUrl_notSet_usesRootPath() throws Exception {
454+
http.with(configurer, c -> c.loginView("/login")).build();
455+
456+
var handler = http.getSharedObject(
457+
VaadinSavedRequestAwareAuthenticationSuccessHandler.class);
458+
459+
assertThat(handler).isNotNull();
460+
assertThat(getDefaultTargetUrl(handler)).isEqualTo("/");
461+
assertThat(isAlwaysUseDefaultTargetUrl(handler)).isFalse();
462+
}
463+
464+
// Helper methods to access protected fields using reflection
465+
private String getDefaultTargetUrl(
466+
VaadinSavedRequestAwareAuthenticationSuccessHandler handler)
467+
throws Exception {
468+
Method method = AbstractAuthenticationTargetUrlRequestHandler.class
469+
.getDeclaredMethod("getDefaultTargetUrl");
470+
method.setAccessible(true);
471+
return (String) method.invoke(handler);
472+
}
473+
474+
private boolean isAlwaysUseDefaultTargetUrl(
475+
VaadinSavedRequestAwareAuthenticationSuccessHandler handler)
476+
throws Exception {
477+
Method method = AbstractAuthenticationTargetUrlRequestHandler.class
478+
.getDeclaredMethod("isAlwaysUseDefaultTargetUrl");
479+
method.setAccessible(true);
480+
return (boolean) method.invoke(handler);
481+
}
482+
373483
@Route
374484
static class TestLoginView extends Component {
375485
}

0 commit comments

Comments
 (0)