Without Spring Security
Vaadin’s built-in security helpers are designed to be used with Spring Security. However, the core of view access control — NavigationAccessControl and the access annotations — doesn’t depend on Spring Security. It reads the current user from HttpServletRequest.getUserPrincipal(), and checks roles with HttpServletRequest.isUserInRole(). It therefore works with any authentication mechanism that sets the user on the request.
This page describes how to use view access control in a Spring Boot application that doesn’t have Spring Security on the class path. The setup is the same as in plain servlet applications, except that the parts are registered as Spring beans.
Vaadin configures navigation access control automatically only when Spring Security is present. Without it, the following aren’t available:
-
VaadinSecurityConfigurerand the HTTP security configuration that it applies; -
AuthenticationContext; -
URL-pattern-based access checking against the Spring Security configuration —
RoutePathAccessCheckerstill works with anAccessPathCheckerof your own; and -
the integrations described on the other pages of this section, such as OAuth2 login.
Securing an application without Spring Security takes the following:
-
a way to authenticate users, and to set the user on the request;
-
a
VaadinServiceInitListenerbean that enables navigation access control; -
a log-in view; and
-
an access annotation on each view class and on each layout class, as described in Annotation-Based Access Control. The log-in view is the only exception.
Setting the User on the Request
How users are authenticated depends on the application. With servlet container authentication, the container sets the user on the request, as described in Container-Managed Authentication. Otherwise, store the authenticated user in the HTTP session, and set it on each request with a servlet filter. This is the same approach as authentication in a plain servlet application, with the parts registered as Spring beans.
The following example stores the user 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;
}
}The filter wraps each request of an authenticated user, so that the request returns the user and the user’s roles. Spring Boot registers a Filter bean for all requests automatically:
Source code
AuthenticationFilter.java
AuthenticationFilter.java@Component
public class AuthenticationFilter extends OncePerRequestFilter {
static final String USER_ATTRIBUTE = AppUser.class.getName();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
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);
}
}Role names in @RolesAllowed are passed unchanged to isUserInRole(). With this filter, @RolesAllowed("ADMIN") therefore matches a user whose roles contain ADMIN.
A service logs users in by storing the user in the session, and logs them out by invalidating the session:
Source code
AuthenticationService.java
AuthenticationService.java@Component
public class AuthenticationService {
public boolean login(String username, String password) {
AppUser user = findUser(username, password); 1
if (user == null) {
return false;
}
var request = VaadinServletRequest.getCurrent()
.getHttpServletRequest();
request.changeSessionId(); 2
request.getSession().setAttribute(
AuthenticationFilter.USER_ATTRIBUTE, user);
return true;
}
public void logout() {
UI.getCurrentOrThrow().getPage().setLocation("./");
VaadinSession.getCurrent().getSession().invalidate();
}
// ...
}-
Checks the credentials against the user store of the application, and returns
nullif they don’t match. Never store passwords in plain text. -
Gives the session a new ID, to protect against session fixation.
Enabling Navigation Access Control
Navigation access control is enabled with a VaadinServiceInitListener that adds NavigationAccessControl as a BeforeEnterListener to every UI. In a Spring application, Vaadin picks up the listener as a bean, so it doesn’t need to be registered under META-INF/services as in a plain servlet application:
Source code
NavigationAccessControlInitializer.java
NavigationAccessControlInitializer.java@Component
public class NavigationAccessControlInitializer
implements VaadinServiceInitListener {
private final NavigationAccessControl accessControl;
public NavigationAccessControlInitializer() {
accessControl = new NavigationAccessControl();
accessControl.setLoginView(LoginView.class); 1
}
@Override
public void serviceInit(ServiceInitEvent event) {
event.getSource().addUIInitListener(uiInitEvent -> uiInitEvent
.getUI().addBeforeEnterListener(accessControl));
}
}-
Sets the view to which users who aren’t authenticated are redirected.
To customize the access checkers or the decision resolver, pass them to the NavigationAccessControl constructor, as described in Navigation Access Control.
Log-in View
The log-in view calls the service when the user submits the form:
Source code
LoginView.java
LoginView.java@Route(value = "login", autoLayout = false)
public class LoginView extends VerticalLayout {
public LoginView(AuthenticationService authService) {
LoginForm login = new LoginForm();
login.addLoginListener(event -> {
if (authService.login(event.getUsername(),
event.getPassword())) {
getUI().ifPresent(ui -> ui.getPage().setLocation("./"));
} else {
login.setError(true);
}
});
add(login);
}
}The log-in view doesn’t need an access annotation: NavigationAccessControl always allows navigation to the configured log-in view.
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 login, instead of navigating to another view within the same request. The URL ./ is relative to the root of the application, because Vaadin sets the base URL of the page to it. The redirect therefore also works when the application is deployed under a context path.