Working down the numerous compile-stoppers in our migrated app, I’ve finally come to ExtLoginScreen and it seems there were major, major changes between CUBA and Jmix. In CUBA, I overrode numerous methods from the LoginScreen superclass.
All redlined on the @Override with the message “does not override method from its superclass.” And the ending call to super.method() is redlined with an error saying the method doesn’t exist.
It seems these methods are not a part of the LoginScreen in Jmix?
I need advice on how to convert this - we have a custom-implemented 2FA system and such, so we need to extend the login screen and handle that.
After playing with this in a test project, I learned in Jmix apparently one doesn’t make an ExtLoginScreen to extend the login screen…one just modifies the login screen itself. Cool. I still need advice on how to interrupt the login process. In CUBA it was simple; just throw a LoginException and you’re good.
Obviously, it’s not that simple in Jmix. I have, in LoginScreen.java:
Most of that obviously is the original code from the controller; I’ve just added the “fake 2FA” stuff. currentAuthentication.getAuthentication.setAuthenticated(false) doesn’t work; that causes an endless exception loop. Throwing the BadCredentialsException doesn’t work either.
I need to know how to invalidate/logout the just-authenticated session if the 2FA fails.
After some more experimenting, I’ve come up with this:
try {
// use Spring's authenticationManager to check the user/pw, but not do the Jmix login stuff...
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
log.info(">> Authed -> {}; checking 2FA", authentication.getName());
if (fake2FA == null || !fake2FA.equals("0000")) {
log.info(">> 2FA failed.");
throw new BadCredentialsException("2FA");
}
log.info(">>> 2FA succeeded, doing real login");
// this is inefficient because it will re-do the Spring auth, etc, but, not sure of another way.
loginScreenSupport.authenticate(
AuthDetails.of(username, password)
.withLocale(localesField.getValue())
.withRememberMe(rememberMeCheckBox.isChecked()), this);
} catch (BadCredentialsException | DisabledException | LockedException e) {
log.info("Login failed", e);
notifications.create(Notifications.NotificationType.ERROR)
.withCaption(messages.getMessage(getClass(), "loginFailed"))
.withDescription(messages.getMessage(getClass(), "badCredentials"))
.show();
}
I first use Spring’s authenticationManager to see if the user/pass are ok, then check the (fake; this is a test project!) 2FA, and if that’s ok, then I use the Jmix loginScreenSupport.authenticate(...) call to actually login to the Jmix app. This (at least seems to!) works as I’d expect.
Obviously, though, loginScreenSupport does the Spring authenticate again which is inefficient, but I’m not sure of another way.
Does anyone have advice as to another way to accomplish this?
I suppose, ideally, the second step of the two-factor authentication should be implemented with a separate, specially implemented AuthenticationProvider.
This provider should receive special object as a token - containing desired user login and one-time password.
The provider checks one-time password, verifies that it belongs to the desired user, and finishes the authentication procedure by returning the successful Authentication object.
The example of AuthenticationProvider implementation is org.springframework.security.authentication.dao.DaoAuthenticationProvider.
This is the implementation of “login by username and password” used by standard Jmix authentication.
Fair enough. Maybe just go with this for right now, and make it better later. I still have not got the app to the fully compiling stage post-migration from CUBA, so getting things working, if not optimal, is important.
@albudarov - so, after reading/watching a lot of info on Spring security it definitely seems like a custom AuthenticationProvider is the way to go (at least eventually) - but the built-in one is already doing most of the work - the username/password part. Can anyone give a high-level overview on implementing a custom AuthenticationProvider that delegates the user/password part to the built-in one, but takes other actions (eg, 2FA), and install said provider to be used in place of the existing one?
Even just a skeleton implementation that delegates to the existing/built-in auth (and showing how to “install” the custom provider) would be a huge help.
Heck, even a pointer to where to get started on installing a custom AuthenticationProvider would be a huge help at this point. Jumping around in the Jmix source, I can’t figure out where it installs its implementation!
All the usual Spring tutorials have you implementing a WebSecurityConfigurerAdapter and a UserDetailsService and such, but Jmix doesn’t seem to do that. There is SecurityConfigurers and such… but they don’t seem to actually do anything. O_o
Just some pointers or a very skeleton implementation with info on how to wire it in would be much appreciated!
You can try to extend the StandardSecurityConfiguration (which actually extends WebSecurityConfigurerAdapter) from Jmix so that it overrides its void configure(AuthenticationManagerBuilder auth). In this method register your own AuthenticationProvider instead of standard DaoAuthenticationProvider.