diff --git a/articles/building-apps/security/add-login/flow.adoc b/articles/building-apps/security/add-login/flow.adoc index 11101b06f0..c940d43e8e 100644 --- a/articles/building-apps/security/add-login/flow.adoc +++ b/articles/building-apps/security/add-login/flow.adoc @@ -31,7 +31,7 @@ public class LoginView extends Main implements BeforeEnterObserver { private final LoginForm login; public LoginView() { - addClassNames(LumoUtility.Display.FLEX, LumoUtility.JustifyContent.CENTER, + addClassNames(LumoUtility.Display.FLEX, LumoUtility.JustifyContent.CENTER, LumoUtility.AlignItems.CENTER); setSizeFull(); login = new LoginForm(); @@ -65,9 +65,37 @@ If your application's root package is `com.example.application`, place the login To instruct Spring Security to use your login view, modify your security configuration: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { +// tag::snippet[] + configurer.loginView(LoginView.class); +// end::snippet[] + }); + + return http.build(); + } + ... +} +---- + +.`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @@ -83,6 +111,8 @@ class SecurityConfig extends VaadinWebSecurity { } ---- +-- + Now, when a user tries to access the application, they'll be redirected to the login page. [IMPORTANT] @@ -130,9 +160,62 @@ Create a new package: [packagename]`[application package].security` Inside this package, create a [classname]`SecurityConfig` class: -.SecurityConfig.class +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +import com.vaadin.flow.spring.security.VaadinSecurityConfigurer; +import org.slf4j.LoggerFactory; +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.userdetails.User; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.provisioning.UserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(LoginView.class); + }); + + return http.build(); + } + + @Bean + public UserDetailsManager userDetailsManager() { + LoggerFactory.getLogger(SecurityConfig.class) + .warn("Using in-memory user details manager!"); + var user = User.withUsername("user") + .password("{noop}user") + .roles("USER") + .build(); + var admin = User.withUsername("admin") + .password("{noop}admin") + .roles("ADMIN") + .build(); + return new InMemoryUserDetailsManager(user, admin); + } +} +---- + +.`SecurityConfig.java` [source,java] ---- + import com.vaadin.flow.spring.security.VaadinWebSecurity; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; @@ -168,6 +251,9 @@ class SecurityConfig extends VaadinWebSecurity { } } ---- + +-- + ==== @@ -198,8 +284,8 @@ public class LoginView extends Main implements BeforeEnterObserver { private final LoginForm login; public LoginView() { - addClassNames(LumoUtility.Display.FLEX, - LumoUtility.JustifyContent.CENTER, + addClassNames(LumoUtility.Display.FLEX, + LumoUtility.JustifyContent.CENTER, LumoUtility.AlignItems.CENTER); setSizeFull(); login = new LoginForm(); @@ -226,9 +312,37 @@ public class LoginView extends Main implements BeforeEnterObserver { ==== Modify [classname]`SecurityConfig` to reference the `LoginView`: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` [source,java] ---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { +// tag::snippet[] + configurer.loginView(LoginView.class); +// end::snippet[] + }); + + return http.build(); + } + ... +} +---- + +.`SecurityConfig.java` +[source,java] +---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @@ -243,6 +357,8 @@ class SecurityConfig extends VaadinWebSecurity { ... } ---- + +-- ==== diff --git a/articles/building-apps/security/add-login/hilla.adoc b/articles/building-apps/security/add-login/hilla.adoc index 6fcf0cf7ee..06fb1d5c9e 100644 --- a/articles/building-apps/security/add-login/hilla.adoc +++ b/articles/building-apps/security/add-login/hilla.adoc @@ -56,7 +56,7 @@ Vaadin does not provide a built-in user information type, so you need to define [source,java] ---- public record UserInfo( - @NonNull String name, + @NonNull String name, @NonNull Collection authorities ) { } @@ -155,9 +155,37 @@ Spring Security's *form login* mechanism automatically processes authentication To instruct Spring Security to use your login view, modify your security configuration: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` [source,java] ---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { +// tag::snippet[] + configurer.loginView("/login"); +// end::snippet[] + }); + + return http.build(); + } + ... +} +---- + +.`SecurityConfig.java` +[source,java] +---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @@ -173,6 +201,7 @@ class SecurityConfig extends VaadinWebSecurity { } ---- +-- Now, when a user tires to access a protected view, they'll be redirected to the login page. [IMPORTANT] @@ -220,9 +249,62 @@ Create a new package: [packagename]`[application package].security` Inside this package, create a [classname]`SecurityConfig` class: -.SecurityConfig.class +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +import com.vaadin.flow.spring.security.VaadinSecurityConfigurer; +import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; +import org.slf4j.LoggerFactory; +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.userdetails.User; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.provisioning.UserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView("/login"); + }); + + return http.build(); + } + + @Bean + public UserDetailsManager userDetailsManager() { + LoggerFactory.getLogger(SecurityConfig.class) + .warn("Using in-memory user details manager!"); + var user = User.withUsername("user") + .password("{noop}user") + .roles("USER") + .build(); + var admin = User.withUsername("admin") + .password("{noop}admin") + .roles("ADMIN") + .build(); + return new InMemoryUserDetailsManager(user, admin); + } +} +---- + +.`SecurityConfig.java` [source,java] ---- + import com.vaadin.flow.spring.security.VaadinWebSecurity; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; @@ -258,6 +340,8 @@ class SecurityConfig extends VaadinWebSecurity { } } ---- + +-- ==== @@ -274,7 +358,7 @@ Inside this package, create a [recordname]`UserInfo` record: import org.jspecify.annotations.NonNull; import java.util.Collection; -public record UserInfo(@NonNull String name, +public record UserInfo(@NonNull String name, @NonNull Collection authorities) { } @@ -390,9 +474,37 @@ export default function LoginView() { ==== Modify [classname]`SecurityConfig` to reference the new login view: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` [source,java] ---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { +// tag::snippet[] + configurer.loginView("/login"); +// end::snippet[] + }); + + return http.build(); + } + ... +} +---- + +.`SecurityConfig.java` +[source,java] +---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @@ -407,6 +519,8 @@ class SecurityConfig extends VaadinWebSecurity { ... } ---- + +-- ==== diff --git a/articles/building-apps/security/add-login/index.adoc b/articles/building-apps/security/add-login/index.adoc index 391a2845df..baa82f2c3f 100644 --- a/articles/building-apps/security/add-login/index.adoc +++ b/articles/building-apps/security/add-login/index.adoc @@ -56,23 +56,62 @@ It's best practice to create a dedicated package for security-related classes. I This is a minimal implementation of a security configuration class: -.SecurityConfig.class +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class // <1> +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { // <2> + // TODO Configure the login view + }); + return http.build(); + } + + @Bean + public UserDetailsManager userDetailsManager() { + LoggerFactory.getLogger(SecurityConfig.class) + .warn("NOT FOR PRODUCTION: Using in-memory user details manager!"); // <3> + var user = User.withUsername("user") + .password("{noop}user") + .roles("USER") + .build(); + var admin = User.withUsername("admin") + .password("{noop}admin") + .roles("ADMIN") + .build(); + return new InMemoryUserDetailsManager(user, admin); + } +} +---- + +.`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @Override protected void configure(HttpSecurity http) throws Exception { - super.configure(http); // <1> + super.configure(http); // <4> // TODO Configure the login view } @Bean public UserDetailsManager userDetailsManager() { LoggerFactory.getLogger(SecurityConfig.class) - .warn("NOT FOR PRODUCITON: Using in-memory user details manager!"); // <2> + .warn("NOT FOR PRODUCITON: Using in-memory user details manager!"); // <3> var user = User.withUsername("user") .password("{noop}user") .roles("USER") @@ -85,17 +124,21 @@ class SecurityConfig extends VaadinWebSecurity { } } ---- -<1> Always call `super.configure()` -- this ensures that the application is properly configured. -<2> *Tip:* Log a warning message whenever using a configuration that shouldn't end up in production. -The [classname]`VaadinWebSecurity` class provides essential security configurations out of the box, including: +-- +<1> Imports `VaadinAwareSecurityContextHolderStrategyConfiguration`, required for Vaadin security to work with Spring Security (auto-imported with `VaadinWebSecurity`). +<2> Always call with `VaadinSecurityConfigurer.vaadin()` -- this ensures that the application is properly configured. +<3> *Tip:* Log a warning message whenever using a configuration that shouldn't end up in production. +<4> Always call `super.configure()` -- this ensures that the application is properly configured. + +The [classname]`VaadinSecurityConfigurer` class provides essential security configurations out of the box, including: * CSRF protection * Default request caching * Access restriction to Vaadin views and services [NOTE] -If you need to customize security rules, such as allowing anonymous access to static resources, do so _before_ calling `super.configure()`. This is because [classname]`VaadinWebSecurity` applies a *catch-all rule* that requires authentication for _all_ requests. +To customize security rules—such as allowing anonymous access to static resources—in either [classname]`VaadinSecurityConfigurer` or [classname]`VaadinWebSecurity`, adjust the configuration where appropriate: use [method]`securityFilterChain` for `VaadinSecurityConfigurer`, or configure `VaadinWebSecurity` _before_ calling `super.configure()`. Both classes apply a *catch-all rule* requiring authentication for _all_ requests, but when using `VaadinSecurityConfigurer` this rule can be customized or disabled (passing `null`) via `VaadinSecurityConfigurer.anyRequest(...)`. == Create a Login View diff --git a/articles/building-apps/security/add-logout/flow.adoc b/articles/building-apps/security/add-logout/flow.adoc index d906331912..c0e35130da 100644 --- a/articles/building-apps/security/add-logout/flow.adoc +++ b/articles/building-apps/security/add-logout/flow.adoc @@ -40,8 +40,36 @@ public class LogoutView extends Main { By default, users are redirected to the root URL (`/`) after logging out. To change this, *specify a custom logout success URL* in your security configuration: +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) // <1> +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { +// tag::snippet[] + configurer.loginView(LoginView.class, "/logged-out.html"); // <2> +// end::snippet[] + }); + return http.build(); + } + ... +} +---- + +.`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @@ -50,13 +78,16 @@ class SecurityConfig extends VaadinWebSecurity { protected void configure(HttpSecurity http) throws Exception { super.configure(http); // tag::snippet[] - setLoginView(http, LoginView.class, "/logged-out.html"); // <1> + setLoginView(http, LoginView.class, "/logged-out.html"); // <2> // end::snippet[] } ... } ---- -<1> Sets `/logged-out.html` as the *logout success URL*. + +-- +<1> Imports `VaadinAwareSecurityContextHolderStrategyConfiguration`, required for Vaadin security to work with Spring Security (auto-imported with `VaadinWebSecurity`). +<2> Sets `/logged-out.html` as the *logout success URL*. If your application runs at `\https://example.com`, users will be redirected to `\https://example.com/logged-out.html` after logging out. @@ -141,7 +172,7 @@ public final class MainLayout extends AppLayout { userMenuItem.getSubMenu().addItem("View Profile"); userMenuItem.getSubMenu().addItem("Manage Settings"); // tag::snippet[] - userMenuItem.getSubMenu().addItem("Logout", + userMenuItem.getSubMenu().addItem("Logout", event -> authenticationContext.logout()); // <1> // end::snippet[] diff --git a/articles/building-apps/security/add-logout/hilla.adoc b/articles/building-apps/security/add-logout/hilla.adoc index 4d540eff90..3022dd119e 100644 --- a/articles/building-apps/security/add-logout/hilla.adoc +++ b/articles/building-apps/security/add-logout/hilla.adoc @@ -41,8 +41,36 @@ export default function LogoutView() { By default, users are redirected to the root URL (`/`) after logging out. To change this, *specify a custom logout success URL* in your security configuration: +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) // <1> +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { +// tag::snippet[] + configurer.loginView("/login", "/logged-out.html"); // <2> +// end::snippet[] + }); + return http.build(); + } + ... +} +---- + +.`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @@ -51,13 +79,16 @@ class SecurityConfig extends VaadinWebSecurity { protected void configure(HttpSecurity http) throws Exception { super.configure(http); // tag::snippet[] - setLoginView(http, "/login", "/logged-out.html"); // <1> + setLoginView(http, "/login", "/logged-out.html"); // <2> // end::snippet[] } ... } ---- -<1> Sets `/logged-out.html` as the *logout success URL*. + +-- +<1> Imports `VaadinAwareSecurityContextHolderStrategyConfiguration`, required for Vaadin security to work with Spring Security (auto-imported with `VaadinWebSecurity`). +<2> Sets `/logged-out.html` as the *logout success URL*. If your application runs at `\https://example.com`, users will be redirected to `\https://example.com/logged-out.html` after logging out. diff --git a/articles/building-apps/security/protect-services/flow.adoc b/articles/building-apps/security/protect-services/flow.adoc index da9cc9d36e..3a7b8e568c 100644 --- a/articles/building-apps/security/protect-services/flow.adoc +++ b/articles/building-apps/security/protect-services/flow.adoc @@ -29,9 +29,36 @@ In this guide, you'll only learn the minimum to get started with Spring method s To enable method security, add [annotationname]`@EnableMethodSecurity` to your security configuration class: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +// tag::snippet[] +@EnableMethodSecurity +// end::snippet[] +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(LoginView.class); + }); + return http.build(); + } + ... +} +---- + +.`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity // tag::snippet[] @EnableMethodSecurity @@ -48,6 +75,7 @@ class SecurityConfig extends VaadinWebSecurity { } ---- +-- .Test the method security [CAUTION] Without [annotationname]`@EnableMethodSecurity`, *all services remain unprotected* -- even if you annotate methods with security rules! Always verify that method security is enabled with automatic tests. _A guide showing you how to do this in a Vaadin application is planned, but not yet written. In the meantime, refer to the https://docs.spring.io/spring-security/reference/servlet/test/method.html[Spring Reference Manual]._ @@ -105,9 +133,28 @@ This tutorial applies to skeletons generated for Vaadin 24.7. Skeletons generate ==== Add [annotationname]`@EnableMethodSecurity` to [classname]`SecurityConfig`: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` [source,java] ---- + +@EnableWebSecurity +// tag::snippet[] +@EnableMethodSecurity +// end::snippet[] +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + ... +} +---- + +.`SecurityConfig.java` +[source,java] +---- + @EnableWebSecurity // tag::snippet[] @EnableMethodSecurity @@ -117,6 +164,8 @@ class SecurityConfig extends VaadinWebSecurity { ... } ---- + +-- ==== @@ -180,10 +229,10 @@ public class TaskListView extends Main { //if (authenticationContext.hasRole(Roles.ADMIN)) { // end::snippet[] add(new ViewToolbar("Task List", - ViewToolbar.group(description, dueDate, createBtn))); + ViewToolbar.group(description, dueDate, createBtn))); // tag::snippet[] //} else { - // add(new ViewToolbar("Task List")); + // add(new ViewToolbar("Task List")); //} // end::snippet[] add(taskGrid); diff --git a/articles/building-apps/security/protect-services/hilla.adoc b/articles/building-apps/security/protect-services/hilla.adoc index d411ea5ac7..dcaadae7e1 100644 --- a/articles/building-apps/security/protect-services/hilla.adoc +++ b/articles/building-apps/security/protect-services/hilla.adoc @@ -24,7 +24,7 @@ Vaadin protects browser-callable services in the same way as it protects <<../pr [IMPORTANT] All browser-callable services are *inaccessible by default* and require explicit annotations to grant access. -You can annotate both *service classes* and individual *service methods*. An annotation placed on the class applies to *all public methods* of the class. An annotation placed on a method *overrides any annotation on the class*. +You can annotate both *service classes* and individual *service methods*. An annotation placed on the class applies to *all public methods* of the class. An annotation placed on a method *overrides any annotation on the class*. The following annotations are supported: @@ -68,7 +68,7 @@ public class ProtectedService { // tag::snippet[] @RolesAllowed(Roles.ADMIN) // <2> // end::snippet[] - public void callableByAdminsOnly() { + public void callableByAdminsOnly() { } } ---- @@ -91,7 +91,7 @@ public class ProtectedService { public void callableByAllUsers() { } - public void callableByAdminsOnly() { + public void callableByAdminsOnly() { } } ---- @@ -104,7 +104,7 @@ public class ProtectedService { [NOTE] The following assumes you have already set up Spring method security. For details, see the <> guide. -By default, requests coming in from the browser are denied by Vaadin before the service is even called. This applies even when the service is proxied and protected by Spring Security. +By default, requests coming in from the browser are denied by Vaadin before the service is even called. This applies even when the service is proxied and protected by Spring Security. To *bypass Vaadin's security check and rely on Spring Security instead*, you have to add [annotationname]`@AnonymousAllowed` to the service, like this: @@ -134,7 +134,7 @@ public class ProtectedService { Better support for Spring Security is planned for a future version of Vaadin Hilla. See https://github.com/vaadin/hilla/issues/3271[this ticket] for more information. .Don't configure Spring Security to use JSR-250 annotations -[WARNING] +[WARNING] Spring method security has support for the JSR-250 annotations that Vaadin uses to protect browser-callable services. However, *Vaadin treats [annotationname]`@PermitAll` differently than Spring Security*. Whereas Vaadin allows access to _authenticated_ users only, Spring Security allows access to _all_ users. @@ -166,9 +166,44 @@ public final class Roles { Then update the [methodname]`userDetailsManager()` method of the [classname]`SecurityConfig` class to use the new constants: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` [source,java] ---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + ... + + @Bean + public UserDetailsManager userDetailsManager() { + LoggerFactory.getLogger(SecurityConfig.class) + .warn("Using in-memory user details manager!"); + var user = User.withUsername("user") + .password("{noop}user") +// tag::snippet[] + .roles(Roles.USER) +// end::snippet[] + .build(); + var admin = User.withUsername("admin") + .password("{noop}admin") +// tag::snippet[] + .roles(Roles.ADMIN) +// end::snippet[] + .build(); + return new InMemoryUserDetailsManager(user, admin); + } +} +---- + +.`SecurityConfig.java` +[source,java] +---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @@ -194,6 +229,8 @@ class SecurityConfig extends VaadinWebSecurity { } } ---- + +-- ==== diff --git a/articles/building-apps/security/protect-views/flow.adoc b/articles/building-apps/security/protect-views/flow.adoc index 8f5923f92f..8977098bfa 100644 --- a/articles/building-apps/security/protect-views/flow.adoc +++ b/articles/building-apps/security/protect-views/flow.adoc @@ -121,7 +121,7 @@ class CustomAccessChecker implements NavigationAccessChecker { ---- <1> Registers the navigation access checker as a singleton Spring bean. -The [classname]`NavigationContext` object contains information about where you're trying to navigate, such as the view class, route parameters, and query parameters. It also contains the principal of the current user. +The [classname]`NavigationContext` object contains information about where you're trying to navigate, such as the view class, route parameters, and query parameters. It also contains the principal of the current user. Since the access checker is a Spring bean, you inject other beans into it. For example, you may want to lookup additional information in order to make the access decision. @@ -140,13 +140,36 @@ The security annotations are actually enforced by a built-in access checker. To enable a custom [interfacename]`NavigationAccessChecker`, create a new [classname]`NavigationAccessControlConfigurer` Spring bean: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + + @Bean + static NavigationAccessControlConfigurer navigationAccessControlConfigurer( // <1> + CustomAccessChecker customAccessChecker) { + return new NavigationAccessControlConfigurer() + .withNavigationAccessChecker(customAccessChecker); // <2> + } + ... +} +---- + +.`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { - + @Bean static NavigationAccessControlConfigurer navigationAccessControlConfigurer( // <1> CustomAccessChecker customAccessChecker) { @@ -156,10 +179,12 @@ class SecurityConfig extends VaadinWebSecurity { ... } ---- + +-- <1> The [annotationname]`@Bean` method must be `static` to prevent bootstrap errors caused by circular dependencies in bean definitions. <2> [classname]`CustomAccessChecker` is now *the only enabled access checker*. -You can have multiple access checkers active at the same time. When you navigate to a view, they will all be consulted. +You can have multiple access checkers active at the same time. When you navigate to a view, they will all be consulted. [NOTE] To enable the built-in annotated view access checker, call `NavigationAccessControlConfigurer.withAnnotatedViewAccessChecker()`. @@ -289,9 +314,44 @@ public final class Roles { Then update the [methodname]`userDetailsManager()` method of the [classname]`SecurityConfig` class to use the new constants: -.SecurityConfig.java +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfig { + ... + + @Bean + public UserDetailsManager userDetailsManager() { + LoggerFactory.getLogger(SecurityConfig.class) + .warn("Using in-memory user details manager!"); + var user = User.withUsername("user") + .password("{noop}user") +// tag::snippet[] + .roles(Roles.USER) +// end::snippet[] + .build(); + var admin = User.withUsername("admin") + .password("{noop}admin") +// tag::snippet[] + .roles(Roles.ADMIN) +// end::snippet[] + .build(); + return new InMemoryUserDetailsManager(user, admin); + } +} +---- + +.`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration class SecurityConfig extends VaadinWebSecurity { @@ -317,6 +377,8 @@ class SecurityConfig extends VaadinWebSecurity { } } ---- + +-- ==== @@ -373,16 +435,16 @@ So far all authenticated users have been able to add tasks to [classname]`TaskLi @PermitAll public class TaskListView extends Main { - public TaskListView(TaskService taskService, Clock clock, + public TaskListView(TaskService taskService, Clock clock, // tag::snippet[] AuthenticationContext authenticationContext) { // end::snippet[] - + // The rest of the constructor omitted // tag::snippet[] if (authenticationContext.hasRole(Roles.ADMIN)) { - add(new ViewToolbar("Task List", + add(new ViewToolbar("Task List", ViewToolbar.group(description, dueDate, createBtn))); // <1> } else { add(new ViewToolbar("Task List")); // <2> diff --git a/articles/flow/integrations/spring/oauth2.adoc b/articles/flow/integrations/spring/oauth2.adoc index fdc6ae5da1..fd4c2559f6 100644 --- a/articles/flow/integrations/spring/oauth2.adoc +++ b/articles/flow/integrations/spring/oauth2.adoc @@ -38,7 +38,7 @@ implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' -- -Next, add the OAuth2 provider settings to the application's configuration file. The following example configuration integrates a https://www.keycloak.org/[Keycloak] OAuth2 provider. To setup a test environment, refer to the <<{articles}/tools/sso/integrations/keycloak#, Keycloak Integration>> in Vaadin SSO Kit documentation. +Next, add the OAuth2 provider settings to the application's configuration file. The following example configuration integrates a https://www.keycloak.org/[Keycloak] OAuth2 provider. To setup a test environment, refer to the <<{articles}/tools/sso/integrations/keycloak#, Keycloak Integration>> in Vaadin SSO Kit documentation. [.example] -- @@ -80,7 +80,7 @@ For integration with other OAuth2 providers, refer to the https://docs.spring.io == Enable OAuth2 Login in Vaadin -To enable OAuth2 login in a Vaadin application, extend the [classname]`VaadinWebSecurity` class and configure the login page and post-logout redirect URI using the [methodname]`setOAuth2LoginPage` method. +To enable OAuth2 login in a Vaadin application, use the [classname]`VaadinSecurityConfigurer` class and configure the login page and post-logout redirect URI using the [methodname]`oauth2LoginPage` method. Besides the [classname]`HttpSecurity` instance, there are two method parameters: @@ -90,25 +90,50 @@ Besides the [classname]`HttpSecurity` instance, there are two method parameters: The post logout redirect URI can be expressed as a relative or absolute URI, or as a template. The supported URI template variables are `{baseScheme}`, `{baseHost}`, `{basePort}`, `{basePath}`, and `{baseUrl}` -- which is the same as `{baseScheme}://{baseHost}{basePort}{basePath}`. [.example] +-- + +.Enable OAuth Login in VaadinSecurityConfigurer +[source,java] +---- + +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +class SecurityConfiguration { + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.oauth2LoginPage( + "/oauth2/authorization/keycloak", // (1) + "{baseUrl}/session-ended" // (2) + ); + }); + return http.build(); + } +} +---- + .Enable OAuth Login in VaadinWebSecurity [source,java] ---- + @Configuration class SecurityConfiguration extends VaadinWebSecurity { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); - setOAuth2LoginPage(http, + setOAuth2LoginPage(http, "/oauth2/authorization/keycloak", // (1) "{baseUrl}/session-ended" // (2) ); } } ---- + +-- <1> Login page for initiating OAuth2 login with the Keycloak client. <2> Post logout redirect URI uses a template to resolve dynamically the URL. -The [methodname]`setOAuth2LoginPage(HttpSecurity, String)` method is a shortcut that defaults the post-logout redirect URL to `{baseUrl}`. +The [methodname]`oauth2LoginPage(String)` method is a shortcut that defaults the post-logout redirect URL to `{baseUrl}`. [discussion-id]`EF8F6AC3-BE67-4BE2-9A78-C371C1D4B9FD` diff --git a/articles/flow/security/advanced-topics/navigation-access-control.adoc b/articles/flow/security/advanced-topics/navigation-access-control.adoc index c57794ef11..e6ae4cc0b2 100644 --- a/articles/flow/security/advanced-topics/navigation-access-control.adoc +++ b/articles/flow/security/advanced-topics/navigation-access-control.adoc @@ -49,8 +49,55 @@ The Vaadin Spring add-on offers an [interfacename]`AccessPathChecker` implementa This means that to protect a Flow route, you can configure a Spring request matchers matching the route path, and use the authorization helpers to define the access grants, as in the following example. +[.example] +-- + [source,java] ---- + +@Route("admin") +public class AdminView extends Div { } + +@Route("protected/profile") +public class ProfileView extends Div { } + +@Route("special/preview-feature") +public class ProfileView extends Div { } + +@Route("") +public class HomeView extends Div { } + +@Configuration +@EnableWebSecurity +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(auth -> auth + .requestMatchers("/admin").hasAnyRole(ROLE_ADMIN) + .requestMatchers("/protected/**").authenticated() + .requestMatchers("/special/**") + .access((authentication, object) -> { + // Custom authorization logic + return new AuthorizationDecision(isPremiumUser(authentication)); + }) + .requestMatchers("/") + .permitAll() + ); + + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + // Configure Vaadin security settings + }); + + return http.build(); + } +} +---- + +[source,java] +---- + @Route("admin") public class AdminView extends Div { } @@ -82,6 +129,7 @@ public class SecurityConfig extends VaadinWebSecurity { } ---- +-- Integration with other security frameworks can be accomplished similarly by providing a specific [interfacename]`AccessPathChecker` implementation. @@ -172,19 +220,19 @@ include::{root}/src/main/java/com/vaadin/demo/flow/auth/CustomDecisionResolver.j == Configuration -Navigation Access Control is enabled automatically on Spring Boot projects using [classname]`VaadinWebSecurity`. To enable the feature in plain Java projects, follow the specific <<{articles}/flow/security/advanced-topics/securing-plain-java-app#, documentation page>>. +Navigation Access Control is enabled automatically on Spring Boot projects using <<{articles}/flow/security/vaadin-security-configurer.adoc#configurer,VaadinSecurityConfigurer>>. To enable the feature in plain Java projects, follow the specific <<{articles}/flow/security/advanced-topics/securing-plain-java-app#, documentation page>>. This section shows how to customize Navigation Access Control for both Spring and plain Java projects. === Spring Projects -When using [classname]`VaadinWebSecurity` as base class for configuring Spring Security, the Navigation Access Control feature is enabled by default. You can disable it by overriding the [methodname]`enableNavigationAccessControl` method in [classname]`VaadinWebSecurity` to return `false`. For backward compatibility, only the [classname]`AnnotatedViewAccessChecker` is active by default. +When using [classname]`VaadinSecurityConfigurer` for configuring Spring Security, the Navigation Access Control feature is enabled by default. You can disable it by calling the [methodname]`enableNavigationAccessControl(false)` method on the configurer. For backward compatibility, only the [classname]`AnnotatedViewAccessChecker` is active by default. For a fine-grained configuration, you can expose a bean of type [classname]`NavigationAccessControlConfigurer`. [classname]`NavigationAccessControlConfigurer` allows activating out-of-the-box navigation access checkers or adding new ones, providing a custom decision resolver, completely disable the functionality, etc. [NOTE] -If [classname]`VaadinWebSecurity` is not used, exposing the [classname]`NavigationAccessControlConfigurer` bean is mandatory to activate navigation access control. +If [classname]`VaadinSecurityConfigurer` is not used, exposing the [classname]`NavigationAccessControlConfigurer` bean is mandatory to activate navigation access control. In the following example, Navigation Access Control is configured to activate route path checker, a custom checker instance and all available [interfacename]`NavigationAccessChecker` beans that extend [classname]`VotingNavigationAccessChecker`. It also provides a custom decision resolver. @@ -208,11 +256,38 @@ NavigationAccessControlConfigurer navigationAccessControlConfigurerCustomizer() <4> Sets a custom decision resolver. [IMPORTANT] -If you add the bean definition method in a configuration class extending [classname]`VaadinWebSecurity`, the method must be declared `static`, to prevent bootstrap errors because of circular dependencies in bean definitions. +When using [classname]`VaadinSecurityConfigurer` in the same configuration class where you define the [classname]`NavigationAccessControlConfigurer` bean, make sure to declare the bean method as `static` to prevent bootstrap errors due to circular dependencies in bean definitions. + +[.example] +-- + +.Define NavigationAccessControlConfigurer Bean with VaadinSecurityConfigurer +[source,java] +---- + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + @Bean + static NavigationAccessControlConfigurer navigationAccessControlConfigurer() { + return new NavigationAccessControlConfigurer() + .withRoutePathAccessChecker() + .withDecisionResolver(new CustomDecisionResolver()); + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + // VaadinSecurityConfigurer will use the NavigationAccessControlConfigurer bean + }).build(); + } +} +---- .Define NavigationAccessControlConfigurer Bean in VaadinWebSecurity Subclass [source,java] ---- + class SecurityConfig extends VaadinWebSecurity { @Bean static NavigationAccessControlConfigurer navigationAccessControlConfigurer() { @@ -223,6 +298,7 @@ class SecurityConfig extends VaadinWebSecurity { } ---- +-- Consult the [classname]`NavigationAccessControlConfigurer` Javadoc for more information on how navigation access control can be customized. @@ -261,4 +337,6 @@ public class NavigationAccessCheckerInitializer implements VaadinServiceInitList ---- + + [discussion-id]`4164EB30-201D-4FD3-940F-03630A752AD5` diff --git a/articles/flow/security/enabling-security.adoc b/articles/flow/security/enabling-security.adoc index ee948cd064..4097fbf2cb 100644 --- a/articles/flow/security/enabling-security.adoc +++ b/articles/flow/security/enabling-security.adoc @@ -25,7 +25,7 @@ To enable the mechanism in a Vaadin Flow Spring Boot application without any sec - Login view; - Spring Security dependencies; - Log-out capability; -- Security configuration class that extends [classname]`VaadinWebSecurity`; and +- Security configuration class that uses [classname]`VaadinSecurityConfigurer`; and - One of these annotations on each view class: [annotationname]`@AnonymousAllowed`, [annotationname]`@PermitAll`, or [annotationname]`@RolesAllowed`. A complete example project, including the source code from this document, is available at https://github.com/vaadin/flow-crm-tutorial[`github.com/vaadin/flow-crm-tutorial`]. @@ -202,17 +202,75 @@ public class MainLayout extends AppLayout { == Security Configuration Class -The next step is to have a Spring Security class that extends [classname]`VaadinWebSecurity`. There's no convention for naming this class, so here it's named [classname]`SecurityConfiguration`. However, take care with Spring Security annotations. +The next step is to have a Spring Security configuration class that uses [classname]`VaadinSecurityConfigurer`. There's no convention for naming this class, so here it's named [classname]`SecurityConfiguration`. However, take care with Spring Security annotations. + +Examples shows also how to use [classname]`VaadinWebSecurity` class instead. It's deprecated since 24.9 and will be removed in next major release. This is a minimal implementation of such a class: + +[.example] +-- + .[filename]`SecurityConfiguration.java` [source,java] ---- + +@EnableWebSecurity // <1> +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) // <2> +public class SecurityConfiguration { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // Configure your static resources with public access + http.authorizeHttpRequests(auth -> auth.requestMatchers("/public/**") + .permitAll()); + + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { // <3> + // This is important to register your login view to the + // navigation access control mechanism: + configurer.loginView(LoginView.class); // <4> + + // You can add any possible extra configurations of your own + // here (the following is just an example): + // configurer.enableCsrfConfiguration(false); + }); + + return http.build(); + } + + /** + * Demo UserDetailsManager which only provides two hardcoded + * in memory users and their roles. + * NOTE: This shouldn't be used in real world applications. + */ + @Bean + public UserDetailsManager userDetailsService() { + UserDetails user = + User.withUsername("user") + .password("{noop}user") + .roles("USER") + .build(); + UserDetails admin = + User.withUsername("admin") + .password("{noop}admin") + .roles("ADMIN") + .build(); + return new InMemoryUserDetailsManager(user, admin); + } +} +---- + +.[filename]`SecurityConfiguration.java` +[source,java] +---- + @EnableWebSecurity // <1> @Configuration public class SecurityConfiguration - extends VaadinWebSecurity { // <2> +extends VaadinWebSecurity { // <2> @Override protected void configure(HttpSecurity http) throws Exception { @@ -267,22 +325,23 @@ public class SecurityConfiguration } } ---- +-- -Notice the including of [annotationname]`@EnableWebSecurity` and [annotationname]`@Configuration` annotations on top of the above class. As their names imply, they instruct Spring to enable its security features. +Notice the including of [annotationname]`@EnableWebSecurity`, [annotationname]`@Configuration`, and [annotationname]`@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)` annotations on top of the above class. As their names imply, they instruct Spring to enable its security features and configure the Vaadin-aware security context holder strategy. -[classname]`VaadinWebSecurity` is a helper class that configures the common Vaadin-related Spring Security settings. By extending it, the view-based access control mechanism is enabled automatically, and no further configuration is needed. +[classname]`VaadinSecurityConfigurer` is a helper class that configures the common Vaadin-related Spring Security settings. By using it, the view-based access control mechanism is enabled automatically, and no further configuration is needed. -The default implementation of the `configure` methods handles all of the Vaadin-related configuration. For example, it ignores static resources, or enables `CSRF` checking, while ignoring unnecessary checking for Vaadin internal requests. +The [classname]`VaadinSecurityConfigurer` handles all of the Vaadin-related configuration. For example, it ignores static resources, enables `CSRF` checking while ignoring unnecessary checking for Vaadin internal requests, and configures proper exception handling for Vaadin applications. -The log-in view can be configured via the provided [methodname]`setLoginView()` method. +The log-in view can be configured via the provided [methodname]`loginView()` method on the configurer. .Never Use Hard-Coded Credentials in Production [WARNING] The implementation of the [methodname]`userDetailsService()` method is just an in-memory implementation for the sake of brevity in this documentation. In a normal application, you can change the Spring Security configuration to use an authentication provider for Lightweight Directory Access Protocol (LDAP), JAAS, and other real-world sources. See https://dzone.com/articles/flow/spring-security-authentication[Spring Security authentication providers] to read more about them. -The most important configuration in the previous example is the call to [methodname]`setLoginView(http, LoginView.class)` inside the first configure method. This is how the view-based access control mechanism knows where to redirect users when they try to navigate to a protected view. +The most important configuration in the previous example is the call to [methodname]`configurer.loginView(LoginView.class)`. This is how the view-based access control mechanism knows where to redirect users when they try to navigate to a protected view. -The log-in view should always be accessible by anonymous users, so it should have the [annotationname]`@AnonymousAllowed` annotation. This is especially important when using the variant of the [methodname]`setLoginView` method where you provide the route path -- although this signature is meant to be used with https://vaadin.com/hilla[Hilla] views, not with Flow views. +The log-in view should always be accessible by anonymous users, so it should have the [annotationname]`@AnonymousAllowed` annotation. This is especially important when using the variant of the [methodname]`loginView` method where you provide the route path -- although this signature is meant to be used with https://vaadin.com/hilla[Hilla] views, not with Flow views. For additional information about navigation access control, consult the <<{articles}/flow/security/advanced-topics/navigation-access-control#, related documentation>>. @@ -290,7 +349,7 @@ For additional information about navigation access control, consult the <<{artic [NOTE] Spring Security 5.7.0 deprecates the `WebSecurityConfigurerAdapter`. Migrate to a component-based security configuration. -`VaadinWebSecurityConfigurerAdapter` is still available for Vaadin 23.2 users, although it's recommended to use component-based security configuration as in `SecurityConfiguration` example here. Read more about https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter[updating from WebSecurityConfigurerAdapter to component-based security configuration]. +The component-based security configuration shown in the `SecurityConfiguration` example above is the recommended approach. Read more about https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter[updating from WebSecurityConfigurerAdapter to component-based security configuration]. Once the `LoginView` is ready, and you've set it as the log-in view in the security configuration, you're ready to move ahead and see how the security annotations work on the views. @@ -305,7 +364,7 @@ Before providing some usage examples of access annotations, it would be good to - [annotationname]`@RolesAllowed` grants access to users having the roles specified in the annotation value. - [annotationname]`@DenyAll` disallows everyone from navigating to a view. This is the default; if a view isn't annotated, the [annotationname]`@DenyAll` logic is applied. -When the security configuration class extends from [classname]`VaadinWebSecurity`, Vaadin's [classname]`SpringSecurityAutoConfiguration` comes into play and enables the navigation access control mechanism with view-based security. Therefore, none of the views are accessible until one of these annotations is applied to them -- except [annotationname]`@DenyAll`. +When using [classname]`VaadinSecurityConfigurer`, Vaadin's [classname]`SpringSecurityAutoConfiguration` comes into play and enables the navigation access control mechanism with view-based security. Therefore, none of the views are accessible until one of these annotations is applied to them -- except [annotationname]`@DenyAll`. Below is an example using [annotationname]`@AnonymousAllowed` to enable all users to navigate to this view: @@ -499,7 +558,7 @@ NavigationAccessControlConfigurer navigationAccessControlConfigurer() { <2> Activation of Spring URL-pattern-based checker. [IMPORTANT] -If you add the bean definition method in a configuration class extending [classname]`VaadinWebSecurity`, the method must be declared `static`, to prevent bootstrap errors because of circular dependencies in bean definitions. +If you add the bean definition method in the same configuration class where you use [classname]`VaadinSecurityConfigurer`, the method must be declared `static`, to prevent bootstrap errors because of circular dependencies in bean definitions. You can also activate only the [classname]`RoutePathAccessChecker`, if you prefer to centralize the authorization configuration by only using the Spring Security URL-pattern-based security feature. @@ -587,21 +646,24 @@ To add impersonation for a Vaadin application, create the [classname]`SwitchUser ---- [NOTE] -The bean should use `VaadinSecurityContextHolderStrategy` bean to work properly. If the [classname]`SwitchUserFilter` is initialized differently, the wrong security holder is used and the feature won't work. If your are not using `VaadinWebSecurity` as base class for your security configuration, you might need to add `@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)` on top of your class. +The bean should use `VaadinSecurityContextHolderStrategy` bean to work properly. If the [classname]`SwitchUserFilter` is initialized differently, the wrong security holder is used and the feature won't work. Make sure to add `@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)` on top of your security configuration class. -To secure the impersonation endpoints, add to the [classname]`VaadinWebSecurity` implementation [methodname]`configure(HttpSecurity http)` the matchers like so: +To secure the impersonation endpoints, configure the HttpSecurity object with the appropriate matchers like so: [source,java] ---- - @Override - protected void configure(HttpSecurity http) throws Exception { + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // Authorize impersonation for admin/impersonated admin - http.authorizeHttpRequests(auth -> auth.requestMatchers(new AntPathRequestMatcher("/impersonate")).hasAnyRole("ADMIN", "PREVIOUS_ADMINISTRATOR")); - http.authorizeHttpRequests(auth -> auth.requestMatchers(new AntPathRequestMatcher("/impersonate/exit")).hasRole("PREVIOUS_ADMINISTRATOR")); + http.authorizeHttpRequests(auth -> auth.requestMatchers("/impersonate").hasAnyRole("ADMIN", "PREVIOUS_ADMINISTRATOR")); + http.authorizeHttpRequests(auth -> auth.requestMatchers("/impersonate/exit").hasRole("PREVIOUS_ADMINISTRATOR")); + + // Configure Vaadin's security using VaadinSecurityConfigurer + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(LoginView.class); + }); - // Set default security policy that permits Vaadin internal requests and - // denies all other - super.configure(http); + return http.build(); // Other configurations } ---- diff --git a/articles/flow/security/vaadin-security-configurer.adoc b/articles/flow/security/vaadin-security-configurer.adoc new file mode 100644 index 0000000000..2766ef3485 --- /dev/null +++ b/articles/flow/security/vaadin-security-configurer.adoc @@ -0,0 +1,346 @@ +--- +title: Vaadin Security Configurer +page-title: How to use Vaadin Security Configurer +description: How to use VaadinSecurityConfigurer to configure security in a Vaadin Flow application using built-in security helpers with Spring Boot. +meta-description: Learn how to use VaadinSecurityConfigurer to configure security in a Vaadin Flow application with Spring Boot using built-in security helpers. +order: 20 +--- + +[#configurer] +=== [since:com.vaadin:vaadin@V24.8]#VaadinSecurityConfigurer# + +==== Overview + +`VaadinSecurityConfigurer` is a Spring Security HTTP configurer specifically designed for Vaadin applications. +It provides built-in customizers to configure security settings for Flow and Hilla by integrating with Spring Security and specialized methods to handle view access control and default security workflows in Vaadin applications. +This configurer follows Spring Security's recommended patterns for security configuration and allows for modular, composable security customization. + +==== Usage + +The `VaadinSecurityConfigurer` can be used in a Spring Security configuration class to set up security for Vaadin applications: + +[source,java] +---- +@Configuration +@EnableWebSecurity +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(MyLoginView.class); + }).build(); + } +} +---- + +The `VaadinAwareSecurityContextHolderStrategyConfiguration` is imported manually to ensure that the [classname]`VaadinSession`-based security context holder is initialized. + +==== Applied Configurers + +The `VaadinSecurityConfigurer` applies several other Spring Security configurers to set up the security filter chain: + +* `FormLoginConfigurer` — If a login view is set with `loginView(Class)` (or overloads) +* `OAuth2LoginConfigurer` — If a login page for OAuth2 authentication is set with `oauth2LoginPage(String)` (or overloads) +* `CsrfConfigurer` — To allow, internal framework requests (can be disabled) +* `LogoutConfigurer` — To configure logout handlers for Vaadin applications (can be disabled) +* `RequestCacheConfigurer` — To set a request cache designed for Vaadin applications (can be disabled) +* `ExceptionHandlingConfigurer` — To configure proper exception handling for Vaadin applications (can be disabled) +* `AuthorizeHttpRequestsConfigurer` — To permit internal framework requests and other public endpoints (can be disabled) + +==== Shared Objects + +The following beans are shared by this configurer (if not already shared): + +* `RequestUtil` — Utility class for request-based security checks +* `AuthenticationContext` — Provides information about the current logged-in user and its roles +* `NavigationAccessControl` — Entry point for navigation access control +* `VaadinRolePrefixHolder` — Holds role prefix accessible outside an active request +* `VaadinDefaultRequestCache` — A request cache implementation which ignores requests that are not for routes +* `VaadinSavedRequestAwareAuthenticationSuccessHandler` — A strategy that uses an available VaadinSession for retrieving the security context + +==== Configuration Methods + +===== Login View Configuration + +[source,java] +---- +public VaadinSecurityConfigurer loginView(Class loginView) +---- + +Configures the login view for use in a Flow application. The provided login view class must be annotated with `@Route`. + +[source,java] +---- +public VaadinSecurityConfigurer loginView(Class loginView, String logoutSuccessUrl) +---- + +Configures the login view for use in a Flow application and the logout success URL. + +[source,java] +---- +public VaadinSecurityConfigurer loginView(String loginView) +---- + +Configures the login view for use in a Hilla application. This is used when your application uses a Hilla-based login view that is available at the given path. + +[source,java] +---- +public VaadinSecurityConfigurer loginView(String loginView, String logoutSuccessUrl) +---- + +Configures the login view for use in a Hilla application and the logout success URL. + +===== OAuth2 Configuration + +[source,java] +---- +public VaadinSecurityConfigurer oauth2LoginPage(String oauth2LoginPage) +---- + +Configures the login page for OAuth2 authentication. If using Spring's OAuth2 client, this should be set to Spring's internal redirect endpoint `/oauth2/authorization/{registrationId}` where `{registrationId}` is the ID of the OAuth2 client registration. + +[source,java] +---- +public VaadinSecurityConfigurer oauth2LoginPage(String oauth2LoginPage, String postLogoutRedirectUri) +---- + +Configures the login page for OAuth2 authentication and the post-logout redirect URI. + +===== Logout Configuration + +[source,java] +---- +public VaadinSecurityConfigurer logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) +---- + +Configures the handler for a successful logout. This overrides the default handler configured automatically with either `loginView(Class)` or `oauth2LoginPage(String)` (and their overloads). + +[source,java] +---- +public VaadinSecurityConfigurer addLogoutHandler(LogoutHandler logoutHandler) +---- + +Adds a `LogoutHandler` to the list of logout handlers. + +===== Feature Toggles + +[source,java] +---- +public VaadinSecurityConfigurer enableCsrfConfiguration(boolean enableCsrfConfiguration) +---- + +Enables or disables automatic CSRF configuration (enabled by default). This configurer will automatically configure Spring's CSRF filter to allow Vaadin internal framework requests to be properly processed. + +[source,java] +---- +public VaadinSecurityConfigurer enableLogoutConfiguration(boolean enableLogoutConfiguration) +---- + +Enables or disables automatic logout configuration (enabled by default). This configurer will automatically configure logout behavior to work properly with Flow and Hilla. + +[source,java] +---- +public VaadinSecurityConfigurer enableRequestCacheConfiguration(boolean enableRequestCacheConfiguration) +---- + +Enables or disables automatic configuration of the request cache (enabled by default). This configurer will automatically configure the request cache to work properly with Vaadin's internal framework requests. + +[source,java] +---- +public VaadinSecurityConfigurer enableExceptionHandlingConfiguration(boolean enableExceptionHandlingConfiguration) +---- + +Enables or disables automatic configuration of exception handling (enabled by default). This configurer will automatically configure exception handling to work properly with Flow and Hilla. + +[source,java] +---- +public VaadinSecurityConfigurer enableAuthorizedRequestsConfiguration(boolean enableAuthorizedRequestsConfiguration) +---- + +Enables or disables automatic configuration of authorized requests (enabled by default). This configurer will automatically configure authorized requests to permit requests to anonymous Flow and Hilla views, and static assets. + +[source,java] +---- +public VaadinSecurityConfigurer enableNavigationAccessControl(boolean enableNavigationAccessControl) +---- + +Enables or disables configuration of `NavigationAccessControl`. `NavigationAccessControl` is enabled by default. + +===== Request Matchers + +[source,java] +---- +public VaadinSecurityConfigurer anyRequest(Consumer.AuthorizedUrl> anyRequestAuthorizeRule) +---- + +Configures the access rule for any request not matching other configured rules. The default rule is to require authentication, which is the equivalent of passing `AuthorizedUrl::authenticated()` to this method. + +[source,java] +---- +public RequestMatcher defaultPermitMatcher() +---- + +Creates and returns a composite `RequestMatcher` for identifying requests that should be permitted without authentication within a Vaadin application. This matcher combines multiple specific matchers, including those for framework internal requests, anonymous endpoints, allowed Hilla views, anonymous routes, custom web icons, and default security configurations. + +==== Examples + +===== Basic Configuration + +[classname]`VaadinSecurityConfigurer` exposes a factory method [methodname]`vaadin` that creates a new instance of the `VaadinSecurityConfigurer`: + +[source,java] +---- +@Configuration +@EnableWebSecurity +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(LoginView.class); + }).build(); + } +} +---- + +===== Multiple Filter Chains + +To configure multiple filter chains, use `@Order` annotation to specify the order of the filter chains. The lower the order value, the higher the priority of the filter chain. + +[classname]`VaadinSecurityConfigurer` should be used at most in one filter chain. Using it in multiple chains may behave in an unexpected ways, e.g. login view being overwritten in [classname]`NavigationAccessControl`. + +[source,java] +---- +@Configuration +@EnableWebSecurity +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Order(1) + @Bean + SecurityFilterChain privateFilterChain(HttpSecurity http) throws Exception { + return http.securityMatcher("/private/**") + .with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(LoginView.class); + }).build(); + } + + @Order(0) + @Bean + SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception { + return http.securityMatcher("/public/**") + .authorizeHttpRequests(auth -> { + auth.anyRequest(AuthorizedUrl::permitAll); + }).build(); + } +} +---- + +Example of two separate security filters: one for handling stateless Spring Boot APIs with token-based authentication, and another for a stateful Vaadin web application with login view. + +[source,java] +---- +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfigurationAPI { + + private final UserDetailsService userDetailsService; + private final AuthTokenFilter authTokenFilter; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + @Order(0) + public SecurityFilterChain securityFilterApi(HttpSecurity http) throws Exception { + HttpSecurity httpSecurity = http + .securityMatcher("/api/**") + .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/v1/login").anonymous() + .requestMatchers("/api/v1/admin/**").hasRole("ADMIN") + .requestMatchers("/api/v1/**").authenticated()); + + http.authenticationProvider(authenticationProvider()); + http.addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class); + + return httpSecurity.build(); + } +} + +@Configuration +@EnableWebSecurity +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Order(1) + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(LoginView.class); + }).build(); + } +} +---- + +===== Custom Authorization Rules + +[source,java] +---- +@Configuration +@EnableWebSecurity +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(auth -> auth + .requestMatchers("/admin-only/**").hasAnyRole("ADMIN") + .requestMatchers("/public/**").permitAll() + .requestMatchers("/error").permitAll()); + + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(LoginView.class) + .logoutSuccessHandler(this::onLogoutOnNonVaadinUrl) + .addLogoutHandler((request, response, authentication) -> { + // Custom logout logic + }); + }); + + return http.build(); + } +} +---- + +===== Disabling Features + +[source,java] +---- +@Configuration +@EnableWebSecurity +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + configurer.loginView(LoginView.class) + .enableCsrfConfiguration(false) + .enableNavigationAccessControl(false); + }).build(); + } +} +---- \ No newline at end of file diff --git a/articles/hilla/lit/guides/security/spring-login.adoc b/articles/hilla/lit/guides/security/spring-login.adoc index be7b54f9c1..a0f6954ea1 100644 --- a/articles/hilla/lit/guides/security/spring-login.adoc +++ b/articles/hilla/lit/guides/security/spring-login.adoc @@ -34,13 +34,51 @@ After doing this, the application is protected with a default Spring login view. == Server Configuration -To implement your own security configuration, create a new configuration class that extends the [classname]`VaadinWebSecurity` class. Then annotate it to enable security. +To implement your own security configuration, create a new configuration class that uses the [classname]`VaadinSecurityConfigurer` class. Then annotate it to enable security. -[classname]`VaadinWebSecurity` is a helper which provides default bean implementations for link:https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/SecurityFilterChain.html[SecurityFilterChain] and link:https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/WebSecurityCustomizer.html[WebSecurityCustomizer]. It takes care of the basic configuration for requests, so that you can concentrate on your application-specific configuration. +[classname]`VaadinSecurityConfigurer` is a helper which provides default bean implementations for link:https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/SecurityFilterChain.html[SecurityFilterChain] and link:https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/WebSecurityCustomizer.html[WebSecurityCustomizer]. It takes care of the basic configuration for requests, so that you can concentrate on your application-specific configuration. + +[.example] +-- .`SecurityConfig.java` [source,java] ---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http, RouteUtil routeUtil) throws Exception { + // Set default security policy that permits Hilla internal requests and + // denies all other + http.authorizeHttpRequests(registry -> registry.requestMatchers( + routeUtil::isRouteAllowed).permitAll()); + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + // use a custom login view and redirect to root on logout + configurer.loginView("/login", "/"); + }); + return http.build(); + } + + @Bean + public UserDetailsManager userDetailsService() { + // Configure users and roles in memory + return new InMemoryUserDetailsManager( + // the {noop} prefix tells Spring that the password is not encoded + User.withUsername("user").password("{noop}user").roles("USER").build(), + User.withUsername("admin").password("{noop}admin").roles("ADMIN", "USER").build() + ); + } +} +---- + +.`SecurityConfig.java` +[source,java] +---- + @EnableWebSecurity @Configuration public class SecurityConfig extends VaadinWebSecurity { @@ -74,6 +112,8 @@ public class SecurityConfig extends VaadinWebSecurity { } ---- +-- + .Never Hard-Coded Credentials [WARNING] You should never hard-code credentials in an application. The <<{articles}/hilla/lit/guides/security/spring-login#appendix-production-data-sources,Security>> documentation has examples of setting up LDAP or SQL-based user management. @@ -81,11 +121,29 @@ You should never hard-code credentials in an application. The <<{articles}/hilla === Public Views & Resources -Public views need to be added to the configuration before calling [methodname]`super.configure()`. Here's an example of this: +Public views need to be added to the configuration in [methodname]`securityFilterChain`. Here's an example of this: + +[.example] +-- .`SecurityConfig.java` [source,java] ---- + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(registry -> { + registry.requestMatchers("/public-view").permitAll(); // custom matcher + }); + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { ... }); + return http.build(); + } +---- + +.`SecurityConfig.java` +[source,java] +---- + @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeHttpRequests(registry -> { @@ -95,7 +153,9 @@ Public views need to be added to the configuration before calling [methodname]`s } ---- -Public resources can be added by overriding [methodname]`configure(WebSecurity web)` like so: +-- + +Public resources can be added to the configuration in [methodname]`securityFilterChain` like so: .`SecurityConfig.java` [source,java] @@ -148,7 +208,7 @@ const routes = [ ] ---- -Update the [classname]`SecurityConfig` to use the [methodname]`setLogin()` helper, which sets up everything needed for a Hilla-based login view: +Update the [classname]`SecurityConfig` to use the [methodname]`loginView()` helper, which sets up everything needed for a Hilla-based login view: [source,java] ---- @@ -328,7 +388,7 @@ The `getRoles` function is still expected to return an array of strings. As a re endif::hilla-react[] -=== Configuration Helper Alternatives +=== [deprecated:com.vaadin:vaadin@V24.9]#Configuration Helper Alternatives# [methodname]`VaadinWebSecurity.configure(http)` configures HTTP security to bypass framework internal resources. If you prefer to make your own configuration, instead of using the helper, the matcher for these resources can be retrieved with [methodname]`VaadinWebSecurity.getDefaultHttpSecurityPermitMatcher()`. @@ -496,9 +556,42 @@ The example given here of managing users in memory is valid for test application The following example demonstrates how to access an SQL database with tables for users and authorities. + +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + //... + + @Bean + UserDetailsService userDetailsService(DataSource dataSource) { + // Configure users and roles in a JDBC database + JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource); + jdbcUserDetailsManager.setUsersByUsernameQuery( + "SELECT username, password, enabled FROM users WHERE username=?"); + jdbcUserDetailsManager.setAuthoritiesByUsernameQuery( + "SELECT username, authority FROM authorities WHERE username=?"); + return jdbcUserDetailsManager; + } + + @Bean + PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} +---- + .`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration public class SecurityConfig extends VaadinWebSecurity { @@ -521,14 +614,57 @@ public class SecurityConfig extends VaadinWebSecurity { } ---- +-- + === LDAP Authentication This next example shows how to configure authentication by using an LDAP repository: +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + //... + + @Bean + public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { // <1> + return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer(); + } + + @Bean + AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource, LdapAuthoritiesPopulator authorities) { + LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory( + contextSource, NoOpPasswordEncoder.getInstance()); + factory.setUserDnPatterns("uid={0},ou=people"); + factory.setUserSearchBase("ou=people"); + factory.setPasswordAttribute("userPassword"); + factory.setLdapAuthoritiesPopulator(authorities); + return factory.createAuthenticationManager(); + } + + @Bean + LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) { + String groupSearchBase = "ou=groups"; + DefaultLdapAuthoritiesPopulator authorities = + new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase); + authorities.setGroupSearchFilter("member={0}"); + return authorities; + } +} +---- + .`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration public class SecurityConfig extends VaadinWebSecurity { @@ -550,6 +686,9 @@ public class SecurityConfig extends VaadinWebSecurity { } ---- +-- +<1> `VaadinSecurityConfigurer` example here configure https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html#servlet-authentication-ldap-embedded[embedded UnboundID LDAP server]. You can also configure https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html#servlet-authentication-ldap-contextsource[LDAP ContextSource] to connect to other LDAP server. + Remember to add the corresponding LDAP client dependency to the project: .`pom.xml` diff --git a/articles/hilla/lit/guides/security/spring-stateless.adoc b/articles/hilla/lit/guides/security/spring-stateless.adoc index c8c3b848f0..7b8f21b986 100644 --- a/articles/hilla/lit/guides/security/spring-stateless.adoc +++ b/articles/hilla/lit/guides/security/spring-stateless.adoc @@ -57,12 +57,24 @@ Add the following dependencies to the project's [filename]`pom.xml` file: Modify the Spring Security configuration and use the [methodname]`VaadinWebSecurity.setStatelessAuthentication()` method to set up stateless authentication, as follows: +[.example] +-- + +.`SecurityConfigurer.java` +[source,java] +---- + +include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/security/stateless/SecurityConfigurer.java[tags="stateless-configure"] +---- .`SecurityConfig.java` [source,java] ---- + include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/security/stateless/SecurityConfig.java[tags="stateless-configure"] ---- + +-- <1> Sets the secret key that's used for signing and verifying the JWT. <2> Sets the JWT signature algorithm. The key length should match the algorithm chosen. For example, the HS256 algorithm used here requires a 32-byte secret key. <3> Sets the issuer JWT claim – a string or a URL that identifies your application. @@ -113,9 +125,36 @@ After completing the previous steps, your application should be using stateless By default, the JWT and cookies expire thirty minutes after the last server request. You can customize the expiration period by using an additional duration argument for the configuration method like so: +[.example] +-- + +.`SecurityConfig.java` +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + ... + http.with(new VaadinStatelessSecurityConfigurer<>(), + cfg -> cfg.withSecretKey().secretKey( + new SecretKeySpec(Base64.getDecoder().decode("..."), + JwsAlgorithms.HS256) + ).and().issuer("com.example.application") + .expiresIn(3600) // The JWT lifetime in seconds + ); + return http.build(); + } +} +---- + .`SecurityConfig.java` [source,java] ---- + @EnableWebSecurity @Configuration public class SecurityConfig extends VaadinWebSecurity { @@ -132,4 +171,6 @@ public class SecurityConfig extends VaadinWebSecurity { } ---- +-- + // end::content[] diff --git a/articles/hilla/lit/guides/upgrading/index.adoc b/articles/hilla/lit/guides/upgrading/index.adoc index cec5bb8d33..c5e55b09e4 100644 --- a/articles/hilla/lit/guides/upgrading/index.adoc +++ b/articles/hilla/lit/guides/upgrading/index.adoc @@ -672,13 +672,24 @@ You'll need to upgrade Spring to the latest versions, including the starter pare === Deprecation -Deprecated `VaadinWebSecurityConfigurerAdapter` was removed since Spring no longer has the `WebSecurityConfigurerAdapter` class. Use instead the `VaadinWebSecurity` base class for your security configuration. Below is an example of this: +Deprecated `VaadinWebSecurityConfigurerAdapter` was removed since Spring no longer has the `WebSecurityConfigurerAdapter` class. Use instead the `VaadinSecurityConfigurer` class or [deprecated:com.vaadin:vaadin@V24.9]#`VaadinWebSecurity` base class# for your security configuration. Below is an example of this: + +[.example] +-- + +[source,java] +---- + +include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/security/stateless/SecurityConfigurer.java[tags="stateless-configure"] +---- [source,java] ---- + include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/security/stateless/SecurityConfig.java[tags="stateless-configure"] ---- +-- In this example, `AuthenticationManagerBuilder` -- used in Spring Boot 2.x -- is replaced by `UserDetailsService`. And `http.authorizeRequests().antMatchers()` are replaced by `http.authorizeHttpRequests().requestMatchers()`. diff --git a/articles/upgrading/index.adoc b/articles/upgrading/index.adoc index c9be48e350..52b88ff5e8 100644 --- a/articles/upgrading/index.adoc +++ b/articles/upgrading/index.adoc @@ -143,10 +143,79 @@ You'll need to upgrade Spring to the latest versions, including the starter pare === Deprecation -The deprecated `VaadinWebSecurityConfigurerAdapter` class was removed since Spring no longer includes the `WebSecurityConfigurerAdapter` class. Use instead the `VaadinWebSecurity` base class for your security configuration. Below is an example of this: +The deprecated `VaadinWebSecurityConfigurerAdapter` class was removed since Spring no longer includes the `WebSecurityConfigurerAdapter` class. Use instead the `VaadinSecurityConfigurer` class or [deprecated:com.vaadin:vaadin@V24.9]#`VaadinWebSecurity` base class# for your security configuration. Below is an example of this: + +[.example] +-- + +[source,java] +---- + +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + /** + * Delegating the responsibility of general configuration + * of HTTP security to the VaadinSecurityConfigurer. + * + * It's configuring the following: + * - Vaadin's CSRF protection by ignoring internal framework requests, + * - default request cache, + * - ignoring public views annotated with @AnonymousAllowed, + * - restricting access to other views/endpoints, and + * - enabling ViewAccessChecker authorization. + */ + + // You can add any possible extra configurations of your own + // here - the following is just an example: + http.rememberMe(customizer -> customizer.alwaysRemember(false)); + + // Configure your static resources with public access before calling + // VaadinSecurityConfigurer.vaadin() as it adds final anyRequest matcher + http.authorizeHttpRequests(auth -> { + auth.requestMatchers("/admin-only/**").hasAnyRole("admin") + .requestMatchers("/public/**").permitAll(); + }); + + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { + // This is important to register your login view to the + // view access checker mechanism: + configurer.loginView(LoginView.class); + }); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * Demo UserDetailsManager which only provides two hardcoded + * in-memory users and their roles. + * This shouldn't be used in real-world applications. + */ + @Bean + public UserDetailsService userDetailsService( + PasswordEncoder passwordEncoder) { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager.createUser(User.withUsername("user") + .password(passwordEncoder.encode("userPass")) + .roles("USER").build()); + manager.createUser(User.withUsername("admin") + .password(passwordEncoder.encode("adminPass")) + .roles("USER", "ADMIN").build()); + return manager; + } +} +---- [source,java] ---- + @EnableWebSecurity @Configuration public class SecurityConfig extends VaadinWebSecurity { @@ -215,6 +284,7 @@ public class SecurityConfig extends VaadinWebSecurity { } ---- +-- In the example here, `AuthenticationManagerBuilder` -- used in Spring Boot 2 -- is replaced by `UserDetailsService`. Also, `http.authorizeRequests().antMatchers()` is replaced with `http.authorizeHttpRequests(auth -> auth.requestMatchers())`. @@ -224,7 +294,7 @@ If the application is using Spring Security 5, the default behavior is for the ` In Spring Security 6, users now must explicitly save the `SecurityContext` to the `SecurityContextRepository`. -You can return the old behavior by adding to [methodname]`configure(HttpSecurity http)` the line `http.securityContext ( (securityContext) -> securityContext.requireExplicitSave( false ) );`. +You can return the old behavior by adding to [methodname]`SecurityFilterChain securityFilterChain(HttpSecurity http)` (or [methodname]`configure(HttpSecurity http)` with `VaadinWebSecurity`) the line `http.securityContext ( (securityContext) -> securityContext.requireExplicitSave( false ) );`. For more information see https://docs.spring.io/spring-security/reference/5.8/migration/servlet/session-management.html#_require_explicit_saving_of_securitycontextrepository[Servlet Migrations - Session Management]. diff --git a/src/main/java/com/vaadin/demo/fusion/security/authentication/SecurityConfigDemo.java b/src/main/java/com/vaadin/demo/fusion/security/authentication/SecurityConfigDemo.java index 0a8ecbc4cc..488b27aa37 100644 --- a/src/main/java/com/vaadin/demo/fusion/security/authentication/SecurityConfigDemo.java +++ b/src/main/java/com/vaadin/demo/fusion/security/authentication/SecurityConfigDemo.java @@ -1,27 +1,38 @@ package com.vaadin.demo.fusion.security.authentication; -import com.vaadin.flow.spring.security.VaadinWebSecurity; +import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; +import com.vaadin.flow.spring.security.VaadinSecurityConfigurer; + 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.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.UserDetailsManager; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.SecurityFilterChain; /** * An example code for demoing the Spring Security configuration, shouldn't * affect the doc application itself. */ -public class SecurityConfigDemo extends VaadinWebSecurity { +//@EnableWebSecurity +//@Configuration +//@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +public class SecurityConfigDemo { - // tag::login[] - @Override - protected void configure(HttpSecurity http) throws Exception { - super.configure(http); - setLoginView(http, "/login"); + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // tag::public-resources[] + http.authorizeHttpRequests(auth -> auth + .requestMatchers("/images/**").permitAll()); + // end::public-resources[] + // tag::login[] + http.with(VaadinSecurityConfigurer.vaadin(), configurer -> configurer.loginView("/login")); + // end::login[] + return http.build(); } - // end::login[] @Bean public UserDetailsManager userDetailsService() { @@ -32,12 +43,4 @@ public UserDetailsManager userDetailsService() { User.withUsername("user").password("{noop}password") .roles("USER").build()); } - - // tag::public-resources[] - @Override - public void configure(WebSecurity web) throws Exception { - super.configure(web); - web.ignoring().requestMatchers(new AntPathRequestMatcher("/images/**")); - } - // end::public-resources[] } diff --git a/src/main/java/com/vaadin/demo/fusion/security/stateless/SecurityConfigurer.java b/src/main/java/com/vaadin/demo/fusion/security/stateless/SecurityConfigurer.java new file mode 100644 index 0000000000..e376481600 --- /dev/null +++ b/src/main/java/com/vaadin/demo/fusion/security/stateless/SecurityConfigurer.java @@ -0,0 +1,59 @@ +package com.vaadin.demo.fusion.security.stateless; + +import javax.crypto.spec.SecretKeySpec; +import java.util.Base64; + +import org.springframework.beans.factory.annotation.Value; +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.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; +import org.springframework.security.web.SecurityFilterChain; + +import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration; +import com.vaadin.flow.spring.security.VaadinSecurityConfigurer; +import com.vaadin.flow.spring.security.stateless.VaadinStatelessSecurityConfigurer; + +// tag::stateless-configure[] +@EnableWebSecurity +@Configuration +@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class) +@Profile("this-is-just-a-demo-class") // hidden-source-line +public class SecurityConfigurer { + + @Value("${my.app.auth.secret}") + private String authSecret; + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // end::stateless-configure[] + http.authorizeHttpRequests(auth -> auth + .requestMatchers("/admin-only/**").hasAnyRole("admin") + .requestMatchers("/public/**").permitAll() + ); + + // tag::stateless-configure[] + // Disable creating and using sessions in Spring Security + http.sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + // Register your login view to the view access checker mechanism + http.with(VaadinSecurityConfigurer.vaadin(), + configurer -> configurer.loginView("/login")); + + // Enable stateless authentication + http.with(new VaadinStatelessSecurityConfigurer<>(), + cfg -> cfg.withSecretKey().secretKey( + new SecretKeySpec(Base64.getDecoder().decode(authSecret), // <1> + JwsAlgorithms.HS256) // <2> + ).and().issuer("com.example.application") // <3> + ); + + return http.build(); + } +} +// end::stateless-configure[]