Protect Views
In this guide, you’ll learn how to control access to specific views both declaratively and programmatically. A hands-on mini-tutorial at the end will help you apply these concepts in a real Vaadin application.
Declarative View Security
The easiest way to grant or deny access to a Flow view is to use annotations. The following annotations are supported:
-
@AnonymousAllowed
allows access to unauthenticated users. -
@PermitAll
allows any authenticated user to navigate to the view. -
@RolesAllowed
allows users having the roles specified in the annotation value to navigate to the view. -
@DenyAll
prevents everyone from navigating to the view. By default, all Flow views are inaccessible unless explicitly annotated.
Note
|
The @AnonymousAllowed annotation is a Vaadin-specific annotation; the others are Jakarta annotations (JSR-250). The Spring Security annotations @Secured and @PreAuthorize are not supported on views.
|
The following example uses @AnonymousAllowed
to allow all users — both authenticated and unauthenticated — to access the view:
@Route("public")
@PageTitle("Public View")
@AnonymousAllowed
public class PublicView extends Main {
...
}
In business applications that require authentication for everything, you typically have to do this on the login view.
The next example uses @PermitAll
to allow only authenticated users — with any role — to access the view:
@Route("private")
@PageTitle("Private View")
@PermitAll
public class PrivateView extends Main {
...
}
This example uses @RolesAllowed
to allow users with the ADMIN
role to navigate to the view:
@Route("admin")
@PageTitle("Admin View")
@RolesAllowed("ADMIN")
public class AdminView extends Main {
...
}
Important
|
Router layouts are also protected
When protecting views, ensure the router layout also allows access. If a view is accessible but its parent layout is restricted, users will still be blocked.
|
Annotation Inheritance
Security annotations are inherited from the closest superclass that has them. Annotating a subclass overrides any inherited annotations. Interfaces aren’t checked for annotations, only classes.
In the following example, UserListingView
requires the ADMIN
role:
@RolesAllowed("ADMIN")
public abstract class AbstractAdminView extends VerticalLayout {
...
}
@Route(value = "user-listing")
public class UserListingView extends AbstractAdminView {
...
}
While multiple security annotations can be applied to a single view, doing so can cause conflicts and is not recommended. However, if multiple annotations exist on a view, they override each other in this order:
-
@DenyAll
overrides all other annotations; -
@AnonymousAllowed
overrides@RolesAllowed
and@PermitAll
; and -
@RolesAllowed
overrides@PermitAll
.
Programmatic View Security
Vaadin provides API:s for protecting views programmatically. It’s more verbose than using the annotations, but gives you greater control.
Making a Custom Navigation Access Checker
If you need more fine-grained control over how access is granted to views, you can implement a custom NavigationAccessChecker
:
@Component 1
class CustomAccessChecker implements NavigationAccessChecker {
@Override
public AccessCheckResult check(NavigationContext context) {
// Check whether to allow or deny access
}
}
-
Registers the navigation access checker as a singleton Spring bean.
The 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.
Once you’ve made a decision, you have to return an AccessCheckResult
. The AccessCheckResult
determines whether navigation is allowed, denied, or deferred. There are four possible outcomes:
AccessCheckResult.allow()
-
Access is granted.
AccessCheckResult.neutral()
-
The access checker cannot make a decision based on the given navigation information. Another access checker have to make the decision, or access will be denied.
AccessCheckResult.deny()
-
Access is denied.
AccessCheckResult.reject()
-
Access is denied because of a misconfiguration or critical development time error.
Note
| The security annotations are actually enforced by a built-in access checker. |
Enabling a Navigation Access Checker
To enable a custom NavigationAccessChecker
, create a new NavigationAccessControlConfigurer
Spring bean:
@EnableWebSecurity
@Configuration
class SecurityConfig extends VaadinWebSecurity {
@Bean
static NavigationAccessControlConfigurer navigationAccessControlConfigurer( 1
CustomAccessChecker customAccessChecker) {
return new NavigationAccessControlConfigurer()
.withNavigationAccessChecker(customAccessChecker); 2
}
...
}
-
The
@Bean
method must bestatic
to prevent bootstrap errors caused by circular dependencies in bean definitions. -
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.
Note
|
To enable the built-in annotated view access checker, call NavigationAccessControlConfigurer.withAnnotatedViewAccessChecker() .
|
An AccessCheckDecisionResolver
computes the final access decision based on the results of every access checker. The default implementation makes the decision by applying the following rules:
Navigation Access Checkers Results | Decision |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Important
|
The built-in annotated view access checker never returns NEUTRAL . It either grants or denies access.
|
By default, having access checkers both allowing and denying access at the same time is considered a configuration error. This can happen if you combine the built-in annotated view access checker with a custom access checker. If this is what you want, you have to create a custom AccessCheckDecisionResolver
. This, and more, is covered in the Navigation Access Control Reference Guide.
Controlling Access within Views
Sometimes, you want multiple roles to be able to access a view, but limit what they can do within it. For instance, one role may have full read-write access whereas another role has only read-only access. To check the roles of the current user, inject an AuthenticationContext
object into your view:
@Route("")
@PermitAll 1
public class MyView extends Main {
public MyView(AuthenticationContext authenticationContext) {
if (authenticationContext.hasRole("ADMIN")) { 2
// Set up the UI for an admin
} else {
// Set up the UI for normal users
}
}
...
}
-
All authenticated user have access to the view.
-
Administrators can do more inside the view than normal users.
AuthenticationContext
has multiple methods for checking the roles and authorities of the current user. Refer to the Javadoc for more information.
Role Constants
To reduce the risk of typos, consider defining all your application’s roles as constants. In the security
package, make a Roles
class:
public final class Roles {
public static final String ADMIN = "ADMIN";
public static final String USER = "USER";
private Roles() {}
}
Then refer to the constants in annotations and method calls:
@Route
@RolesAllowed(Roles.ADMIN)
public class AdminView extends Main {
...
}
@Route("")
@PermitAll
public class MyView extends Main {
public MyView(AuthenticationContext authenticationContext) {
if (authenticationContext.hasRole(Roles.ADMIN)) {
// Set up the UI for an admin
} else {
// Set up the UI for normal users
}
}
...
}
Try It
In this mini-tutorial, you’ll learn how to add declarative view security to a real Vaadin application. You’ll also learn how to control access programmatically inside a view. The tutorial uses the project from the Add Logout guide. If you haven’t completed that tutorial yet, do it now before proceeding.
Create Role Constants
Create a new class Roles
in the [application package].security
package:
public final class Roles {
public static final String ADMIN = "ADMIN";
public static final String USER = "USER";
private Roles() {
}
}
Then update the userDetailsManager()
method of the SecurityConfig
class to use the new constants:
@EnableWebSecurity
@Configuration
class SecurityConfig extends VaadinWebSecurity {
...
@Bean
public UserDetailsManager userDetailsManager() {
LoggerFactory.getLogger(SecurityConfig.class)
.warn("Using in-memory user details manager!");
var user = User.withUsername("user")
.password("{noop}user")
.roles(Roles.USER)
.build();
var admin = User.withUsername("admin")
.password("{noop}admin")
.roles(Roles.ADMIN)
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
Create Admin View
Create a new class AdminView
in the [application package].todo.ui.view
package:
import com.example.application.security.Roles; 1
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.RolesAllowed;
@Route
@PageTitle("Task Admin")
@Menu(order = 10, icon = "vaadin:wrench", title = "Task Admin")
@RolesAllowed(Roles.ADMIN)
public class AdminView extends Main {
public AdminView() {
setText("Admin View");
}
}
-
Replace with real package.
Now navigate to: http://localhost:8080
Log in as an ADMIN
. You should see Task Admin in the navigation menu. Clicking it should take you to the admin view.
Now log out and log back in as a USER
. The Task Admin menu item should no longer be visible.
Attempt to access http://localhost:8080/admin directly. You should see an access denied error.
Make the Task List Read-Only For Users
So far all authenticated users have been able to add tasks to TodoView
. You’ll now change it so that only users with the ADMIN
role can add tasks. Open TodoView
and change the constructor as follows:
@Route("")
@PageTitle("Task List")
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Task List")
@PermitAll
public class TodoView extends Main {
public TodoView(TodoService todoService, Clock clock,
AuthenticationContext authenticationContext) {
// The rest of the constructor omitted
if (authenticationContext.hasRole(Roles.ADMIN)) {
add(new ViewToolbar("Task List",
ViewToolbar.group(description, dueDate, createBtn))); 1
} else {
add(new ViewToolbar("Task List")); 2
}
add(todoGrid);
}
...
}
-
Only create the toolbar if the user is an
ADMIN
.
Go back to your browser and try the application. The toolbar should only be visible in the Task List when you are logged in as ADMIN
.
Note
|
The description , dueDate , and createBtn components are created for all users, even though they are only used by administrators. Technically this is a waste of memory, but this time it is acceptable since the view is so small.
|
Final Thoughts
Now that your views are secure, the next step is to protect application services. Learn how in the Protect Services guide.