Ich möchte einer Angular & Spring-Anwendung) eine Multi-Faktor-Authentifizierung mit TOTP-Soft-Token hinzufügen und dabei alles so nah wie möglich an den Standardeinstellungen von Spring Boot halten Sicherheitsstarter .
Die Token-Validierung erfolgt lokal (mit der Aerogear-Otp-Java-Bibliothek), ohne API-Anbieter eines Drittanbieters.
Das Einrichten von Token für einen Benutzer funktioniert, die Überprüfung durch Nutzung von Spring Security Authentication Manager/Providers jedoch nicht.
Die API verfügt über einen Endpunkt /auth/token
, Von dem das Frontend ein JWT-Token erhalten kann, indem Benutzername und Kennwort angegeben werden. Die Antwort enthält auch einen Authentifizierungsstatus, der entweder AUTHENTIFIZIERT oder PRE_AUTHENTICATED_MFA_REQUIRED sein kann.
Wenn der Benutzer MFA benötigt, wird dem Token eine einzige erteilte Berechtigung von PRE_AUTHENTICATED_MFA_REQUIRED
Und eine Ablaufzeit von 5 Minuten ausgestellt. Auf diese Weise kann der Benutzer auf den Endpunkt /auth/mfa-token
Zugreifen, auf dem er den TOTP-Code aus seiner Authenticator-App bereitstellen und das vollständig authentifizierte Token für den Zugriff auf die Site erhalten kann.
Ich habe mein benutzerdefiniertes MfaAuthenticationProvider
erstellt, das AuthenticationProvider
implementiert:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// validate the OTP code
}
@Override
public boolean supports(Class<?> authentication) {
return OneTimePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
Und ein OneTimePasswordAuthenticationToken
, das AbstractAuthenticationToken
erweitert, um den Benutzernamen (aus dem signierten JWT) und den OTP-Code zu enthalten.
Ich habe mein benutzerdefiniertes WebSecurityConfigurerAdapter
, wo ich mein benutzerdefiniertes AuthenticationProvider
über http.authenticationProvider()
hinzufüge. Nach JavaDoc scheint dies der richtige Ort zu sein:
Ermöglicht das Hinzufügen eines zusätzlichen AuthenticationProviders
Die relevanten Teile meines SecurityConfig
sehen so aus.
@Configuration
@EnableWebSecurity
@EnableJpaAuditing(auditorAwareRef = "appSecurityAuditorAware")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
public SecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authenticationProvider(new MfaAuthenticationProvider());
http.authorizeRequests()
// Public endpoints, HTML, Assets, Error Pages and Login
.antMatchers("/", "favicon.ico", "/asset/**", "/pages/**", "/api/auth/token").permitAll()
// MFA auth endpoint
.antMatchers("/api/auth/mfa-token").hasAuthority(ROLE_PRE_AUTH_MFA_REQUIRED)
// much more config
Das AuthController
hat das AuthenticationManagerBuilder
injiziert und zieht alles zusammen.
@RestController
@RequestMapping(AUTH)
public class AuthController {
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@PostMapping("/mfa-token")
public ResponseEntity<Token> mfaToken(@Valid @RequestBody OneTimePassword oneTimePassword) {
var username = SecurityUtils.getCurrentUserLogin().orElse("");
var authenticationToken = new OneTimePasswordAuthenticationToken(username, oneTimePassword.getCode());
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
// rest of class
Das Posten gegen /auth/mfa-token
Führt jedoch zu folgendem Fehler:
"error": "Forbidden",
"message": "Access Denied",
"trace": "org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for de.....OneTimePasswordAuthenticationToken
Warum holt Spring Security meinen Authentifizierungsanbieter nicht ab? Das Debuggen des Controllers zeigt mir, dass DaoAuthenticationProvider
der einzige Authentifizierungsanbieter in AuthenticationProviderManager
ist.
Wenn ich mein MfaAuthenticationProvider
als Bean verfügbar mache, ist nur der Anbieter registriert, sodass ich das Gegenteil erhalte:
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken.
Also, wie bekomme ich beides?
Was ist der empfohlene Weg, um ein zusätzliches AuthenticationProvider
in ein Spring Boot Security Starter konfiguriertes System zu integrieren, damit ich beide bekomme, das DaoAuthenticationProvider
und mein eigenes benutzerdefiniertes MfaAuthenticationProvider
? Ich möchte die Standardeinstellungen von Spring Boot Scurity Starter beibehalten und zusätzlich einen eigenen Provider haben.
Ich weiß, dass der OTP-Algorithmus selbst nicht innerhalb der Zeitscheibe, in der der Code gültig ist, vor Wiederholungsangriffen schützt. RFC 6238 macht dies deutlich
Der Prüfer darf den zweiten Versuch des OTP NICHT akzeptieren, nachdem die erfolgreiche Validierung für das erste OTP durchgeführt wurde, wodurch die einmalige Verwendung eines OTP sichergestellt wird.
Ich habe mich gefragt, ob es einen empfohlenen Weg gibt, Schutz zu implementieren. Da die OTP-Token zeitbasiert sind, denke ich daran, die letzte erfolgreiche Anmeldung im Benutzermodell zu speichern und sicherzustellen, dass es nur eine erfolgreiche Anmeldung pro 30 Sekunden Zeitscheibe gibt. Dies bedeutet natürlich eine Synchronisation auf dem Benutzermodell. Irgendwelche besseren Ansätze?
Danke dir.
- -
PS: Da es sich um eine Sicherheitsfrage handelt, suche ich nach einer Antwort aus glaubwürdigen und/oder offiziellen Quellen. Danke dir.
Um meine eigene Frage zu beantworten, habe ich sie nach weiteren Recherchen so umgesetzt.
Ich habe einen Anbieter als Pojo, der AuthenticationProvider
implementiert. Es ist absichtlich keine Bohne/Komponente. Andernfalls würde Spring es als einzigen Anbieter registrieren.
public class MfaAuthenticationProvider implements AuthenticationProvider {
private final AccountService accountService;
@Override
public Authentication authenticate(Authentication authentication) {
// here be code
}
In meiner SecurityConfig lasse ich Spring das AuthenticationManagerBuilder
automatisch verdrahten und injiziere mein MfaAuthenticationProvider
manuell
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationManagerBuilder authenticationManagerBuilder;
@Override
protected void configure(HttpSecurity http) throws Exception {
// other code
authenticationManagerBuilder.authenticationProvider(getMfaAuthenticationProvider());
// more code
}
// package private for testing purposes.
MfaAuthenticationProvider getMfaAuthenticationProvider() {
return new MfaAuthenticationProvider(accountService);
}
Wenn der Benutzer nach der Standardauthentifizierung MFA aktiviert hat, wird er mit der erteilten Berechtigung PRE_AUTHENTICATED_MFA_REQUIRED vorauthentifiziert. Dadurch können sie auf einen einzelnen Endpunkt zugreifen, /auth/mfa-token
. Dieser Endpunkt nimmt den Benutzernamen vom gültigen JWT und dem bereitgestellten TOTP und sendet ihn an die Methode authenticate()
des authenticationManagerBuilder, die das MfaAuthenticationProvider
auswählt, da es OneTimePasswordAuthenticationToken
verarbeiten kann.
var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);