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 extends Component> 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 extends Component> 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