Setting Up Security
This page describes how to set up view access control in a plain servlet application.
Securing a plain servlet application takes the following:
-
a way to authenticate users, and to set the user on the request;
-
a log-in view, and a way to log out;
-
a
VaadinServiceInitListenerthat enables navigation access control, registered underMETA-INF/services; and -
an access annotation on each view class and on each layout class.
Authenticating Users
Navigation access control reads the current user from HttpServletRequest.getUserPrincipal(), and checks roles with HttpServletRequest.isUserInRole(). The following example authenticates users against the application’s own user store, such as a database table, and keeps the authenticated user in the HTTP session. If the servlet container authenticates the users instead, see Container-Managed Authentication.
The user is kept in the session as a Principal that also holds the user’s roles:
Source code
AppUser.java
AppUser.javapublic record AppUser(String name, Set<String> roles)
implements Principal, Serializable {
@Override
public String getName() {
return name;
}
}A servlet filter wraps each request of an authenticated user, so that the request returns the user and checks the user’s roles:
Source code
AuthenticationFilter.java
AuthenticationFilter.java@WebFilter(urlPatterns = "/*", asyncSupported = true) 1
public class AuthenticationFilter extends HttpFilter {
static final String USER_ATTRIBUTE = AppUser.class.getName();
@Override
protected void doFilter(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpSession session = request.getSession(false);
AppUser user = session == null ? null
: (AppUser) session.getAttribute(USER_ATTRIBUTE);
if (user == null) {
chain.doFilter(request, response);
return;
}
chain.doFilter(new HttpServletRequestWrapper(request) {
@Override
public Principal getUserPrincipal() {
return user;
}
@Override
public boolean isUserInRole(String role) {
return user.roles().contains(role);
}
}, response);
}
}-
Enables asynchronous processing, which push needs for transports that suspend the request, such as long polling.
SecurityUtils logs users in by storing the user in the session, and logs them out by invalidating the session:
Source code
SecurityUtils.java
SecurityUtils.javapublic class SecurityUtils {
public static boolean isAuthenticated() {
VaadinServletRequest request = VaadinServletRequest.getCurrent();
return request != null && request.getUserPrincipal() != null;
}
public static boolean authenticate(String username, String password) {
AppUser user = findUser(username, password); 1
if (user == null) {
return false;
}
HttpServletRequest request = VaadinServletRequest.getCurrent()
.getHttpServletRequest(); 2
request.changeSessionId(); 3
request.getSession().setAttribute(
AuthenticationFilter.USER_ATTRIBUTE, user);
return true;
}
public static void logout() {
UI.getCurrentOrThrow().getPage().setLocation("./"); 4
VaadinSession.getCurrent().getSession().invalidate();
}
// ...
}-
Checks the credentials against the user store of the application, for example by loading the user from a database and comparing a password hash. Returns
nullif they don’t match. Never store passwords in plain text. -
The current request is available only in threads that handle a request, so call
authenticate()from a listener, not from a background thread. -
Gives the session a new ID, to protect against session fixation.
-
Redirects to the root of the application. The URL is relative, because Vaadin sets the base URL of the page to the root of the application, so it also works when the application is deployed under a context path.
The filter sets the user on the requests that follow the log-in request, not on the log-in request itself. The log-in view therefore reloads the page after a successful log-in, as shown in Log-in View.
Role names in @RolesAllowed are passed unchanged to isUserInRole(). With the filter above, @RolesAllowed("ADMIN") matches a user whose roles contain ADMIN.
Log-in View
The log-in view is where navigation access control sends users who aren’t authenticated when they try to open a protected view. The following example uses the Login component, but any view works:
Source code
LoginView.java
LoginView.java@Route(value = "login", autoLayout = false)
@PageTitle("Login")
public class LoginView extends VerticalLayout {
public LoginView() {
LoginForm login = new LoginForm();
login.addLoginListener(event -> {
if (SecurityUtils.authenticate(event.getUsername(),
event.getPassword())) {
UI.getCurrentOrThrow().getPage().setLocation("./"); 1
} else {
login.setError(true);
}
});
add(login);
}
}-
Reloads the page at the root of the application, so that the following requests carry the authenticated user.
The log-in view doesn’t need an access annotation. Navigation access control always allows navigation to the configured log-in view.
Logging Out
The following example shows a log-out button in the header of the main layout:
Source code
MainLayout.java
MainLayout.java@AnonymousAllowed 1
public class MainLayout extends AppLayout {
public MainLayout() {
H1 logo = new H1("Vaadin CRM");
logo.addClassName("logo");
HorizontalLayout header;
if (SecurityUtils.isAuthenticated()) {
Button logout = new Button("Logout", click ->
SecurityUtils.logout());
header = new HorizontalLayout(logo, logout);
} else {
header = new HorizontalLayout(logo);
}
// Other page components omitted.
addToNavbar(header);
}
}-
The access annotations of a layout are checked in addition to those of the views inside it. This layout hosts both public and protected views, so it allows anonymous access.
Enabling Navigation Access Control
A VaadinServiceInitListener enables NavigationAccessControl for all UIs:
Source code
NavigationAccessCheckerInitializer.java
NavigationAccessCheckerInitializer.javaimport com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;
import com.vaadin.flow.server.auth.NavigationAccessControl;
import org.vaadin.example.views.login.LoginView;
public class NavigationAccessCheckerInitializer implements VaadinServiceInitListener {
private NavigationAccessControl accessControl;
public NavigationAccessCheckerInitializer() {
accessControl = new NavigationAccessControl(); 1
accessControl.setLoginView(LoginView.class); 2
}
@Override
public void serviceInit(ServiceInitEvent serviceInitEvent) {
serviceInitEvent.getSource().addUIInitListener(uiInitEvent -> {
uiInitEvent.getUI().addBeforeEnterListener(accessControl); 3
});
}
}-
Creates the navigation access control. It’s enabled by default.
-
Sets the view to which users who aren’t authenticated are redirected.
-
Adds the access control as a
BeforeEnterListenerto every UI, so that it checks every navigation.
To customize the access checkers or the decision resolver, see Navigation Access Control.
Vaadin loads the listener with the Java Service Provider Interface (SPI). Create a file named com.vaadin.flow.server.VaadinServiceInitListener in the META-INF/services directory of the application resources, and put the fully qualified name of the listener class into it, for example:
Source code
src/main/resources/META-INF/services/com.vaadin.flow.server.VaadinServiceInitListener
src/main/resources/META-INF/services/com.vaadin.flow.server.VaadinServiceInitListenerorg.vaadin.example.security.NavigationAccessCheckerInitializerFor more information, see VaadinServiceInitListener.
Access Annotations
Annotate each view class and each layout class with an access annotation, as described in Annotation-Based Access Control. The log-in view is the only exception.
The annotation-based access checks need an additional dependency. @AnonymousAllowed is part of Vaadin, but @PermitAll, @RolesAllowed, and @DenyAll are part of the Jakarta Annotations API, which Vaadin doesn’t add to your project. Add it to the dependencies in your pom.xml file:
Source code
XML
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>3.0.0</version>
</dependency>If your application server provides the Jakarta Annotations API, as Jakarta EE servers do, add the dependency with the provided scope instead.
With this setup, users who aren’t authenticated are redirected to the log-in view when they try to navigate to a view that requires authentication. For what authenticated users see when they navigate to a view for which they have no permission, see Access-Denied & Not-Found Views.
5D3E1BB8-9D7C-4FAD-9381-8DBB3C65F6A8