Skip to content

feat: Document VaadinSecurityConfigurer #4420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: latest
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions articles/building-apps/security/add-login/flow.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -70,14 +70,19 @@ To instruct Spring Security to use your login view, modify your security configu
----
@EnableWebSecurity
@Configuration
class SecurityConfig extends VaadinWebSecurity {
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Configure Vaadin's security using VaadinSecurityConfigurer
http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
// tag::snippet[]
setLoginView(http, LoginView.class);
configurer.loginView(LoginView.class);
// end::snippet[]
});

return http.build();
}
...
}
Expand Down Expand Up @@ -133,23 +138,32 @@ Inside this package, create a [classname]`SecurityConfig` class:
.SecurityConfig.class
[source,java]
----
import com.vaadin.flow.spring.security.VaadinWebSecurity;
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
class SecurityConfig extends VaadinWebSecurity {
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
@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
Expand Down Expand Up @@ -198,8 +212,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();
Expand Down Expand Up @@ -231,14 +245,19 @@ Modify [classname]`SecurityConfig` to reference the `LoginView`:
----
@EnableWebSecurity
@Configuration
class SecurityConfig extends VaadinWebSecurity {
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Configure Vaadin's security using VaadinSecurityConfigurer
http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
// tag::snippet[]
setLoginView(http, LoginView.class);
configurer.loginView(LoginView.class);
// end::snippet[]
});

return http.build();
}
...
}
Expand Down
53 changes: 36 additions & 17 deletions articles/building-apps/security/add-login/hilla.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> authorities
) {
}
Expand Down Expand Up @@ -160,14 +160,19 @@ To instruct Spring Security to use your login view, modify your security configu
----
@EnableWebSecurity
@Configuration
class SecurityConfig extends VaadinWebSecurity {
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Configure Vaadin's security using VaadinSecurityConfigurer
http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
// tag::snippet[]
setLoginView(http, "/login");
configurer.loginView("/login");
// end::snippet[]
});

return http.build();
}
...
}
Expand Down Expand Up @@ -223,23 +228,32 @@ Inside this package, create a [classname]`SecurityConfig` class:
.SecurityConfig.class
[source,java]
----
import com.vaadin.flow.spring.security.VaadinWebSecurity;
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
class SecurityConfig extends VaadinWebSecurity {
@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");
});

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
return http.build();
}

@Bean
Expand Down Expand Up @@ -274,7 +288,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<String> authorities) {
}

Expand Down Expand Up @@ -395,14 +409,19 @@ Modify [classname]`SecurityConfig` to reference the new login view:
----
@EnableWebSecurity
@Configuration
class SecurityConfig extends VaadinWebSecurity {
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Configure Vaadin's security using VaadinSecurityConfigurer
http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
// tag::snippet[]
setLoginView(http, "/login");
configurer.loginView("/login");
// end::snippet[]
});

return http.build();
}
...
}
Expand Down
1 change: 1 addition & 0 deletions articles/building-apps/security/add-login/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ This is a minimal implementation of a security configuration class:
----
@EnableWebSecurity
@Configuration
// TODO: use VaadinSecurityConfigurer
class SecurityConfig extends VaadinWebSecurity {

@Override
Expand Down
3 changes: 2 additions & 1 deletion articles/building-apps/security/add-logout/flow.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ By default, users are redirected to the root URL (`/`) after logging out. To cha
----
@EnableWebSecurity
@Configuration
// TODO: use VaadinSecurityConfigurer
class SecurityConfig extends VaadinWebSecurity {

@Override
Expand Down Expand Up @@ -141,7 +142,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[]

Expand Down
1 change: 1 addition & 0 deletions articles/building-apps/security/add-logout/hilla.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ By default, users are redirected to the root URL (`/`) after logging out. To cha
----
@EnableWebSecurity
@Configuration
// TODO: use VaadinSecurityConfigurer
class SecurityConfig extends VaadinWebSecurity {

@Override
Expand Down
5 changes: 3 additions & 2 deletions articles/building-apps/security/protect-services/flow.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ To enable method security, add [annotationname]`@EnableMethodSecurity` to your s
@EnableMethodSecurity
// end::snippet[]
@Configuration
// TODO: use VaadinSecurityConfigurer
class SecurityConfig extends VaadinWebSecurity {

@Override
Expand Down Expand Up @@ -180,10 +181,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);
Expand Down
11 changes: 6 additions & 5 deletions articles/building-apps/security/protect-services/hilla.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -68,7 +68,7 @@ public class ProtectedService {
// tag::snippet[]
@RolesAllowed(Roles.ADMIN) // <2>
// end::snippet[]
public void callableByAdminsOnly() {
public void callableByAdminsOnly() {
}
}
----
Expand All @@ -91,7 +91,7 @@ public class ProtectedService {
public void callableByAllUsers() {
}

public void callableByAdminsOnly() {
public void callableByAdminsOnly() {
}
}
----
Expand All @@ -104,7 +104,7 @@ public class ProtectedService {
[NOTE]
The following assumes you have already set up Spring method security. For details, see the <<flow#,Protect Flow Services>> 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:

Expand Down Expand Up @@ -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.


Expand Down Expand Up @@ -171,6 +171,7 @@ Then update the [methodname]`userDetailsManager()` method of the [classname]`Sec
----
@EnableWebSecurity
@Configuration
// TODO: use VaadinSecurityConfigurer
class SecurityConfig extends VaadinWebSecurity {
...

Expand Down
14 changes: 8 additions & 6 deletions articles/building-apps/security/protect-views/flow.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
----
<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.

Expand All @@ -145,8 +145,9 @@
----
@EnableWebSecurity
@Configuration
// TODO: use VaadinSecurityConfigurer
class SecurityConfig extends VaadinWebSecurity {

@Bean
static NavigationAccessControlConfigurer navigationAccessControlConfigurer( // <1>
CustomAccessChecker customAccessChecker) {
Expand All @@ -159,7 +160,7 @@
<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.

Check warning on line 163 in articles/building-apps/security/protect-views/flow.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Will] Avoid using 'will'. Raw Output: {"message": "[Vaadin.Will] Avoid using 'will'.", "location": {"path": "articles/building-apps/security/protect-views/flow.adoc", "range": {"start": {"line": 163, "column": 98}}}, "severity": "WARNING"}

[NOTE]
To enable the built-in annotated view access checker, call `NavigationAccessControlConfigurer.withAnnotatedViewAccessChecker()`.
Expand Down Expand Up @@ -294,6 +295,7 @@
----
@EnableWebSecurity
@Configuration
// TODO: use VaadinSecurityConfigurer
class SecurityConfig extends VaadinWebSecurity {
...

Expand Down Expand Up @@ -373,16 +375,16 @@
@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>
Expand Down
Loading
Loading