diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java index 209b0bdbb72..f345901f196 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java @@ -21,7 +21,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; @@ -43,7 +42,6 @@ import com.vaadin.flow.spring.flowsecurity.data.UserInfo; import com.vaadin.flow.spring.flowsecurity.service.UserInfoService; import com.vaadin.flow.spring.flowsecurity.views.LoginView; -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; import static com.vaadin.flow.spring.flowsecurity.service.UserInfoService.ROLE_ADMIN; import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin; @@ -52,7 +50,6 @@ @EnableMethodSecurity(prePostEnabled = false, jsr250Enabled = true, securedEnabled = true) @Configuration @Profile("default") -@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) public class SecurityConfig { private final UserInfoService userInfoService; diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java index 5d1c5f60b50..7c56572c87d 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java @@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -41,14 +40,12 @@ import com.vaadin.flow.spring.flowsecurity.service.UserInfoService; import com.vaadin.flow.spring.flowsecurity.views.LoginView; import com.vaadin.flow.spring.security.NavigationAccessControlConfigurer; -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; import static com.vaadin.flow.spring.flowsecurity.service.UserInfoService.ROLE_ADMIN; import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin; @EnableWebSecurity @Configuration -@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) public class SecurityConfig { @Autowired diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java index f88c5c3bc46..d0f3f8e4ce7 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java @@ -28,6 +28,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -70,11 +71,12 @@ public class SecurityConfig { @Autowired private RequestUtil requestUtil; - private final AuthenticationContext authenticationContext = new AuthenticationContext(); + @Autowired + private SecurityContextHolderStrategy securityContextHolderStrategy; @Bean public AuthenticationContext authenticationContext() { - return authenticationContext; + return new AuthenticationContext(securityContextHolderStrategy); } @Bean diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/test/java/com/vaadin/flow/spring/flowsecurity/AppViewIT.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/test/java/com/vaadin/flow/spring/flowsecurity/AppViewIT.java index ec1e22befbd..54d47c9e595 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/test/java/com/vaadin/flow/spring/flowsecurity/AppViewIT.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/test/java/com/vaadin/flow/spring/flowsecurity/AppViewIT.java @@ -286,11 +286,6 @@ public void navigate_in_thread_without_access() { } @Test - @Ignore(""" - Requires VaadinAwareSecurityContextHolderStrategyConfiguration that - in a custom Spring Security configuration without Vaadin helpers might not be imported. - Leaving the test here just as a template in case of future improvements. - """) public void navigate_in_thread_with_access() { open(LOGIN_PATH); loginAdmin(); diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/test/java/com/vaadin/flow/spring/flowsecurity/UIAccessContextIT.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/test/java/com/vaadin/flow/spring/flowsecurity/UIAccessContextIT.java index 01caa7e53d8..0d6558eced6 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/test/java/com/vaadin/flow/spring/flowsecurity/UIAccessContextIT.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/src/test/java/com/vaadin/flow/spring/flowsecurity/UIAccessContextIT.java @@ -16,7 +16,6 @@ package com.vaadin.flow.spring.flowsecurity; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import org.openqa.selenium.WebDriver; @@ -28,11 +27,6 @@ public class UIAccessContextIT extends AbstractIT { @Test - @Ignore(""" - Requires VaadinAwareSecurityContextHolderStrategyConfiguration that - in a custom Spring Security configuration without Vaadin helpers might not be imported. - Leaving the test here just as a template in case of future improvements. - """) public void securityContextSetForUIAccess() throws Exception { String expectedUserBalance = "Hello John the User, your bank account balance is $10000.00."; String expectedAdminBalance = "Hello Emma the Admin, your bank account balance is $200000.00."; diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-themes-contextpath/src/main/java/com/vaadin/flow/spring/flowthemessecuritycontextpath/SecurityConfig.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow-themes-contextpath/src/main/java/com/vaadin/flow/spring/flowthemessecuritycontextpath/SecurityConfig.java index 30703bf3306..5d25a2e926e 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-themes-contextpath/src/main/java/com/vaadin/flow/spring/flowthemessecuritycontextpath/SecurityConfig.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-themes-contextpath/src/main/java/com/vaadin/flow/spring/flowthemessecuritycontextpath/SecurityConfig.java @@ -17,20 +17,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; - import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin; @EnableWebSecurity @Configuration @Profile("default") -@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) public class SecurityConfig { @Bean diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java index 26b891699e6..2edad32ff2f 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java @@ -25,14 +25,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; -import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -50,7 +49,6 @@ import com.vaadin.flow.spring.flowsecurity.service.UserInfoService; import com.vaadin.flow.spring.flowsecurity.views.LoginView; import com.vaadin.flow.spring.security.UidlRedirectStrategy; -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; import static com.vaadin.flow.spring.flowsecurity.service.UserInfoService.ROLE_ADMIN; import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin; @@ -58,7 +56,6 @@ @EnableWebSecurity @Configuration @Profile("default") -@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) public class SecurityConfig { private final UserInfoService userInfoService; @@ -171,9 +168,10 @@ public UserDetails loadUserByUsername(String username) } @Bean - @DependsOn("VaadinSecurityContextHolderStrategy") - public SwitchUserFilter switchUserFilter() { + public SwitchUserFilter switchUserFilter( + SecurityContextHolderStrategy securityContextHolderStrategy) { SwitchUserFilter filter = new SwitchUserFilter(); + filter.setSecurityContextHolderStrategy(securityContextHolderStrategy); filter.setUserDetailsService(userDetailsService()); filter.setSwitchUserMatcher(PathPatternRequestMatcher .pathPattern(HttpMethod.GET, "/impersonate")); diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityUtils.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityUtils.java index 5e394964fd6..69fc287ad6a 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityUtils.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityUtils.java @@ -15,9 +15,12 @@ */ package com.vaadin.flow.spring.flowsecurity; +import java.util.Collection; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; @@ -42,6 +45,14 @@ public UserInfo getAuthenticatedUserInfo() { return userInfoService.findByUsername(userDetails.get().getUsername()); } + public SecurityContext getSecurityContext() { + return authenticationContext.getSecurityContext(); + } + + public Collection getAuthorities() { + return authenticationContext.getGrantedAuthorities(); + } + public void logout() { authenticationContext.logout(); } diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/AdminView.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/AdminView.java index 3155a0b8ad3..d01fba9af21 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/AdminView.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/AdminView.java @@ -20,7 +20,6 @@ import java.util.concurrent.TimeUnit; import org.springframework.security.concurrent.DelegatingSecurityContextRunnable; -import org.springframework.security.core.context.SecurityContextHolder; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; @@ -63,7 +62,7 @@ public AdminView(SecurityUtils securityUtils) { } }; Runnable wrappedRunnable = new DelegatingSecurityContextRunnable( - doNavigation, SecurityContextHolder.getContext()); + doNavigation, securityUtils.getSecurityContext()); new Thread(wrappedRunnable).start(); }); add(accessRolePrefixedAdminPageFromThread); diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/MainView.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/MainView.java index 79c6cf2fd71..c022ed22bd8 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/MainView.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/MainView.java @@ -20,7 +20,6 @@ import java.util.concurrent.TimeUnit; import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; import com.vaadin.flow.component.Component; @@ -125,10 +124,9 @@ private Component createDrawerContent(Tabs menu) { .setLocation("impersonate?username=john"))); impersonate.setId("impersonate"); layout.add(impersonate); - } else if (SecurityContextHolder.getContext().getAuthentication() - .getAuthorities().stream().anyMatch( - auth -> SwitchUserFilter.ROLE_PREVIOUS_ADMINISTRATOR - .equals(auth.getAuthority()))) { + } else if (securityUtils.getAuthorities().stream().anyMatch( + auth -> SwitchUserFilter.ROLE_PREVIOUS_ADMINISTRATOR + .equals(auth.getAuthority()))) { Button impersonate = new Button("Exit impersonation", e -> getUI().ifPresent(ui -> ui.getPage() .setLocation("impersonate/exit"))); diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/PublicView.java b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/PublicView.java index abdcafd05f7..9ef6cd3ab37 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/PublicView.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/PublicView.java @@ -16,7 +16,6 @@ package com.vaadin.flow.spring.flowsecurity.views; import org.springframework.security.concurrent.DelegatingSecurityContextRunnable; -import org.springframework.security.core.context.SecurityContextHolder; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; @@ -28,6 +27,7 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.auth.AnonymousAllowed; +import com.vaadin.flow.spring.flowsecurity.SecurityUtils; @Route(value = "", layout = MainView.class) @PageTitle("Public View") @@ -37,7 +37,7 @@ public class PublicView extends FlexLayout { public static final String BACKGROUND_NAVIGATION_ID = "backgroundNavi"; - public PublicView() { + public PublicView(SecurityUtils securityUtils) { setFlexDirection(FlexDirection.COLUMN); setHeightFull(); @@ -65,7 +65,7 @@ public PublicView() { }; Runnable wrappedRunnable = new DelegatingSecurityContextRunnable( navigateToAdmin, - SecurityContextHolder.getContext()); + securityUtils.getSecurityContext()); new Thread(wrappedRunnable).start(); }); backgroundNavigation.setId(BACKGROUND_NAVIGATION_ID); diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-webicons-urlmapping/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java b/flow-tests/vaadin-spring-tests/test-spring-security-webicons-urlmapping/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java index 7ab65e706f2..cdae82a95ef 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-webicons-urlmapping/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-webicons-urlmapping/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java @@ -17,20 +17,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; - import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin; @EnableWebSecurity @Configuration @Profile("default") -@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) public class SecurityConfig { @Bean diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-webicons/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java b/flow-tests/vaadin-spring-tests/test-spring-security-webicons/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java index 7ab65e706f2..cdae82a95ef 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-webicons/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java +++ b/flow-tests/vaadin-spring-tests/test-spring-security-webicons/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityConfig.java @@ -17,20 +17,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; - import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin; @EnableWebSecurity @Configuration @Profile("default") -@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) public class SecurityConfig { @Bean diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/AuthenticationUtil.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/AuthenticationUtil.java index 5bdbd46bd38..9e0bd915269 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/AuthenticationUtil.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/AuthenticationUtil.java @@ -15,10 +15,13 @@ */ package com.vaadin.flow.spring; +import java.util.Objects; import java.util.function.Function; +import java.util.function.Supplier; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** @@ -26,13 +29,27 @@ */ public class AuthenticationUtil { + private static Supplier securityContextSupplier = SecurityContextHolder::getContext; + + /** + * Sets the supplier for providing a custom SecurityContext. + * + * @param supplier + * the supplier function used to provide a SecurityContext + */ + public static void setSecurityContextSupplier( + Supplier supplier) { + securityContextSupplier = Objects.requireNonNull(supplier, + "SecurityContext supplier cannot be null"); + } + /** * Gets the authenticated user from the Spring SecurityContextHolder. * * @return the authenticated user or {@code null} */ public static Authentication getSecurityHolderAuthentication() { - Authentication authentication = SecurityContextHolder.getContext() + Authentication authentication = securityContextSupplier.get() .getAuthentication(); if (authentication instanceof AnonymousAuthenticationToken) { return null; diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringSecurityAutoConfiguration.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringSecurityAutoConfiguration.java index bfa1c1319c4..6a2c7aedf8c 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringSecurityAutoConfiguration.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringSecurityAutoConfiguration.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; +import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -26,6 +27,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.core.GrantedAuthorityDefaults; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; import com.vaadin.flow.server.auth.AccessAnnotationChecker; @@ -40,6 +42,7 @@ import com.vaadin.flow.spring.security.RequestUtil; import com.vaadin.flow.spring.security.SpringAccessPathChecker; import com.vaadin.flow.spring.security.SpringNavigationAccessControl; +import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategy; import com.vaadin.flow.spring.security.VaadinDefaultRequestCache; import com.vaadin.flow.spring.security.VaadinRolePrefixHolder; @@ -158,9 +161,10 @@ public RoutePathAccessChecker routePathAccessChecker( @ConditionalOnMissingBean public AccessPathChecker accessPatchChecker( VaadinConfigurationProperties vaadinProperties, - @Lazy WebInvocationPrivilegeEvaluator evaluator) { - return new SpringAccessPathChecker(evaluator, - vaadinProperties.getUrlMapping()); + @Lazy WebInvocationPrivilegeEvaluator evaluator, + SecurityContextHolderStrategy securityContextHolderStrategy) { + return new SpringAccessPathChecker(securityContextHolderStrategy, + evaluator, vaadinProperties.getUrlMapping()); } /** @@ -206,7 +210,24 @@ public VaadinRolePrefixHolder vaadinRolePrefixHolder( @Bean @ConditionalOnMissingBean - AuthenticationContext authenticationContext() { - return new AuthenticationContext(); + SecurityContextHolderStrategy vaadinAwareSecurityContextHolderStrategy() { + return new VaadinAwareSecurityContextHolderStrategy(); + } + + // Ensure AuthenticationUtil is wired to whichever + // SecurityContextHolderStrategy bean is in use, + // even if a custom one is provided by the application. + @Bean + SmartInitializingSingleton securityContextHolderStrategyInitializer( + SecurityContextHolderStrategy securityContextHolderStrategy) { + return () -> AuthenticationUtil.setSecurityContextSupplier( + securityContextHolderStrategy::getContext); + } + + @Bean + @ConditionalOnMissingBean + AuthenticationContext authenticationContext( + SecurityContextHolderStrategy securityContextHolderStrategy) { + return new AuthenticationContext(securityContextHolderStrategy); } } diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/AuthenticationContext.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/AuthenticationContext.java index cb2ad1bd15c..0b5e917fd8d 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/AuthenticationContext.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/AuthenticationContext.java @@ -25,6 +25,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -39,6 +40,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; @@ -75,6 +77,47 @@ public class AuthenticationContext { private VaadinRolePrefixHolder rolePrefixHolder; + private final SecurityContextHolderStrategy securityContextHolderStrategy; + + /** + * Creates a new instance of {@link AuthenticationContext} using the + * provided {@link SecurityContextHolderStrategy} to get the current + * {@link SecurityContext}. + * + * @param strategy + * the strategy to get the current {@link SecurityContext} + */ + public AuthenticationContext(SecurityContextHolderStrategy strategy) { + Objects.requireNonNull(strategy, "strategy cannot be null"); + this.securityContextHolderStrategy = strategy; + } + + /** + * Creates a new instance of {@link AuthenticationContext} using the + * {@link SecurityContextHolderStrategy} from + * {@link SecurityContextHolder#getContextHolderStrategy()}. + *

+ * Prefer using + * {@link #AuthenticationContext(SecurityContextHolderStrategy)} to ensure + * the correct strategy for the current context is used. + * + * @deprecated Use + * {@link #AuthenticationContext(SecurityContextHolderStrategy)} + */ + @Deprecated(since = "25.0", forRemoval = true) + public AuthenticationContext() { + this(SecurityContextHolder.getContextHolderStrategy()); + } + + /** + * Gets the current {@link SecurityContext}. + * + * @return the current {@link SecurityContext} + */ + public SecurityContext getSecurityContext() { + return securityContextHolderStrategy.getContext(); + } + /** * Gets an {@link Optional} with an instance of the current user if it has * been authenticated, or empty if the user is not authenticated. @@ -171,7 +214,7 @@ private void doLogout(UI ui) { .ofNullable(VaadinServletResponse.getCurrent()) .map(VaadinServletResponse::getHttpServletResponse) .orElse(null); - Authentication auth = SecurityContextHolder.getContext() + Authentication auth = securityContextHolderStrategy.getContext() .getAuthentication(); logoutHandler.logout(request, response, auth); @@ -433,8 +476,9 @@ void setRolePrefixHolder(VaadinRolePrefixHolder rolePrefixHolder) { this.rolePrefixHolder = rolePrefixHolder; } - private static Optional getAuthentication() { - return Optional.of(SecurityContextHolder.getContext()) + private Optional getAuthentication() { + return Optional.of(securityContextHolderStrategy) + .map(SecurityContextHolderStrategy::getContext) .map(SecurityContext::getAuthentication) .filter(auth -> !(auth instanceof AnonymousAuthenticationToken)); } diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/SpringAccessPathChecker.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/SpringAccessPathChecker.java index e6a9e81cc78..ae09579ff9b 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/SpringAccessPathChecker.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/SpringAccessPathChecker.java @@ -21,6 +21,7 @@ import java.util.function.Predicate; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer; import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -100,6 +101,7 @@ */ public class SpringAccessPathChecker implements AccessPathChecker { + private final transient SecurityContextHolderStrategy securityContextHolderStrategy; private final transient WebInvocationPrivilegeEvaluator evaluator; private final String urlMapping; @@ -109,7 +111,10 @@ public class SpringAccessPathChecker implements AccessPathChecker { * * @param evaluator * evaluator to check path permissions. + * @deprecated Use + * {@link #SpringAccessPathChecker(WebInvocationPrivilegeEvaluator, String)} */ + @Deprecated(since = "25.0", forRemoval = true) public SpringAccessPathChecker(WebInvocationPrivilegeEvaluator evaluator) { this(evaluator, null); } @@ -125,11 +130,53 @@ public SpringAccessPathChecker(WebInvocationPrivilegeEvaluator evaluator) { * evaluator to check path permissions. * @param urlMapping * Vaadin servlet url mapping + * @deprecated Use + * {@link #SpringAccessPathChecker(SecurityContextHolderStrategy, WebInvocationPrivilegeEvaluator, String)} */ + @Deprecated(since = "25.0", forRemoval = true) public SpringAccessPathChecker(WebInvocationPrivilegeEvaluator evaluator, String urlMapping) { - this.urlMapping = urlMapping; + this(SecurityContextHolder.getContextHolderStrategy(), evaluator, + urlMapping); + } + + /** + * Creates a new instance that uses the given + * {@link SecurityContextHolderStrategy} to get the security context and + * {@link WebInvocationPrivilegeEvaluator} to check path permissions. + * + * @param securityContextHolderStrategy + * strategy to get the security context + * @param evaluator + * evaluator to check path permissions + */ + public SpringAccessPathChecker( + SecurityContextHolderStrategy securityContextHolderStrategy, + WebInvocationPrivilegeEvaluator evaluator) { + this(securityContextHolderStrategy, evaluator, null); + } + + /** + * Creates a new instance that uses the given + * {@link SecurityContextHolderStrategy} to get the security context and + * {@link WebInvocationPrivilegeEvaluator} to check path permissions. + *

+ * It applies the given Vaadin servlet url mapping to the input path before + * delegating the check to the evaluator. + * + * @param securityContextHolderStrategy + * strategy to get the security context + * @param evaluator + * evaluator to check path permissions + * @param urlMapping + * Vaadin servlet url mapping + */ + public SpringAccessPathChecker( + SecurityContextHolderStrategy securityContextHolderStrategy, + WebInvocationPrivilegeEvaluator evaluator, String urlMapping) { + this.securityContextHolderStrategy = securityContextHolderStrategy; this.evaluator = evaluator; + this.urlMapping = urlMapping; } @Override @@ -137,7 +184,7 @@ public boolean hasAccess(String path, Principal principal, Predicate roleChecker) { path = RequestUtil.applyUrlMapping(urlMapping, path); return evaluator.isAllowed(path, - SecurityContextHolder.getContext().getAuthentication()); + securityContextHolderStrategy.getContext().getAuthentication()); } /** diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinAwareSecurityContextHolderStrategyConfiguration.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinAwareSecurityContextHolderStrategyConfiguration.java deleted file mode 100644 index c4a1165d9eb..00000000000 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinAwareSecurityContextHolderStrategyConfiguration.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2000-2025 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.flow.spring.security; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolderStrategy; - -/** - * Provides configuration of Vaadin aware {@link SecurityContextHolderStrategy} - */ -@Configuration -public class VaadinAwareSecurityContextHolderStrategyConfiguration { - - /** - * Registers {@link SecurityContextHolderStrategy} bean. - *

- * Beans of this type will automatically be used by - * {@link org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration} - * to configure the current {@link SecurityContextHolderStrategy}. - * - * @return the Vaadin aware security context holder strategy - */ - @Bean(name = "VaadinSecurityContextHolderStrategy") - @ConditionalOnMissingBean - public VaadinAwareSecurityContextHolderStrategy securityContextHolderStrategy() { - VaadinAwareSecurityContextHolderStrategy vaadinAwareSecurityContextHolderStrategy = new VaadinAwareSecurityContextHolderStrategy(); - // Use a security context holder that can find the context from Vaadin - // specific classes - SecurityContextHolder.setContextHolderStrategy( - vaadinAwareSecurityContextHolderStrategy); - return vaadinAwareSecurityContextHolderStrategy; - } - -} diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurer.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurer.java index 115751fd303..2e82d597719 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurer.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurer.java @@ -42,12 +42,14 @@ import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer; import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.DelegatingAccessDeniedHandler; import org.springframework.security.web.access.RequestMatcherDelegatingAccessDeniedHandler; +import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.logout.LogoutFilter; @@ -749,6 +751,12 @@ public O postProcess(O filter) { getAuthenticationContext().setLogoutHandlers( configurer.getLogoutSuccessHandler(), configurer.getLogoutHandlers()); + var securityContextHolderStrategy = getSharedObjectOrBean( + SecurityContextHolderStrategy.class); + if (securityContextHolderStrategy != null) { + filter.setSecurityContextHolderStrategy( + securityContextHolderStrategy); + } return filter; } }; @@ -845,6 +853,21 @@ private void customizeAuthorizeHttpRequests( registry.requestMatchers(toRequestPrincipalAwareMatcher( getRequestUtil()::isEndpointRequest)).authenticated(); } + registry.requestMatchers(defaultPermitMatcher()).permitAll(); + registry.withObjectPostProcessor( + new ObjectPostProcessor() { + @Override + public O postProcess( + O filter) { + var securityContextHolderStrategy = getSharedObjectOrBean( + SecurityContextHolderStrategy.class); + if (securityContextHolderStrategy != null) { + filter.setSecurityContextHolderStrategy( + securityContextHolderStrategy); + } + return filter; + } + }); } private ApplicationContext getApplicationContext() { diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/stateless/VaadinStatelessSecurityConfigurer.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/stateless/VaadinStatelessSecurityConfigurer.java index bc7255c58ae..82fb6fc3107 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/stateless/VaadinStatelessSecurityConfigurer.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/stateless/VaadinStatelessSecurityConfigurer.java @@ -29,6 +29,7 @@ import com.nimbusds.jose.jwk.OctetSequenceKey; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; +import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.config.Customizer; @@ -37,6 +38,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; @@ -189,8 +191,10 @@ public void configure(H http) { trustResolver = new AuthenticationTrustResolverImpl(); } jwtSecurityContextRepository.setTrustResolver(trustResolver); + http.addFilterBefore( - new UpdateJwtCookiesFilter(jwtSecurityContextRepository), + new UpdateJwtCookiesFilter(jwtSecurityContextRepository, + getSecurityContextHolderStrategy(http)), HeaderWriterFilter.class); } @@ -199,7 +203,22 @@ public void configure(H http) { ((VaadinDefaultRequestCache) requestCache) .setDelegateRequestCache(new CookieRequestCache()); } + } + private SecurityContextHolderStrategy getSecurityContextHolderStrategy( + H http) { + SecurityContextHolderStrategy securityContextHolderStrategy = http + .getSharedObject(SecurityContextHolderStrategy.class); + if (securityContextHolderStrategy == null) { + var provider = http.getSharedObject(ApplicationContext.class) + .getBeanProvider(SecurityContextHolderStrategy.class); + securityContextHolderStrategy = provider.getIfAvailable(); + } + if (securityContextHolderStrategy == null) { + securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + } + return securityContextHolderStrategy; } /** @@ -326,10 +345,13 @@ private static final class UpdateJwtCookiesFilter extends OncePerRequestFilter { private final JwtSecurityContextRepository jwtSecurityContextRepository; + private final SecurityContextHolderStrategy securityContextHolderStrategy; private UpdateJwtCookiesFilter( - JwtSecurityContextRepository jwtSecurityContextRepository) { + JwtSecurityContextRepository jwtSecurityContextRepository, + SecurityContextHolderStrategy securityContextHolderStrategy) { this.jwtSecurityContextRepository = jwtSecurityContextRepository; + this.securityContextHolderStrategy = securityContextHolderStrategy; } @Override @@ -337,7 +359,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { UpdateJWTCookieOnCommitResponseWrapper responseWrapper = new UpdateJWTCookieOnCommitResponseWrapper( - request, response, jwtSecurityContextRepository); + request, response, jwtSecurityContextRepository, + securityContextHolderStrategy); try { filterChain.doFilter(request, responseWrapper); } finally { @@ -354,12 +377,16 @@ private static final class UpdateJWTCookieOnCommitResponseWrapper private final HttpServletRequest request; + private final SecurityContextHolderStrategy securityContextHolderStrategy; + UpdateJWTCookieOnCommitResponseWrapper(HttpServletRequest request, HttpServletResponse response, - JwtSecurityContextRepository jwtSecurityContextRepository) { + JwtSecurityContextRepository jwtSecurityContextRepository, + SecurityContextHolderStrategy securityContextHolderStrategy) { super(response); this.request = request; this.jwtSecurityContextRepository = jwtSecurityContextRepository; + this.securityContextHolderStrategy = securityContextHolderStrategy; } @Override @@ -372,8 +399,8 @@ private void writeCookies() { if (isDisableOnResponseCommitted()) { return; } - org.springframework.security.core.context.SecurityContext context = SecurityContextHolder - .getContextHolderStrategy().getContext(); + org.springframework.security.core.context.SecurityContext context = securityContextHolderStrategy + .getContext(); if (context != null && !SerializedJwtSplitCookieRepository .containsCookie(this)) { jwtSecurityContextRepository.saveContext(context, request, diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java index a5fc8f9a593..feaff61b527 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java @@ -104,7 +104,6 @@ protected Stream getExcludedPatterns() { "com\\.vaadin\\.flow\\.spring\\.security\\.AuthenticationContext", "com\\.vaadin\\.flow\\.spring\\.security\\.NavigationAccessControlConfigurer", "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinAwareSecurityContextHolderStrategy", - "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinAwareSecurityContextHolderStrategyConfiguration", "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinSecurityConfigurer", "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinSecurityConfigurer(\\$.*)?", "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinDefaultRequestCache", diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringSecurityAutoConfigurationTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringSecurityAutoConfigurationTest.java index ead3d5640a7..a3c0c3a3d95 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringSecurityAutoConfigurationTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringSecurityAutoConfigurationTest.java @@ -33,7 +33,6 @@ import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -53,7 +52,6 @@ import com.vaadin.flow.spring.security.NavigationAccessControlConfigurer; import com.vaadin.flow.spring.security.SpringAccessPathChecker; import com.vaadin.flow.spring.security.SpringNavigationAccessControl; -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; import com.vaadin.flow.spring.security.VaadinSecurityConfigurer; import static org.assertj.core.api.Assertions.assertThat; @@ -250,7 +248,6 @@ NavigationAccessControlConfigurer navigationAccessCheckersConfigurer() { } @EnableWebSecurity - @Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) private static class BaseSecurityClass { @Bean diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/AuthenticationContextTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/AuthenticationContextTest.java index 1853e3dfd14..bb0b7a9ebc8 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/AuthenticationContextTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/AuthenticationContextTest.java @@ -23,12 +23,15 @@ import java.util.Optional; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration; @@ -36,7 +39,7 @@ import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; @@ -65,9 +68,19 @@ @RunWith(SpringRunner.class) @ContextConfiguration(classes = { ObjectPostProcessorConfiguration.class, + AuthenticationContextTest.AuthenticationContextTestConfiguration.class, VaadinSecurityConfigurerTest.TestConfig.class }) public class AuthenticationContextTest { + @TestConfiguration + static class AuthenticationContextTestConfiguration { + + @Bean + SecurityContextHolderStrategy vaadinAwareSecurityContextHolderStrategy() { + return new VaadinAwareSecurityContextHolderStrategy(); + } + } + @Autowired ObjectPostProcessor postProcessor; @@ -77,7 +90,15 @@ public class AuthenticationContextTest { @Autowired ApplicationContext appCtx; - private final AuthenticationContext authContext = new AuthenticationContext(); + @Autowired + SecurityContextHolderStrategy securityContextHolderStrategy; + + private AuthenticationContext authContext; + + @Before + public void setUp() { + authContext = new AuthenticationContext(securityContextHolderStrategy); + } @Test public void isAuthenticated_notAuthenticated_false() { @@ -512,8 +533,8 @@ public void then( } private SetupForLogoutTest getSetupForLogoutTest() { - Authentication authentication = SecurityContextHolder.getContext() - .getAuthentication(); + Authentication authentication = securityContextHolderStrategy + .getContext().getAuthentication(); LogoutSuccessHandler successHandler = Mockito .mock(LogoutSuccessHandler.class); @@ -560,7 +581,8 @@ public void applySecurityConfiguration_logoutHandlerConfigured() .logout(cfg -> cfg.logoutSuccessHandler(logoutSuccessHandler) .addLogoutHandler(handler1).addLogoutHandler(handler2)); httpSecurity.build(); - AuthenticationContext authCtx = new AuthenticationContext(); + AuthenticationContext authCtx = new AuthenticationContext( + securityContextHolderStrategy); AuthenticationContext.applySecurityConfiguration(httpSecurity, authCtx); Assert.assertNotNull(authCtx.getLogoutSuccessHandler()); @@ -589,7 +611,8 @@ public void applySecurityConfiguration_unbuiltHttpSecurity_throws() .logout(cfg -> cfg.logoutSuccessHandler(logoutSuccessHandler) .addLogoutHandler(handler1).addLogoutHandler(handler2)); - AuthenticationContext authCtx = new AuthenticationContext(); + AuthenticationContext authCtx = new AuthenticationContext( + securityContextHolderStrategy); IllegalStateException exception = Assert.assertThrows( IllegalStateException.class, () -> AuthenticationContext @@ -602,7 +625,8 @@ public void applySecurityConfiguration_unbuiltHttpSecurity_throws() @WithMockUser(authorities = { "FOO_USER", "FOO_ADMIN" }) public void supportsCustomRolePrefixes() { var prefixHolder = new VaadinRolePrefixHolder("FOO_"); - var authContext = new AuthenticationContext(); + var authContext = new AuthenticationContext( + securityContextHolderStrategy); authContext.setRolePrefixHolder(prefixHolder); Assert.assertTrue(authContext.hasAnyRole("USER", "ADMIN")); Assert.assertTrue(authContext.hasAllRoles("USER", "ADMIN")); diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/SpringAccessPathCheckerTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/SpringAccessPathCheckerTest.java index 596f7c3c257..17f2667dabc 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/SpringAccessPathCheckerTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/SpringAccessPathCheckerTest.java @@ -24,7 +24,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.SecurityFilterChain; @@ -47,6 +47,9 @@ class SpringAccessPathCheckerTest { @Autowired private SpringAccessPathChecker accessPathChecker; + @Autowired + private SecurityContextHolderStrategy securityContextHolderStrategy; + @Test @WithAnonymousUser void checkAccess_anonymous() { @@ -140,7 +143,7 @@ void checkAccess_adminAndGuest() { } private boolean checkAccess(String admin) { - Principal principal = SecurityContextHolder.getContext() + Principal principal = securityContextHolderStrategy.getContext() .getAuthentication(); Function roleChecker = AuthenticationUtil .getSecurityHolderRoleChecker(); @@ -152,10 +155,17 @@ private boolean checkAccess(String admin) { @EnableWebSecurity public static class TestConfig { + @Bean + SecurityContextHolderStrategy vaadinAwareSecurityContextHolderStrategy() { + return new VaadinAwareSecurityContextHolderStrategy(); + } + @Bean SpringAccessPathChecker rootPathAccessChecker( + SecurityContextHolderStrategy securityContextHolderStrategy, WebInvocationPrivilegeEvaluator evaluator) { - return new SpringAccessPathChecker(evaluator); + return new SpringAccessPathChecker(securityContextHolderStrategy, + evaluator); } @Bean diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/UrlMappingSpringAccessPathCheckerTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/UrlMappingSpringAccessPathCheckerTest.java index 81ead8115f1..ee07d3116d0 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/UrlMappingSpringAccessPathCheckerTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/UrlMappingSpringAccessPathCheckerTest.java @@ -24,7 +24,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.SecurityFilterChain; @@ -47,6 +47,9 @@ class UrlMappingSpringAccessPathCheckerTest { @Autowired private SpringAccessPathChecker accessPathChecker; + @Autowired + private SecurityContextHolderStrategy securityContextHolderStrategy; + @Test @WithAnonymousUser void checkAccess_anonymous() { @@ -140,7 +143,7 @@ void checkAccess_adminAndGuest() { } private boolean checkAccess(String admin) { - Principal principal = SecurityContextHolder.getContext() + Principal principal = securityContextHolderStrategy.getContext() .getAuthentication(); Function roleChecker = AuthenticationUtil .getSecurityHolderRoleChecker(); @@ -152,10 +155,17 @@ private boolean checkAccess(String admin) { @EnableWebSecurity public static class TestConfig { + @Bean + SecurityContextHolderStrategy vaadinAwareSecurityContextHolderStrategy() { + return new VaadinAwareSecurityContextHolderStrategy(); + } + @Bean SpringAccessPathChecker urlMappingPpathAccessChecker( + SecurityContextHolderStrategy securityContextHolderStrategy, WebInvocationPrivilegeEvaluator evaluator) { - return new SpringAccessPathChecker(evaluator, "/url-mapping/*"); + return new SpringAccessPathChecker(securityContextHolderStrategy, + evaluator, "/url-mapping/*"); } @Bean diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/UserPrincipalRequestMatcherSpringAccessPathCheckerTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/UserPrincipalRequestMatcherSpringAccessPathCheckerTest.java index 95eeae2900a..e5f2ea1dc2c 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/UserPrincipalRequestMatcherSpringAccessPathCheckerTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/UserPrincipalRequestMatcherSpringAccessPathCheckerTest.java @@ -27,6 +27,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.SecurityFilterChain; @@ -70,14 +71,23 @@ private boolean checkAccess(String path) { return accessPathChecker.hasAccess(path, principal, roleChecker::apply); } - @Configuration + @Configuration(proxyBeanMethods = false) @EnableWebSecurity public static class TestConfig { + @Bean + SecurityContextHolderStrategy vaadinAwareSecurityContextHolderStrategy() { + VaadinAwareSecurityContextHolderStrategy strategy = new VaadinAwareSecurityContextHolderStrategy(); + AuthenticationUtil.setSecurityContextSupplier(strategy::getContext); + return strategy; + } + @Bean SpringAccessPathChecker urlMappingPathAccessChecker( + SecurityContextHolderStrategy securityContextHolderStrategy, WebInvocationPrivilegeEvaluator evaluator) { - return new SpringAccessPathChecker(evaluator); + return new SpringAccessPathChecker(securityContextHolderStrategy, + evaluator); } /** diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurerTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurerTest.java index b88b51738de..f6c04c20636 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurerTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinSecurityConfigurerTest.java @@ -17,11 +17,13 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -51,8 +53,11 @@ import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer; import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer; +import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; import org.springframework.security.web.access.ExceptionTranslationFilter; @@ -61,6 +66,7 @@ import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; @@ -74,7 +80,10 @@ import com.vaadin.flow.internal.hilla.EndpointRequestUtil; import com.vaadin.flow.internal.hilla.FileRouterRequestUtil; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.VaadinSession; +import com.vaadin.flow.server.WrappedHttpSession; import com.vaadin.flow.server.auth.NavigationAccessControl; +import com.vaadin.flow.spring.AuthenticationUtil; import com.vaadin.flow.spring.SpringBootAutoConfiguration; import com.vaadin.flow.spring.SpringSecurityAutoConfiguration; @@ -83,6 +92,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @WebAppConfiguration @ContextConfiguration(classes = { SpringBootAutoConfiguration.class, @@ -114,6 +124,9 @@ class VaadinSecurityConfigurerTest { private HttpSecurity http; + @Autowired + private SecurityContextHolderStrategy securityContextHolderStrategy; + private VaadinSecurityConfigurer configurer; @MockitoBean @@ -124,20 +137,50 @@ class VaadinSecurityConfigurerTest { @BeforeEach void setUp() { + SecurityContextHolder.clearContext(); + Assertions.assertInstanceOf( + VaadinAwareSecurityContextHolderStrategy.class, + securityContextHolderStrategy); var authManagerBuilder = new AuthenticationManagerBuilder(postProcessor) .authenticationProvider(new TestingAuthenticationProvider()); http = new HttpSecurity(postProcessor, authManagerBuilder, Map.of(ApplicationContext.class, applicationContext, PathPatternRequestMatcher.Builder.class, - requestMatcherBuilder)); + requestMatcherBuilder, + SecurityContextHolderStrategy.class, + securityContextHolderStrategy)); configurer = VaadinSecurityConfigurer.vaadin(); } @AfterEach void tearDown() { + securityContextHolderStrategy.clearContext(); SecurityContextHolder.clearContext(); } + @Test + void authenticationUtil_securityContextHolderInjected() { + Authentication authentication = new TestingAuthenticationToken("user", + new Object(), "ROLE_USER"); + SecurityContext securityContext = Mockito.mock(SecurityContext.class); + when(securityContext.getAuthentication()).thenReturn(authentication); + HttpSession httpSession = Mockito.mock(HttpSession.class); + VaadinSession vaadinSession = Mockito.mock(VaadinSession.class); + Mockito.when(vaadinSession.getSession()) + .thenReturn(new WrappedHttpSession(httpSession)); + when(httpSession.getAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)) + .thenReturn(securityContext); + + VaadinSession.setCurrent(vaadinSession); + try { + Assertions.assertSame(authentication, + AuthenticationUtil.getSecurityHolderAuthentication()); + } finally { + VaadinSession.setCurrent(null); + } + } + @Test void withDefaults_chainHasDefaultFilters() throws Exception { var filters = http.with(configurer, Customizer.withDefaults()).build() @@ -183,7 +226,7 @@ void oauth2LoginPage_chainHasAuthenticationFilter() throws Exception { void logoutSuccessHandler_handlerIsConfigured( @Mock LogoutSuccessHandler handler) throws Exception { var auth = new UsernamePasswordAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(auth); + securityContextHolderStrategy.getContext().setAuthentication(auth); var request = new MockHttpServletRequest("POST", "/logout"); request.setPathInfo("/logout"); @@ -202,7 +245,7 @@ void logoutSuccessHandler_handlerIsConfigured( void addLogoutHandler_handlerIsAdded(@Mock LogoutHandler handler) throws Exception { var auth = new UsernamePasswordAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(auth); + securityContextHolderStrategy.getContext().setAuthentication(auth); var request = new MockHttpServletRequest("POST", "/logout"); request.setPathInfo("/logout"); @@ -221,7 +264,7 @@ void addLogoutHandler_handlerIsAdded(@Mock LogoutHandler handler) void anyRequest_authorizeRuleIsConfigured() throws Exception { var auth = new AnonymousAuthenticationToken("key", "user", List.of(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); - SecurityContextHolder.getContext().setAuthentication(auth); + securityContextHolderStrategy.getContext().setAuthentication(auth); var request = new MockHttpServletRequest("GET", "/any"); request.setPathInfo("/any"); @@ -314,7 +357,7 @@ void hillaAnonymousEndpointRequest_arePermitted() throws Exception { var auth = new AnonymousAuthenticationToken("key", "user", List.of(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); - SecurityContextHolder.getContext().setAuthentication(auth); + securityContextHolderStrategy.getContext().setAuthentication(auth); var path = "/connect/HillaEndpoint/anonymous"; var request = new MockHttpServletRequest("POST", path); request.setPathInfo(path); @@ -344,7 +387,7 @@ void hillaEndpointRequest_areAuthenticated() throws Exception { var auth = new TestingAuthenticationToken("user", "password", List.of(new SimpleGrantedAuthority("ROLE_USER"))); - SecurityContextHolder.getContext().setAuthentication(auth); + securityContextHolderStrategy.getContext().setAuthentication(auth); var path = "/connect/HillaEndpoint/authenticated"; var request = new MockHttpServletRequest("POST", path); request.setPathInfo(path); @@ -374,7 +417,7 @@ void hilla_checkAllowedRoutes() throws Exception { var auth = new TestingAuthenticationToken("user", "password", List.of(new SimpleGrantedAuthority("ROLE_USER"))); - SecurityContextHolder.getContext().setAuthentication(auth); + securityContextHolderStrategy.getContext().setAuthentication(auth); var path = "/hilla-view"; var request = new MockHttpServletRequest("POST", path); request.setPathInfo(path); diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/stateless/JwtStatelessAuthenticationTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/stateless/JwtStatelessAuthenticationTest.java index 93d33b0949a..d845425a45d 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/stateless/JwtStatelessAuthenticationTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/stateless/JwtStatelessAuthenticationTest.java @@ -64,7 +64,6 @@ import com.vaadin.flow.spring.SpringBootAutoConfiguration; import com.vaadin.flow.spring.SpringSecurityAutoConfiguration; import com.vaadin.flow.spring.security.RequestUtil; -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin; import static org.hamcrest.Matchers.greaterThan; @@ -242,8 +241,7 @@ private static void assertCookiesUpdated(MvcResult result, Cookie jwtCookie, @TestConfiguration @EnableWebSecurity - @Import({ FakeController.class, - VaadinAwareSecurityContextHolderStrategyConfiguration.class }) + @Import(FakeController.class) public static class SecurityConfig { @Bean("VaadinSecurityFilterChainBean")