Protect Services
In a Vaadin application using Hilla views, securing @BrowserCallable
services is crucial to prevent unauthorized access. In this guide, you’ll learn how to control access to specific browser-callable services. A hands-on mini-tutorial at the end will help you apply these concepts in a real Vaadin application.
Caution
|
Browser-callable services don’t use Spring method security
Vaadin handles the protection of browser-callable services outside Spring Security. This means that if you inject a browser-callable service into another Java service or Flow view, it will not be protected by default.
|
Securing the Services
Vaadin protects browser-callable services in the same way as it protects Flow views — with annotations.
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.
The following annotations are supported:
-
@AnonymousAllowed
allows access to unauthenticated users. -
@PermitAll
allows any authenticated user to call the service or method. -
@RolesAllowed
allows users having the roles specified in the annotation value to call the service or method. -
@DenyAll
prevents everyone from calling the service or method.
Note
|
The @AnonymousAllowed annotation is a Vaadin-specific annotation; the others are Jakarta annotations (JSR-250).
|
The following example uses @AnonymousAllowed
to allow all users — both authenticated and unauthenticated — to call the service:
@BrowserCallable
@AnonymousAllowed
public class PublicService {
public void doSomething() {
//...
}
}
The following example combines @PermitAll
and @RolesAllowed
to allow all authenticated users to call the service, except for one method that can be called only by administrators:
@BrowserCallable
@PermitAll
public class ProtectedService {
public void callableByAllUsers() { 1
}
@RolesAllowed(Roles.ADMIN) 2
public void callableByAdminsOnly() {
}
}
-
Inherits its access permissions from the
@PermitAll
annotation on the class. -
Overrides the
@PermitAll
annotation on the class, limiting the access.
You can also do the other way around. This example allows only administrators to call the service, except for one method that can be called by all authenticated users:
@BrowserCallable
@RolesAllowed(Roles.ADMIN)
public class ProtectedService {
@PermitAll
public void callableByAllUsers() {
}
public void callableByAdminsOnly() {
}
}
Using Spring Method Security for Browser-Callable Services
Flow services are protected by Spring method security. If you have a protected Flow service that you want to make available for Hilla views, you can do that with some tweaking.
Note
| The following assumes you have already set up Spring method security. For details, see the 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.
To bypass Vaadin’s security check and rely on Spring Security instead, you have to add @AnonymousAllowed
to the service, like this:
@BrowserCallable
@AnonymousAllowed 1
@PreAuthorize("isAuthenticated()") 2
public class ProtectedService {
public MyData callableByAllUsers() {
}
@PreAuthorize("hasRole('" + Roles.ADMIN + "')")
public void callableByAdminsOnly(MyData data) {
}
}
-
Bypass Vaadin’s security check.
-
Enable Spring method security instead.
Note
| Better support for Spring Security is planned for a future version of Vaadin Hilla. See this ticket for more information. |
Warning
|
Don’t configure Spring Security to use JSR-250 annotations
Spring method security has support for the JSR-250 annotations that Vaadin uses to protect browser-callable services. However, Vaadin treats @PermitAll differently than Spring Security. Whereas Vaadin allows access to authenticated users only, Spring Security allows access to all users.
|
Try It
In this mini-tutorial, you’ll learn how to secure browser-callable services in a real Vaadin application. The tutorial uses the project from the Protect Views 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);
}
}
Secure the Todo Service
In an earlier tutorial, you made the task list read-only for users, allowing only admins to create tasks.
Open TodoService
and replace @AnonymousAllowed
with @PermitAll
. Then, add @RolesAllowed
to createTodo()
:
@BrowserCallable
@PermitAll
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class TodoService {
...
@RolesAllowed(Roles.ADMIN)
public void createTodo(String description, @Nullable LocalDate dueDate) {
//...
}
public List<Todo> list(Pageable pageable) {
// ...
}
}
Restart the application and open your browser at: http://localhost:8080
Log in as ADMIN
and create some tasks. Everything should work as before.
Break the Task List
To see that the service is actually protected, you’re going to break the task list. Open src/main/frontend/views/@index.tsx
and change TodoView()
so that isAdmin
is always true
:
...
export default function TodoView() {
const dataProvider = useDataProvider<Todo>({
list: (pageable) => TodoService.list(pageable),
});
const auth = useAuth();
const isAdmin = true; // auth.hasAccess({ rolesAllowed: ["ADMIN"] });
// ..
}
Then go back to the browser, logout, and login as USER
. If you now try to create a task, you should get an error message.
Now change TodoView()
back again.
Final Thoughts
Your Vaadin application now has both secure views and secure services. However, it still uses in-memory authentication. You should replace it with a stronger storage mechanism.
Note
| A guide showing you how to do this in a Vaadin application is planned, but not yet written. In the meantime, refer to the Spring Security Reference Manual. |