From ea623f713d123e17ff5cf48cab832c7e2c6a089e Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Tue, 9 Aug 2022 15:51:19 +0200 Subject: [PATCH 1/9] feat: Introduced component-based security configuration for Spring --- .../spring/flowsecurity/SecurityConfig.java | 81 ++-- .../spring/security/VaadinWebSecurity.java | 434 ++++++++++++++++++ .../VaadinWebSecurityConfigurerAdapter.java | 3 +- 3 files changed, 482 insertions(+), 36 deletions(-) create mode 100644 vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java 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 38a44d03300..76cc7c9bb24 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 @@ -1,32 +1,38 @@ package com.vaadin.flow.spring.flowsecurity; -import java.util.stream.Collectors; - import javax.servlet.ServletContext; -import com.vaadin.flow.spring.RootMappedCondition; -import com.vaadin.flow.spring.VaadinConfigurationProperties; -import com.vaadin.flow.spring.flowsecurity.data.UserInfo; -import com.vaadin.flow.spring.flowsecurity.data.UserInfoRepository; -import com.vaadin.flow.spring.flowsecurity.views.LoginView; -import com.vaadin.flow.spring.security.VaadinWebSecurityConfigurerAdapter; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.ldap.EmbeddedLdapServerContextSourceFactoryBean; +import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import com.vaadin.flow.spring.RootMappedCondition; +import com.vaadin.flow.spring.VaadinConfigurationProperties; +import com.vaadin.flow.spring.flowsecurity.data.UserInfo; +import com.vaadin.flow.spring.flowsecurity.data.UserInfoRepository; +import com.vaadin.flow.spring.flowsecurity.views.LoginView; +import com.vaadin.flow.spring.security.VaadinWebSecurity; +import com.vaadin.flow.spring.security.VaadinWebSecurityConfigurerAdapter; @EnableWebSecurity @Configuration -public class SecurityConfig extends VaadinWebSecurityConfigurerAdapter { +public class SecurityConfig extends VaadinWebSecurity { public static String ROLE_USER = "user"; public static String ROLE_ADMIN = "admin"; @@ -59,39 +65,44 @@ public String getLogoutSuccessUrl() { return logoutSuccessUrl; } + @Bean @Override - protected void configure(HttpSecurity http) throws Exception { - // Admin only access for given resources + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/admin-only/**") .hasAnyRole(ROLE_ADMIN); - - super.configure(http); - setLoginView(http, LoginView.class, getLogoutSuccessUrl()); + return super.filterChain(http); } + @Bean @Override - public void configure(WebSecurity web) throws Exception { - super.configure(web); - web.ignoring().antMatchers("/public/**"); + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> { + super.webSecurityCustomizer().customize(web); + web.ignoring().antMatchers("/public/**"); + }; } - @Override - protected void configure(AuthenticationManagerBuilder auth) - throws Exception { - auth.userDetailsService(username -> { - UserInfo userInfo = userInfoRepository.findByUsername(username); - if (userInfo == null) { - throw new UsernameNotFoundException( - "No user present with username: " + username); - } else { - return new User(userInfo.getUsername(), - userInfo.getEncodedPassword(), - userInfo.getRoles().stream() - .map(role -> new SimpleGrantedAuthority( - "ROLE_" + role)) - .collect(Collectors.toList())); + @Bean + public InMemoryUserDetailsManager userDetailsService() { + return new InMemoryUserDetailsManager() { + @Override + public UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException { + UserInfo userInfo = userInfoRepository.findByUsername(username); + if (userInfo == null) { + throw new UsernameNotFoundException( + "No user present with username: " + username); + } else { + return new User(userInfo.getUsername(), + userInfo.getEncodedPassword(), + userInfo.getRoles().stream() + .map(role -> new SimpleGrantedAuthority( + "ROLE_" + role)) + .collect(Collectors.toList())); + } } - }); + }; } + } diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java new file mode 100644 index 00000000000..27cfe646922 --- /dev/null +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java @@ -0,0 +1,434 @@ +/* + * Copyright 2000-2021 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 javax.crypto.SecretKey; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jose.jws.MacAlgorithm; +import org.springframework.security.web.SecurityFilterChain; +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.authentication.HttpStatusEntryPoint; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.csrf.CsrfException; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.AnyRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.internal.AnnotationReader; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.internal.RouteUtil; +import com.vaadin.flow.server.HandlerHelper; +import com.vaadin.flow.server.auth.ViewAccessChecker; +import com.vaadin.flow.spring.security.stateless.VaadinStatelessSecurityConfigurer; + +/** + * Provides basic Vaadin component-based security configuration for the project. + *

+ * Sets up security rules for a Vaadin application and restricts all URLs except + * for public resources and internal Vaadin URLs to authenticated user. + *

+ * The default behavior can be altered by extending the public/protected methods + * in the class. + *

+ * To use this, create your own web security class by extending this class and + * annotate it with @EnableWebSecurity and + * @Configuration. + *

+ * For example +@EnableWebSecurity +@Configuration +public class MyWebSecurity extends VaadinWebSecurity { + +} + * + */ +public abstract class VaadinWebSecurity { + + @Autowired + private VaadinDefaultRequestCache vaadinDefaultRequestCache; + + @Autowired + private RequestUtil requestUtil; + + @Autowired + private ViewAccessChecker viewAccessChecker; + + /** + * The paths listed as "ignoring" in this method are handled without any + * Spring Security involvement. They have no access to any security context + * etc. + *

+ * {@inheritDoc} + */ + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring() + .requestMatchers(getDefaultWebSecurityIgnoreMatcher( + requestUtil.getUrlMapping())); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + // Use a security context holder that can find the context from Vaadin + // specific classes + SecurityContextHolder.setStrategyName( + VaadinAwareSecurityContextHolderStrategy.class.getName()); + + // Respond with 401 Unauthorized HTTP status code for unauthorized + // requests for protected Hilla endpoints, so that the response could + // be handled on the client side using e.g. `InvalidSessionMiddleware`. + http.exceptionHandling() + .accessDeniedHandler(createAccessDeniedHandler()) + .defaultAuthenticationEntryPointFor( + new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), + requestUtil::isEndpointRequest); + + // Vaadin has its own CSRF protection. + // Spring CSRF is not compatible with Vaadin internal requests + http.csrf().ignoringRequestMatchers( + requestUtil::isFrameworkInternalRequest); + + // Ensure automated requests to e.g. closing push channels, service + // workers, + // endpoints are not counted as valid targets to redirect user to on + // login + http.requestCache().requestCache(vaadinDefaultRequestCache); + + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry urlRegistry = http + .authorizeRequests(); + // Vaadin internal requests must always be allowed to allow public Flow + // pages + // and/or login page implemented using Flow. + urlRegistry.requestMatchers(requestUtil::isFrameworkInternalRequest) + .permitAll(); + // Public endpoints are OK to access + urlRegistry.requestMatchers(requestUtil::isAnonymousEndpoint) + .permitAll(); + // Public routes are OK to access + urlRegistry.requestMatchers(requestUtil::isAnonymousRoute).permitAll(); + urlRegistry.requestMatchers(getDefaultHttpSecurityPermitMatcher( + requestUtil.getUrlMapping())).permitAll(); + + // all other requests require authentication + urlRegistry.anyRequest().authenticated(); + + // Enable view access control + viewAccessChecker.enable(); + + return http.build(); + } + + /** + * Matcher for framework internal requests. + * + * Assumes Vaadin servlet to be mapped on root path ({@literal /*}). + * + * @return default {@link HttpSecurity} bypass matcher + */ + public static RequestMatcher getDefaultHttpSecurityPermitMatcher() { + return getDefaultHttpSecurityPermitMatcher("/*"); + } + + /** + * Matcher for framework internal requests, with Vaadin servlet mapped on + * the given path. + * + * @param urlMapping + * url mapping for the Vaadin servlet. + * @return default {@link HttpSecurity} bypass matcher + */ + public static RequestMatcher getDefaultHttpSecurityPermitMatcher( + String urlMapping) { + Objects.requireNonNull(urlMapping, + "Vaadin servlet url mapping is required"); + Stream.Builder paths = Stream.builder(); + Stream.of(HandlerHelper.getPublicResourcesRequiringSecurityContext()) + .map(path -> RequestUtil.applyUrlMapping(urlMapping, path)) + .forEach(paths::add); + + String mappedRoot = RequestUtil.applyUrlMapping(urlMapping, ""); + if (!"/".equals(mappedRoot)) { + // When using an url path, static resources are still fetched from + // /VAADIN/ in the context root + paths.add("/VAADIN/**"); + } + return new OrRequestMatcher(paths.build() + .map(AntPathRequestMatcher::new).collect(Collectors.toList())); + } + + /** + * Matcher for Vaadin static (public) resources. + * + * Assumes Vaadin servlet to be mapped on root path ({@literal /*}). + * + * @return default {@link WebSecurity} ignore matcher + */ + public static RequestMatcher getDefaultWebSecurityIgnoreMatcher() { + return getDefaultWebSecurityIgnoreMatcher("/*"); + } + + /** + * Matcher for Vaadin static (public) resources, with Vaadin servlet mapped + * on the given path. + * + * Assumes Vaadin servlet to be mapped on root path ({@literal /*}). + * + * @param urlMapping + * the url mapping for the Vaadin servlet + * @return default {@link WebSecurity} ignore matcher + */ + public static RequestMatcher getDefaultWebSecurityIgnoreMatcher( + String urlMapping) { + Objects.requireNonNull(urlMapping, + "Vaadin servlet url mapping is required"); + return new OrRequestMatcher(Stream + .of(HandlerHelper.getPublicResources()) + .map(path -> RequestUtil.applyUrlMapping(urlMapping, path)) + .map(AntPathRequestMatcher::new).collect(Collectors.toList())); + } + + /** + * Sets up login for the application using form login with the given path + * for the login view. + *

+ * This is used when your application uses a Hilla based login view + * available at the given path. + * + * @param http + * the http security from {@link #configure(HttpSecurity)} + * @param hillaLoginViewPath + * the path to the login view + * @throws Exception + * if something goes wrong + */ + protected void setLoginView(HttpSecurity http, String hillaLoginViewPath) + throws Exception { + setLoginView(http, hillaLoginViewPath, "/"); + } + + /** + * Sets up login for the application using form login with the given path + * for the login view. + *

+ * This is used when your application uses a Hilla based login view + * available at the given path. + * + * @param http + * the http security from {@link #configure(HttpSecurity)} + * @param hillaLoginViewPath + * the path to the login view + * @param logoutUrl + * the URL to redirect the user to after logging out + * @throws Exception + * if something goes wrong + */ + protected void setLoginView(HttpSecurity http, String hillaLoginViewPath, + String logoutUrl) throws Exception { + hillaLoginViewPath = applyUrlMapping(hillaLoginViewPath); + FormLoginConfigurer formLogin = http.formLogin(); + formLogin.loginPage(hillaLoginViewPath).permitAll(); + formLogin.successHandler( + getVaadinSavedRequestAwareAuthenticationSuccessHandler(http)); + http.logout().logoutSuccessUrl(logoutUrl); + http.exceptionHandling().defaultAuthenticationEntryPointFor( + new LoginUrlAuthenticationEntryPoint(hillaLoginViewPath), + AnyRequestMatcher.INSTANCE); + viewAccessChecker.setLoginView(hillaLoginViewPath); + } + + /** + * Sets up login for the application using the given Flow login view. + * + * @param http + * the http security from {@link #configure(HttpSecurity)} + * @param flowLoginView + * the login view to use + * @throws Exception + * if something goes wrong + */ + protected void setLoginView(HttpSecurity http, + Class flowLoginView) throws Exception { + setLoginView(http, flowLoginView, "/"); + } + + /** + * Sets up login for the application using the given Flow login view. + * + * @param http + * the http security from {@link #configure(HttpSecurity)} + * @param flowLoginView + * the login view to use + * @param logoutUrl + * the URL to redirect the user to after logging out + * + * @throws Exception + * if something goes wrong + */ + protected void setLoginView(HttpSecurity http, + Class flowLoginView, String logoutUrl) + throws Exception { + Optional route = AnnotationReader.getAnnotationFor(flowLoginView, + Route.class); + + if (!route.isPresent()) { + throw new IllegalArgumentException( + "Unable find a @Route annotation on the login view " + + flowLoginView.getName()); + } + + String loginPath = RouteUtil.getRoutePath(flowLoginView, route.get()); + if (!loginPath.startsWith("/")) { + loginPath = "/" + loginPath; + } + loginPath = applyUrlMapping(loginPath); + + // Actually set it up + FormLoginConfigurer formLogin = http.formLogin(); + formLogin.loginPage(loginPath).permitAll(); + formLogin.successHandler( + getVaadinSavedRequestAwareAuthenticationSuccessHandler(http)); + http.csrf().ignoringAntMatchers(loginPath); + http.logout().logoutSuccessUrl(logoutUrl); + http.exceptionHandling().defaultAuthenticationEntryPointFor( + new LoginUrlAuthenticationEntryPoint(loginPath), + AnyRequestMatcher.INSTANCE); + viewAccessChecker.setLoginView(flowLoginView); + } + + /** + * Sets up stateless JWT authentication using cookies. + * + * @param http + * the http security from {@link #configure(HttpSecurity)} + * @param secretKey + * the secret key for encoding and decoding JWTs, must use a + * {@link MacAlgorithm} algorithm name + * @param issuer + * the issuer JWT claim + * @throws Exception + * if something goes wrong + */ + protected void setStatelessAuthentication(HttpSecurity http, + SecretKey secretKey, String issuer) throws Exception { + setStatelessAuthentication(http, secretKey, issuer, 1800L); + } + + /** + * Sets up stateless JWT authentication using cookies. + * + * @param http + * the http security from {@link #configure(HttpSecurity)} + * @param secretKey + * the secret key for encoding and decoding JWTs, must use a + * {@link MacAlgorithm} algorithm name + * @param issuer + * the issuer JWT claim + * @param expiresIn + * lifetime of the JWT and cookies, in seconds + * @throws Exception + * if something goes wrong + */ + protected void setStatelessAuthentication(HttpSecurity http, + SecretKey secretKey, String issuer, long expiresIn) + throws Exception { + VaadinStatelessSecurityConfigurer vaadinStatelessSecurityConfigurer = new VaadinStatelessSecurityConfigurer<>(); + http.apply(vaadinStatelessSecurityConfigurer); + + vaadinStatelessSecurityConfigurer.withSecretKey().secretKey(secretKey) + .and().issuer(issuer).expiresIn(expiresIn); + } + + /** + * Helper method to prepend configured servlet path to the given path. + * + * Path will always be considered as relative to servlet path, even if it + * starts with a slash character. + * + * @param path + * path to be prefixed with servlet path + * @return the input path prepended by servlet path. + */ + protected String applyUrlMapping(String path) { + return requestUtil.applyUrlMapping(path); + } + + private VaadinSavedRequestAwareAuthenticationSuccessHandler getVaadinSavedRequestAwareAuthenticationSuccessHandler( + HttpSecurity http) { + VaadinSavedRequestAwareAuthenticationSuccessHandler vaadinSavedRequestAwareAuthenticationSuccessHandler = new VaadinSavedRequestAwareAuthenticationSuccessHandler(); + vaadinSavedRequestAwareAuthenticationSuccessHandler + .setDefaultTargetUrl(applyUrlMapping("")); + RequestCache requestCache = http.getSharedObject(RequestCache.class); + if (requestCache != null) { + vaadinSavedRequestAwareAuthenticationSuccessHandler + .setRequestCache(requestCache); + } + return vaadinSavedRequestAwareAuthenticationSuccessHandler; + } + + private AccessDeniedHandler createAccessDeniedHandler() { + final AccessDeniedHandler defaultHandler = new AccessDeniedHandlerImpl(); + + final AccessDeniedHandler http401UnauthorizedHandler = new Http401UnauthorizedAccessDeniedHandler(); + + final LinkedHashMap, AccessDeniedHandler> exceptionHandlers = new LinkedHashMap<>(); + exceptionHandlers.put(CsrfException.class, http401UnauthorizedHandler); + + final LinkedHashMap matcherHandlers = new LinkedHashMap<>(); + matcherHandlers.put(requestUtil::isEndpointRequest, + new DelegatingAccessDeniedHandler(exceptionHandlers, + new AccessDeniedHandlerImpl())); + + return new RequestMatcherDelegatingAccessDeniedHandler(matcherHandlers, + defaultHandler); + } + + private static class Http401UnauthorizedAccessDeniedHandler + implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException) + throws IOException, ServletException { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + } + } +} diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurityConfigurerAdapter.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurityConfigurerAdapter.java index 1a361371059..5a654c712de 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurityConfigurerAdapter.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurityConfigurerAdapter.java @@ -19,7 +19,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - import java.io.IOException; import java.util.LinkedHashMap; import java.util.Objects; @@ -78,8 +77,10 @@ public class MySecurityConfigurerAdapter extends VaadinWebSecurityConfigurerAdapter { } + * @deprecated Use component-based security configuration {@link VaadinWebSecurity} * */ +@Deprecated public abstract class VaadinWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { From be619c675c67dd9b4d9f014ca54559f724385705 Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Wed, 10 Aug 2022 08:53:54 +0200 Subject: [PATCH 2/9] test: Additional Spring classes exclusions added to serialization test --- .../com/vaadin/flow/spring/SpringClassesSerializableTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 719241e2d2f..3998ec818a2 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 @@ -90,7 +90,8 @@ protected Stream getExcludedPatterns() { "com\\.vaadin\\.flow\\.spring\\.scopes\\.AbstractScope", "com\\.vaadin\\.flow\\.spring\\.scopes\\.VaadinUIScope", "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinAwareSecurityContextHolderStrategy", - "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinWebSecurityConfigurerAdapter", + "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinWebSecurity", + "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinWebSecurity\\$Http401UnauthorizedAccessDeniedHandler", "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinWebSecurityConfigurerAdapter", "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinWebSecurityConfigurerAdapter\\$Http401UnauthorizedAccessDeniedHandler", "com\\.vaadin\\.flow\\.spring\\.security\\.VaadinDefaultRequestCache", From fe8a0a49f51a01eca3d6669fbd56774e688dd398 Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Wed, 10 Aug 2022 10:45:47 +0200 Subject: [PATCH 3/9] chore: Javadocs update --- .../flow/spring/security/VaadinWebSecurity.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java index 27cfe646922..a07c07893f9 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java @@ -234,7 +234,7 @@ public static RequestMatcher getDefaultWebSecurityIgnoreMatcher( * available at the given path. * * @param http - * the http security from {@link #configure(HttpSecurity)} + * the http security from {@link #filterChain(HttpSecurity)} * @param hillaLoginViewPath * the path to the login view * @throws Exception @@ -253,7 +253,7 @@ protected void setLoginView(HttpSecurity http, String hillaLoginViewPath) * available at the given path. * * @param http - * the http security from {@link #configure(HttpSecurity)} + * the http security from {@link #filterChain(HttpSecurity)} * @param hillaLoginViewPath * the path to the login view * @param logoutUrl @@ -279,7 +279,7 @@ protected void setLoginView(HttpSecurity http, String hillaLoginViewPath, * Sets up login for the application using the given Flow login view. * * @param http - * the http security from {@link #configure(HttpSecurity)} + * the http security from {@link #filterChain(HttpSecurity)} * @param flowLoginView * the login view to use * @throws Exception @@ -294,7 +294,7 @@ protected void setLoginView(HttpSecurity http, * Sets up login for the application using the given Flow login view. * * @param http - * the http security from {@link #configure(HttpSecurity)} + * the http security from {@link #filterChain(HttpSecurity)} * @param flowLoginView * the login view to use * @param logoutUrl @@ -338,7 +338,7 @@ protected void setLoginView(HttpSecurity http, * Sets up stateless JWT authentication using cookies. * * @param http - * the http security from {@link #configure(HttpSecurity)} + * the http security from {@link #filterChain(HttpSecurity)} * @param secretKey * the secret key for encoding and decoding JWTs, must use a * {@link MacAlgorithm} algorithm name @@ -356,7 +356,7 @@ protected void setStatelessAuthentication(HttpSecurity http, * Sets up stateless JWT authentication using cookies. * * @param http - * the http security from {@link #configure(HttpSecurity)} + * the http security from {@link #filterChain(HttpSecurity)} * @param secretKey * the secret key for encoding and decoding JWTs, must use a * {@link MacAlgorithm} algorithm name From 275fddab4c41e7cd06398a96128011a5a83e12a9 Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Fri, 12 Aug 2022 13:08:08 +0200 Subject: [PATCH 4/9] feat: moved getDefaultWebSecurityIgnoreMatcher from WebSecurityCustomizer to SecurityFilterChain Changed approach of registering public resources (from ignoring to permiAll). Fixes: #13868 --- .../spring/flowsecurity/SecurityConfig.java | 15 +------------- .../spring/security/VaadinWebSecurity.java | 20 +++++-------------- 2 files changed, 6 insertions(+), 29 deletions(-) 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 76cc7c9bb24..6ff99f288c4 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 @@ -7,12 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; -import org.springframework.security.config.ldap.EmbeddedLdapServerContextSourceFactoryBean; -import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; @@ -28,7 +24,6 @@ import com.vaadin.flow.spring.flowsecurity.data.UserInfoRepository; import com.vaadin.flow.spring.flowsecurity.views.LoginView; import com.vaadin.flow.spring.security.VaadinWebSecurity; -import com.vaadin.flow.spring.security.VaadinWebSecurityConfigurerAdapter; @EnableWebSecurity @Configuration @@ -70,19 +65,11 @@ public String getLogoutSuccessUrl() { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/admin-only/**") .hasAnyRole(ROLE_ADMIN); + http.authorizeRequests().antMatchers("/public/**").permitAll(); setLoginView(http, LoginView.class, getLogoutSuccessUrl()); return super.filterChain(http); } - @Bean - @Override - public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> { - super.webSecurityCustomizer().customize(web); - web.ignoring().antMatchers("/public/**"); - }; - } - @Bean public InMemoryUserDetailsManager userDetailsService() { return new InMemoryUserDetailsManager() { diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java index a07c07893f9..3aa6a4c75f6 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java @@ -32,7 +32,6 @@ import org.springframework.security.access.AccessDeniedException; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.core.context.SecurityContextHolder; @@ -91,20 +90,6 @@ public abstract class VaadinWebSecurity { @Autowired private ViewAccessChecker viewAccessChecker; - /** - * The paths listed as "ignoring" in this method are handled without any - * Spring Security involvement. They have no access to any security context - * etc. - *

- * {@inheritDoc} - */ - @Bean - public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring() - .requestMatchers(getDefaultWebSecurityIgnoreMatcher( - requestUtil.getUrlMapping())); - } - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -148,6 +133,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { urlRegistry.requestMatchers(getDefaultHttpSecurityPermitMatcher( requestUtil.getUrlMapping())).permitAll(); + // matcher for Vaadin static (public) resources + urlRegistry.requestMatchers( + getDefaultWebSecurityIgnoreMatcher(requestUtil.getUrlMapping())) + .permitAll(); + // all other requests require authentication urlRegistry.anyRequest().authenticated(); From fdae6990ec803ce2c818e5ad3cbf02e0d2097e9e Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Fri, 12 Aug 2022 14:48:01 +0200 Subject: [PATCH 5/9] Update vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java Co-authored-by: Marco Collovati --- .../java/com/vaadin/flow/spring/security/VaadinWebSecurity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java index 3aa6a4c75f6..d313e1bb837 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2021 Vaadin Ltd. + * Copyright 2000-2022 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 From 7fa7a3b6d4c8e49f6382aa4f7915ae74cd86100f Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Tue, 16 Aug 2022 10:44:44 +0200 Subject: [PATCH 6/9] Implementation update to ease migration --- .../spring/flowsecurity/SecurityConfig.java | 6 +-- .../spring/security/VaadinWebSecurity.java | 43 ++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) 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 6ff99f288c4..6a2caa65425 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 @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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; @@ -60,14 +61,13 @@ public String getLogoutSuccessUrl() { return logoutSuccessUrl; } - @Bean @Override - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + public void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/admin-only/**") .hasAnyRole(ROLE_ADMIN); http.authorizeRequests().antMatchers("/public/**").permitAll(); + super.configure(http); setLoginView(http, LoginView.class, getLogoutSuccessUrl()); - return super.filterChain(http); } @Bean diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java index d313e1bb837..479b0f29787 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java @@ -30,12 +30,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; @@ -67,6 +71,9 @@ * The default behavior can be altered by extending the public/protected methods * in the class. *

+ * Provides default bean implementations for {@link SecurityFilterChain} and + * {@link WebSecurityCustomizer}. + *

* To use this, create your own web security class by extending this class and * annotate it with @EnableWebSecurity and * @Configuration. @@ -90,9 +97,20 @@ public abstract class VaadinWebSecurity { @Autowired private ViewAccessChecker viewAccessChecker; - @Bean + /** + * Registers default {@link SecurityFilterChain} bean. + *

+ * Defines a filter chain which is capable of being matched against an + * {@code HttpServletRequest}. in order to decide whether it applies to that + * request. + */ + @Bean(name = "VaadinSecurityFilterChainBean") public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + configure(http); + return http.build(); + } + protected void configure(HttpSecurity http) throws Exception { // Use a security context holder that can find the context from Vaadin // specific classes SecurityContextHolder.setStrategyName( @@ -143,8 +161,29 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // Enable view access control viewAccessChecker.enable(); + } - return http.build(); + /** + * Registers default {@link WebSecurityCustomizer} bean. + *

+ * Beans of this type will automatically be used by + * {@link WebSecurityConfiguration} to customize {@link WebSecurity}. + *

+ * Default no {@link WebSecurity} customization is performed. + */ + @Bean(name = "VaadinWebSecurityCustomizerBean") + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> { + try { + configure(web); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } + + protected void configure(WebSecurity web) throws Exception { + // no-operation } /** From 5d9385ba255d6f9ffbd928f82570818c8cd1bc49 Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Tue, 16 Aug 2022 12:07:35 +0200 Subject: [PATCH 7/9] javadocs update --- .../flow/spring/security/VaadinWebSecurity.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java index 479b0f29787..8d7e361416f 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java @@ -103,6 +103,9 @@ public abstract class VaadinWebSecurity { * Defines a filter chain which is capable of being matched against an * {@code HttpServletRequest}. in order to decide whether it applies to that * request. + *

+ * {@link HttpSecurity} configuration can be customized by overriding + * {@link VaadinWebSecurity#configure(HttpSecurity)}. */ @Bean(name = "VaadinSecurityFilterChainBean") public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -110,6 +113,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } + /** + * Applies Vaadin default configuration to {@link HttpSecurity}. + * + * Typically, subclasses should call super to apply default Vaadin + * configuration in addition to custom rules. + * + * @param http + * @throws Exception + */ protected void configure(HttpSecurity http) throws Exception { // Use a security context holder that can find the context from Vaadin // specific classes @@ -169,6 +181,9 @@ protected void configure(HttpSecurity http) throws Exception { * Beans of this type will automatically be used by * {@link WebSecurityConfiguration} to customize {@link WebSecurity}. *

+ * {@link WebSecurity} configuration can be customized by overriding + * {@link VaadinWebSecurity#configure(WebSecurity)} + *

* Default no {@link WebSecurity} customization is performed. */ @Bean(name = "VaadinWebSecurityCustomizerBean") From 2a79dfebf8ea9244952d3a95eb7c4984d3b1995b Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Tue, 16 Aug 2022 14:00:42 +0200 Subject: [PATCH 8/9] javadocs update --- .../com/vaadin/flow/spring/security/VaadinWebSecurity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java index 8d7e361416f..c8ec93a7281 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java @@ -119,8 +119,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { * Typically, subclasses should call super to apply default Vaadin * configuration in addition to custom rules. * - * @param http - * @throws Exception + * @param http the {@link HttpSecurity} to modify + * @throws Exception if an error occurs */ protected void configure(HttpSecurity http) throws Exception { // Use a security context holder that can find the context from Vaadin From e12b0b30c071e6f06caa50e6271b029e3d6b2473 Mon Sep 17 00:00:00 2001 From: marcin <106965834+MarcinVaadin@users.noreply.github.com> Date: Tue, 16 Aug 2022 14:31:20 +0200 Subject: [PATCH 9/9] javadocs update - formatter --- .../com/vaadin/flow/spring/security/VaadinWebSecurity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java index c8ec93a7281..d5656e63807 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinWebSecurity.java @@ -119,8 +119,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { * Typically, subclasses should call super to apply default Vaadin * configuration in addition to custom rules. * - * @param http the {@link HttpSecurity} to modify - * @throws Exception if an error occurs + * @param http + * the {@link HttpSecurity} to modify + * @throws Exception + * if an error occurs */ protected void configure(HttpSecurity http) throws Exception { // Use a security context holder that can find the context from Vaadin