Navigation Access Control
Navigation Access Control is a Flow security feature in which you can allow or deny the navigation to a certain view based on different and pluggable rules. When active, the access control intercepts navigation events, evaluates all configured rules. It then decides whether the target view should be rendered, or if the access should be denied.
Navigation Access Control is an improvement and a replacement of the ViewAccessChecker
mechanism. It provides annotation-based view security. It also expands the capabilities of the former access checker by introducing a new path-based check and by allowing custom rules, which are set by implementing the NavigationAccessChecker
interface.
Architecture
The Navigation Access Control mechanism is composed of three fundamental components: NavigationAccessControl
; NavigationAccessChecker
; and AccessCheckDecisionResolver
.
NavigationAccessControl
is the entry point for navigation security. It listens for navigation events, and it evaluates all security rules. Based on the results, it allows or denies access to the target view.
NavigationAccessChecker
interface implementers represent the security rules that are evaluated during a navigation event.
AccessCheckDecisionResolver
has the responsibility of taking the final decision to allow or deny a navigation, based on the result of the evaluation of the security rules.
NavigationAccessControl
can be configured to evaluate multiple NavigationAccessChecker
. For example, you can enable annotation-based view access check and path-based access check. During a navigation request, both checkers are evaluated against the current NavigationContext
and they produce an AccessCheckResult
.
The AccessCheckDecisionResolver
analyzes the results and provides the final decision. The access control uses that decision to proceed with the navigation or reroute to the login view or to an error page.
Navigation Access Checkers
Vaadin Flow provides two implementations of NavigationAccessChecker
interface: AnnotatedViewAccessChecker
and RoutePathAccessChecker
.
AnnotatedViewAccessChecker
AnnotatedViewAccessChecker
works in the same way as the former ViewAccessChecker
. It looks for security annotation (@AnonymousAllowed
, @PermitAll
, @RolesAllowed
, or @DenyAll
) on the target view class. It evaluates them against the current navigation context. AnnotatedViewAccessChecker
delegates the check to an instance of AccessAnnotationChecker
, that can be extended to provide custom rules.
RoutePathAccessChecker
RoutePathAccessChecker
evaluates instead the path used to navigate to a view. It requires an implementation of AccessPathChecker
, that is responsible for the effective path verification.
The Vaadin Spring add-on offers an AccessPathChecker
implementation based on Spring Security configuration. The path used to navigate to the target view is evaluated using the Spring WebInvocationPrivilegeEvaluator
component, that applies the rules defined by the request matchers configured for Spring Security.
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.
@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 { }
public class SecurityConfig extends VaadinWebSecurity {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers(antMatchers("/admin")).hasAnyRole(ROLE_ADMIN)
.requestMatchers(antMatchers("/protected/**")).authenticated()
.requestMatchers(antMatchers("/special/**"))
.access((authentication, object) -> {
// Custom authorization logic
return new AuthorizationDecision(isPremiumUser(authentication));
})
.requestMatchers(antMatchers("/"))
.permitAll()
);
}
}
Integration with other security frameworks can be accomplished similarly by providing a specific AccessPathChecker
implementation.
Implementing a Custom Navigation Access Checker
If the above checkers aren’t enough to fulfill the project requirements, a custom NavigationAccessChecker
can be implemented. NavigationAccessChecker
has a single method that takes a NavigationContext
as input and produces an AccessCheckResult
as output.
The NavigationContext
provides information about the current navigation, such as target view class and location, potential authenticated user and its roles.
The AccessCheckResult
holds the decision taken by the checker — and potentially an informative reason about the decision. The decision can be ALLOW
, DENY
, NEUTRAL
, or REJECT
. ALLOW
and DENY
are self-explanatory. NEUTRAL
means that the checker doesn’t have enough information to make a decision, and it delegates the responsibility to the other configured checkers. REJECT
has the same meaning of DENY
, but it’s used to signal a critical situation where the checker cannot make the decision because of a system configuration error.
In development mode, a rejection is handled by throwing an exception so that the situation can be detected immediately. The AccessCheckResult
object is created by calling its factory methods allow
, deny
, neutral
, or reject
. In alternative, similar methods can be called on NavigationContext
instance.
For example, a navigation access check implementation that allows access to a voting view only when voting has been marked as open, could look like the following code:
public class VotingNavigationAccessChecker implements NavigationAccessChecker {
@Override
public AccessCheckResult check(NavigationContext context) {
AccessCheckResult result;
if (VotingView.class.equals(context.getNavigationTarget())) {
if (context.getParameters().getParameterNames()
.contains("eventId")) {
// Allow access only if the event the user is going to express
// its vote is actually open for voting, otherwise deny it.
result = context.getParameters().getInteger("eventId")
.filter(this::isVotingOpen)
.map(unused -> AccessCheckResult.allow())
.orElseGet(() -> AccessCheckResult.deny("Voting closed"));
} else {
// Critical error, the navigation does not carry a required
// information. Probably a misconfigured route annotation or
// a broken link in another view
result = AccessCheckResult.reject("Event identifier not provided");
}
} else {
// Not a navigation to voting view, let other checkers take the
// decision
result = AccessCheckResult.neutral();
}
return result;
}
private boolean isVotingOpen(int eventId) {
// Fetch the event from data storage and check if voting is currently
// opened (implementation omitted in this example)
return false;
}
}
Navigation Error Handling Phase
When implementing a custom NavigationAccessChecker
, it’s important to know that the checker is called also when the navigation is rerouted to an error handler view. The NavigationContext
class provides the isErrorHandling()
method to verify if the call happens during a navigation error handling phase. In this case, a custom checker would probably abstain from making a decision for the internal navigation, by returning a NEUTRAL
result.
@Override
public AccessCheckResult check(NavigationContext context) {
if (context.isErrorHandling()) {
return AccessCheckResult.neutral();
}
....
}
Decision Resolver
The AccessCheckDecisionResolver
component is responsible for analyzing the results provided by the navigation access checkers, and for computing the final decision to grant or deny access to a view.
The default implementation makes the decision by applying the following rules:
Navigation Access Checkers Results | Decision |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
As shown in the above table, if the navigation access checkers do not agree on the decision — excluding neutral votes — the default resolver rejects the navigation, causing an exception to be thrown in development mode. This situation happens usually because of invalid security configurations. For example, a view may be annotated with @AnnonymousAllowed
, but the Spring Security configuration has a request matcher for the view path that grants access only to an authenticated user.
In error handling phase, almost the same rule applies with the exception that if all the results are NEUTRAL
, the access is granted.
The reason is that, in this case, the target of the navigation is supposed to be an error handler component that do not expose sensible information.
Navigation Access Checkers Results | Decision |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If the default implementation doesn’t fit the requirements of the project, you can implement your own AccessCheckDecisionResolver
. The interface defines a single method that takes as input the results computed by the navigation access checker and the current navigation context. Its output is an AccessCheckResult
.
In the following example, the decision resolver allows access to the view if at least one of the checkers provided an ALLOW
result:
public class CustomDecisionResolver
implements AccessCheckDecisionResolver {
@Override
public AccessCheckResult resolve(List<AccessCheckResult> results, NavigationContext context) {
if (results.stream().anyMatch(
r -> r.decision() == AccessCheckDecision.ALLOW)) {
return AccessCheckResult.allow();
}
return AccessCheckResult.deny("Access denied");
}
}
Configuration
Navigation Access Control is enabled automatically on Spring Boot projects using VaadinWebSecurity
. To enable the feature in plain Java projects, follow the specific documentation page.
This section shows how to customize Navigation Access Control for both Spring and plain Java projects.
Spring Projects
When using VaadinWebSecurity
as base class for configuring Spring Security, the Navigation Access Control feature is enabled by default. You can disable it by overriding the enableNavigationAccessControl
method in VaadinWebSecurity
to return false
. For backward compatibility, only the AnnotatedViewAccessChecker
is active by default.
For a fine-grained configuration, you can expose a bean of type NavigationAccessControlConfigurer
. 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 VaadinWebSecurity is not used, exposing the 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 NavigationAccessChecker
beans that extend VotingNavigationAccessChecker
. It also provides a custom decision resolver.
@Bean
NavigationAccessControlConfigurer navigationAccessControlConfigurerCustomizer() {
return new NavigationAccessControlConfigurer()
.withRoutePathAccessChecker() // (1)
.withNavigationAccessChecker(new CustomChecker()) // (2)
.withAvailableNavigationAccessCheckers(checker ->
checker instanceof VotingNavigationAccessChecker // (3)
)
.withDecisionResolver(new CustomDecisionResolver()); // (4)
}
-
Activates the route path access checker. The implementation is provided by the Vaadin Spring module and exposed as a bean.
-
Adds a custom navigation access checker, by creating a new instance.
-
Activates all registered navigation access checker bean, that matches the predicate.
-
Sets a custom decision resolver.
Important
|
If you add the bean definition method in a configuration class extending VaadinWebSecurity , the method must be declared static , to prevent bootstrap errors because of circular dependencies in bean definitions.
|
class SecurityConfig extends VaadinWebSecurity {
@Bean
static NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
return new NavigationAccessControlConfigurer()
.withRoutePathAccessChecker()
.withDecisionResolver(new CustomDecisionResolver());
}
}
Consult the NavigationAccessControlConfigurer
Javadoc for more information on how navigation access control can be customized.
Plain Java Projects
Navigation Access Control settings in plain Java projects is documented in this separate page.
To apply custom settings, you need to create the NavigationAccessControl
instance, providing the list of navigation access checkers and the decision resolver to be used.
It’s important to know that for plain Java projects, there is no out-of-the-box support for route path access checking. However, as described earlier, it is possible to implement a custom AccessPathChecker
that integrates with the security framework used in the project.
Here is how you can implement the example of the previous paragraph, in a non-Spring Java project:
public class FooBarSecurityAccessPathChecker implements AccessPathChecker {
@Override
public boolean hasAccess(
String path, Principal principal, Predicate<String> roleChecker) {
// implementation omitted
}
}
public class NavigationAccessCheckerInitializer implements VaadinServiceInitListener {
public NavigationControlAccessCheckerInitializer() {
accessControl = new NavigationAccessControl(List.of(
new RoutePathAccessChecker(new FooBarSecurityAccessPathChecker()),
new CustomChecker(),
new VotingNavigationAccessChecker()
), new CustomDecisionResolver());
accessControl.setLoginView(LoginView.class);
}
}
4164EB30-201D-4FD3-940F-03630A752AD5