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:
-
Connectionimplemented byConnectionImpl. -
LoginProviderimplementations. -
HttpRequestFilterimplementations.
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
LoginPasswordCredentialsobject passing the login and password to its constructor and invokesConnection.login()method with this credentials. -
Connectionuses chain ofLoginProviderobjects. There isLoginPasswordLoginProviderthat works withLoginPasswordCredentialsinstances. Depending on the cuba.checkPasswordOnClient it either invokesAuthenticationService.login(Credentials)passing user’s login and password; or loads theUserentity by login, checks the password against the loaded password hash and logs in as a trusted client withTrustedClientCredentialsand cuba.trustedClientPassword. -
If the authentication is successful, the created
AuthenticationDetailsinstance with the active UserSession is passed back toConnection. -
Connectioncreates aClientUserSessionwrapper and sets it toVaadinSession. -
Connectioncreates aSecurityContextinstance and sets it toAppContext. -
ConnectionfiresStateChangeEventthat triggers UI update and leads to theMainScreeninitialization.
All LoginProvider implementations must:
-
Authenticate user using
Credentialsobject. -
Start a new user session with
AuthenticationServiceor return another active session (for instance, anonymous). -
Return authentication details or null if it cannot login user with this
Credentialsobject, for instance, if the login provider is disabled or is not properly configured. -
Throw
LoginExceptionin case of incorrectCredentialsor passLoginExceptionfrom 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
LoginProviderinterface:-
AnonymousLoginProvider- provides anonymous login for non-logged-in users. -
LoginPasswordLoginProvider- delegates login toAuthenticationServicewithLoginPasswordCredentials. -
RememberMeLoginProvider- delegates login toAuthenticationServicewithRememberMeCredentials. -
LdapLoginProvider- acceptsLoginPasswordCredentials, performs authentication using LDAP and delegates login toAuthenticationServicewithTrustedClientCredentials. -
ExternalUserLoginProvider- acceptsExternalUserCredentialsand delegates login toAuthenticationServicewithTrustedClientCredentials. 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-ConnectionImplfires the following application events during login procedure:-
BeforeLoginEvent/AfterLoginEvent -
LoginFailureEvent -
UserConnectedEvent/UserDisconnectedEvent -
UserSessionStartedEvent/UserSessionFinishedEvent -
UserSessionSubstitutedEvent
Event handlers of
BeforeLoginEventandLoginFailureEventmay throwLoginExceptionto 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 -
DefaultAppfires the following events:-
AppInitializedEvent- fired afterAppinitialization, performed once per HTTP session. -
AppStartedEvent- fired on the first request processing of anAppright before login as anonymous user. Event handlers may login the user using theConnectionobject bound toApp. -
AppLoggedInEvent- fired after UI initialization ofAppwhen a user is logged in. -
AppLoggedOutEvent- fired after UI initialization ofAppwhen a user is logged out. -
SessionHeartbeatEvent- fired on heartbeat requests from a client web browser.
AppStartedEventcan be used to implement SSO login with third-party authentication system, for instance Jasig CAS. Usually, it is used together with a customHttpRequestFilterbean 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 foranonymoususer as well. -
- Web Session Lifecycle Events
-
The framework sends two events related to the HTTP session lifecycle:
-
WebSessionInitializedEventis sent when HTTP session is initialized. -
WebSessionDestroyedEventis sent when HTTP session is destroyed.
These events can be used to perform some system-level actions. Note that there is no
SecurityContextavailable in the thread. -
- Extension points
-
You can extend login mechanisms using the following types of extension points:
-
Connection- replace existingConnectionImpl. -
HttpRequestFilter- implement additionalHttpRequestFilter. -
LoginProviderimplementations - 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"/> -