Multi-Faktor-Authentifizierung mit Spring Boot 2 und Spring Security 5

11

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 Security Starter halten .

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.

TL; DR

  • Wie kann ein zusätzlicher AuthenticationProvider offiziell in ein mit Spring Boot Security Starter konfiguriertes System integriert werden?
  • Welche Möglichkeiten werden empfohlen, um Wiederholungsangriffe zu verhindern?

Lange Version

Die API verfügt über einen Endpunkt, /auth/tokenvon dem das Frontend ein JWT-Token erhalten kann, indem Benutzername und Kennwort angegeben werden. Die Antwort enthält auch einen Authentifizierungsstatus, der entweder AUTHENTICATED oder PRE_AUTHENTICATED_MFA_REQUIRED sein kann .

Wenn der Benutzer MFA benötigt, wird dem Token eine einzige erteilte Berechtigung von PRE_AUTHENTICATED_MFA_REQUIREDund eine Ablaufzeit von 5 Minuten ausgestellt. Auf diese Weise kann der Benutzer auf den Endpunkt zugreifen, auf dem er /auth/mfa-tokenden TOTP-Code über seine Authenticator-App bereitstellen und das vollständig authentifizierte Token für den Zugriff auf die Site erhalten kann.

Anbieter und Token

Ich habe meine Gewohnheit erstellt, MfaAuthenticationProviderdie implementiert AuthenticationProvider:

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // validate the OTP code
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return OneTimePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

Und a, OneTimePasswordAuthenticationTokendas AbstractAuthenticationTokenden Benutzernamen (aus dem signierten JWT) und den OTP-Code enthält.

Konfig

Ich habe meine Gewohnheit WebSecurityConfigurerAdapter, wo ich meine Gewohnheit AuthenticationProviderüber hinzufüge http.authenticationProvider(). Nach JavaDoc scheint dies der richtige Ort zu sein:

Ermöglicht das Hinzufügen eines zusätzlichen AuthenticationProviders

Die relevanten Teile von mir sehen so SecurityConfigaus.

    @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

Regler

Das AuthControllerhat das AuthenticationManagerBuildereingespritzt 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-tokenfü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 dies DaoAuthenticationProviderder einzige Authentifizierungsanbieter ist AuthenticationProviderManager.

Wenn ich meine MfaAuthenticationProviderals Bean ausstelle, ist dies der einzige registrierte Anbieter, sodass ich das Gegenteil erhalte:

No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken. 

Also, wie bekomme ich beides?

Meine Frage

Was ist die empfohlene Methode, um ein zusätzliches System AuthenticationProviderin ein von Spring Boot Security Starter konfiguriertes System zu integrieren, damit ich sowohl das DaoAuthenticationProviderals auch mein eigenes benutzerdefiniertes System erhalte MfaAuthenticationProvider? Ich möchte die Standardeinstellungen von Spring Boot Scurity Starter beibehalten und zusätzlich einen eigenen Provider haben.

Verhinderung von Wiederholungsangriffen

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 darüber nach, 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?

Vielen Dank.

- -

PS: Da es sich um eine Sicherheitsfrage handelt, suche ich nach einer Antwort aus glaubwürdigen und / oder offiziellen Quellen. Vielen Dank.

phisch
quelle

Antworten:

0

Um meine eigene Frage zu beantworten, habe ich sie nach weiteren Recherchen so umgesetzt.

Ich habe einen Anbieter als Pojo, der implementiert AuthenticationProvider. 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 automatisch verdrahten AuthenticationManagerBuilderund injiziere meine manuellMfaAuthenticationProvider

@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 authenticate()Methode des authenticationManagerBuilder, der den auswählbaren MfaAuthenticationProviderNamen auswählt OneTimePasswordAuthenticationToken.

    var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
    var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
phisch
quelle