3.5.19. Web Login
This section describes how the web client authentication works and how to extend it in your project. For information about authentication on the middle tier, see Login.
See Anonymous Access & Social Login guide to learn how to set up public access to some screens of the application and implement custom login using a Google, Facebook, or GitHub account. |
Implementation of the login procedure of the Web Client block has the following mechanisms:
-
Connection
implemented byConnectionImpl
. -
LoginProvider
implementations. -
HttpRequestFilter
implementations.
The main interface of Web login subsystem is Connection
which contains the following key methods:
-
login() - authenticates a user, starts a session and changes the state of the connection.
-
logout() - log out of the system.
-
substituteUser() - substitute a user in the current session with another user. This method creates a new UserSession instance, but with the same session ID.
-
getSession() - get the current user session.
After successful login, Connection sets UserSession object to the attribute of VaadinSession
and sets SecurityContext
. The Connection object is bound to VaadinSession
thus it cannot be used from non-UI threads, it throws IllegalConcurrentAccessException in case of login/logout
call from a non UI thread.
Usually, login is performed from the LoginScreen
screen that supports login with login/password and "remember me" credentials.
The default implementation of Connection
is ConnectionImpl
, which delegates login to a chain of LoginProvider
instances. A LoginProvider
is a login module that can process a specific Credentials
implementation, also it has a special supports()
method to allow the caller to query if it supports a given Credentials
type.
Standard user login process:
-
Users enter their username and password.
-
Web client block creates a
LoginPasswordCredentials
object passing the login and password to its constructor and invokesConnection.login()
method with this credentials. -
Connection
uses chain ofLoginProvider
objects. There isLoginPasswordLoginProvider
that works withLoginPasswordCredentials
instances. Depending on the cuba.checkPasswordOnClient it either invokesAuthenticationService.login(Credentials)
passing user’s login and password; or loads theUser
entity by login, checks the password against the loaded password hash and logs in as a trusted client withTrustedClientCredentials
and cuba.trustedClientPassword. -
If the authentication is successful, the created
AuthenticationDetails
instance with the active UserSession is passed back toConnection
. -
Connection
creates aClientUserSession
wrapper and sets it toVaadinSession
. -
Connection
creates aSecurityContext
instance and sets it toAppContext
. -
Connection
firesStateChangeEvent
that triggers UI update and leads to theMainScreen
initialization.
All LoginProvider
implementations must:
-
Authenticate user using
Credentials
object. -
Start a new user session with
AuthenticationService
or return another active session (for instance, anonymous). -
Return authentication details or null if it cannot login user with this
Credentials
object, for instance, if the login provider is disabled or is not properly configured. -
Throw
LoginException
in case of incorrectCredentials
or passLoginException
from the middleware to the caller.
HttpRequestFilter
- marker interface for beans that will be automatically added to the application filter chain as HTTP filter: https://docs.oracle.com/javaee/6/api/javax/servlet/Filter.html. You can use it to implement additional authentication, pre- and post-processing of request and response.
You can expose additional Filter
if you create Spring Framework component and implement HttpRequestFilter
interface:
@Component
public class CustomHttpFilter implements HttpRequestFilter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// delegate to the next filter/servlet
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
Please note that the minimal implementation has to delegate execution to FilterChain
otherwise your application will not work. By default, filters added as HttpRequestFilter
beans will not receive requests to VAADIN
directory and other paths specified in cuba.web.cubaHttpFilterBypassUrls
app property.
- Built-in login providers
-
The platform contains the following implementations of
LoginProvider
interface:-
AnonymousLoginProvider
- provides anonymous login for non-logged-in users. -
LoginPasswordLoginProvider
- delegates login toAuthenticationService
withLoginPasswordCredentials
. -
RememberMeLoginProvider
- delegates login toAuthenticationService
withRememberMeCredentials
. -
LdapLoginProvider
- acceptsLoginPasswordCredentials
, performs authentication using LDAP and delegates login toAuthenticationService
withTrustedClientCredentials
. -
ExternalUserLoginProvider
- acceptsExternalUserCredentials
and delegates login toAuthenticationService
withTrustedClientCredentials
. It can be used to perform login as a provided user name.
All the implementations create an active user session using
AuthenticationService.login()
.You can override any of them using Spring Framework mechanisms.
-
- Events
-
Standard implementation of
Connection
-ConnectionImpl
fires the following application events during login procedure:-
BeforeLoginEvent
/AfterLoginEvent
-
LoginFailureEvent
-
UserConnectedEvent
/UserDisconnectedEvent
-
UserSessionStartedEvent
/UserSessionFinishedEvent
-
UserSessionSubstitutedEvent
Event handlers of
BeforeLoginEvent
andLoginFailureEvent
may throwLoginException
to cancel login process or override the original login failure exception.For instance, you can permit login to Web Client only for users with login that includes a company domain using
BeforeLoginEvent
.@Component public class BeforeLoginEventListener { @Order(10) @EventListener protected void onBeforeLogin(BeforeLoginEvent event) throws LoginException { if (event.getCredentials() instanceof LoginPasswordCredentials) { LoginPasswordCredentials loginPassword = (LoginPasswordCredentials) event.getCredentials(); if (loginPassword.getLogin() != null && !loginPassword.getLogin().contains("@company")) { throw new LoginException( "Only users from @company are allowed to login"); } } } }
Additionally, the standard application class -
DefaultApp
fires the following events:-
AppInitializedEvent
- fired afterApp
initialization, performed once per HTTP session. -
AppStartedEvent
- fired on the first request processing of anApp
right before login as anonymous user. Event handlers may login the user using theConnection
object bound toApp
. -
AppLoggedInEvent
- fired after UI initialization ofApp
when a user is logged in. -
AppLoggedOutEvent
- fired after UI initialization ofApp
when a user is logged out. -
SessionHeartbeatEvent
- fired on heartbeat requests from a client web browser.
AppStartedEvent
can be used to implement SSO login with third-party authentication system, for instance Jasig CAS. Usually, it is used together with a customHttpRequestFilter
bean that should collect and provide additional authentication data.Let’s assume that we will automatically log in users if they have a special cookie value -
PROMO_USER
.@Order(10) @Component public class AppStartedEventListener implements ApplicationListener<AppStartedEvent> { private static final String PROMO_USER_COOKIE = "PROMO_USER"; @Inject private Logger log; @Override public void onApplicationEvent(AppStartedEvent event) { String promoUserLogin = event.getApp().getCookieValue(PROMO_USER_COOKIE); if (promoUserLogin != null) { Connection connection = event.getApp().getConnection(); if (!connection.isAuthenticated()) { try { connection.login(new ExternalUserCredentials(promoUserLogin)); } catch (LoginException e) { log.warn("Unable to login promo user {}: {}", promoUserLogin, e.getMessage()); } finally { event.getApp().removeCookie(PROMO_USER_COOKIE); } } } } }
Thus if users have "PROMO_USER" cookie and open the application, they will be automatically logged in as
promoUserLogin
.If you want to perform additional actions after login and UI initialization you could use
AppLoggedInEvent
. Keep in mind that you have to check if a user is authenticated or not in event handlers, all the events are fired foranonymous
user as well. -
- Web Session Lifecycle Events
-
The framework sends two events related to the HTTP session lifecycle:
-
WebSessionInitializedEvent
is sent when HTTP session is initialized. -
WebSessionDestroyedEvent
is sent when HTTP session is destroyed.
These events can be used to perform some system-level actions. Note that there is no
SecurityContext
available in the thread. -
- Extension points
-
You can extend login mechanisms using the following types of extension points:
-
Connection
- replace existingConnectionImpl
. -
HttpRequestFilter
- implement additionalHttpRequestFilter
. -
LoginProvider
implementations - implement additional or replace existingLoginProvider
. -
Events - implement event handler for one of the available events.
You can replace existing beans using Spring Framework mechanisms, for instance by registering a new bean in Spring XML config of the web module.
<bean id="cuba_LoginPasswordLoginProvider" class="com.company.demo.web.CustomLoginProvider"/>
-