A New TwoFactorAuthenticationFilter #9534
Labels
in: web
An issue in web modules (web, webmvc)
status: duplicate
A duplicate of another issue
type: enhancement
A general enhancement
Uh oh!
There was an error while loading. Please reload this page.
Hi,
I want to make it easier to implement two factor authentication (OTPs) using Spring Security. I found myself struggling to do it in my project and had to create a kind of janky workaround since there were a number of final methods I could not override.
That gave me the idea to actually go and make some real changes and came up with the following potential structure:
The key here is a new TwoFactorAuthenticationFilter which extends AbstractAuthenticationProcessingFilter. This filter would be responsible for intercepting POST requests to a login processing URL (with username and password) in addition to POST requests to a 2FA processing URL. This class would by default use a ProviderManager with at least two providers. One to confirm username and password and the other to confirm 2FA codes.
When a POST to the login processing URL is received, the usual username and password authentication process is followed:
If authentication succeeds the filter has to decide whether to initiate the 2FA process for the user, or return the authenticated
UsernamePasswordAuthenticationToken and lets the user proceed. This decision will be made by requesting information from the principal of type TwoFactorUserDetails. This class is a subclass of UserDetails and simply adds a method to retrieve 2FA
status.
If the isTwoFactorAuthenticationEnabled() comes back true, the 2FA process begins. This starts with delegating to a
TwoFactorCodeService interface. TwoFactorCodeServices implement three abstract methods:
These three methods will be implemented in a DefaultTwoFactorCodeService and they will make use of:
The TwoFactorAuthenticationFilter will use generateCode() to get its hands on a TwoFactorAuthenticationCodeWrapper
which wraps a TwoFactorAuthenticationCode with the sessionId, the username, and the time the code was created (this information will be stored in the TwoFactorCodeRepository as well).
Once the code has been generated, the filter will delegate to a TwoFactorCodeSendStrategyDelegator interface with one sendCode() abstract method this delegator will then be tasked with calling a TwoFactorCodeStrategy interface sendCode() method.
If there is an exception thrown while sending the code, the filter will invoke a CodeSendFailureStrategy to handleFailure().
The default for this is NullCodeSendFailureStrategy. The filter then proceeds by redirecting the user to the twoFactorRedirectUrl and returning null so that the doFilter() method in the AbstractAuthenticationProcessingFilter calls
return;
. It is then the users' responsibility to create and a 2FA page and the form match the POST to twoFactorAuthenticationProcessingUrl assigned to the filter.When the request with the code is then received at for example POST /2FA/authenticate, the filter is then again invoked. This time it creates a TwoFactorAuthenticationToken which will be passed to the ProviderManager. The ProviderManager will then call the TwoFactorAuthenticationProvider which will use the TwoFactorAuthenticationService to validate the received code. If the code is correct and not expired, cleanUp() will be called and new and authenticated TwoFactorAuthenticationToken will be returned to the filter which returns it to the super doFilter() method.
This is rough idea of how I think this could work there are a few gaps in it. Such as redirects if not awaiting a code and code re-sending options etc. But i'm wondering what people think.
The text was updated successfully, but these errors were encountered: