Autenticazione a più fattori con Spring Boot 2 e Spring Security 5


11

Voglio aggiungere l'autenticazione a più fattori con i token soft TOTP a un'applicazione Angular & Spring, mantenendo tutto il più vicino possibile alle impostazioni predefinite di Spring Boot Security Starter .

La convalida del token avviene localmente (con la libreria aerogear-otp-java), nessun provider API di terze parti.

L'impostazione dei token per un utente funziona, ma la loro convalida sfruttando Gestore / provider di autenticazione Spring Security no.

TL; DR

  • Qual è il modo ufficiale per integrare un altro AuthenticationProvider in un sistema configurato con Spring Boot Security Starter ?
  • Quali sono i modi consigliati per prevenire gli attacchi replay?

Versione lunga

L'API ha un endpoint /auth/tokendal quale il frontend può ottenere un token JWT fornendo nome utente e password. La risposta include anche uno stato di autenticazione, che può essere AUTHENTICATED o PRE_AUTHENTICATED_MFA_REQUIRED .

Se l'utente richiede l'AMF, il token viene rilasciato con un'unica autorità concessa PRE_AUTHENTICATED_MFA_REQUIREDe un tempo di scadenza di 5 minuti. Ciò consente all'utente di accedere all'endpoint in /auth/mfa-tokencui può fornire il codice TOTP dalla propria app Authenticator e ottenere il token completamente autenticato per accedere al sito.

Provider e token

Ho creato la mia abitudine MfaAuthenticationProviderche implementa AuthenticationProvider:

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

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

E un OneTimePasswordAuthenticationTokenche si estende AbstractAuthenticationTokenper contenere il nome utente (preso dal JWT firmato) e il codice OTP.

config

Ho la mia abitudine WebSecurityConfigurerAdapter, dove aggiungo la mia personalizzata AuthenticationProvidertramite http.authenticationProvider(). Accedendo a JavaDoc, questo sembra essere il posto giusto:

Consente di aggiungere un altro AuthenticationProvider da utilizzare

Le parti rilevanti del mio SecurityConfigassomigliano a questo.

    @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

controllore

L' AuthControllerha AuthenticationManagerBuilderiniettato e lo sta mettendo insieme.

@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

Tuttavia, la pubblicazione in contro /auth/mfa-tokenporta a questo errore:

"error": "Forbidden",
"message": "Access Denied",
"trace": "org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for de.....OneTimePasswordAuthenticationToken

Perché Spring Security non rileva il mio provider di autenticazione? Il debug del controller mi mostra che DaoAuthenticationProviderè l'unico provider di autenticazione in AuthenticationProviderManager.

Se espongo il mio MfaAuthenticationProvidercome bean, è l' unico provider registrato, quindi ottengo il contrario:

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

Quindi, come ottengo entrambi?

La mia domanda

Qual è il modo consigliato per integrare un ulteriore AuthenticationProviderin un sistema configurato con Spring Boot Security Starter , in modo da ottenere sia la DaoAuthenticationProvidermia che la mia personalizzazione MfaAuthenticationProvider? Voglio mantenere le impostazioni predefinite di Spring Boot Scurity Starter e avere anche il mio provider.

Prevenzione di Replay Attack

So che l'algoritmo OTP non protegge da solo dagli attacchi di riproduzione entro l'intervallo di tempo in cui il codice è valido; RFC 6238 lo chiarisce

Il verificatore NON DEVE accettare il secondo tentativo di OTP dopo che è stata emessa la convalida corretta per il primo OTP, il che garantisce un solo utilizzo di un OTP.

Mi chiedevo se esiste un modo raccomandato per implementare la protezione. Poiché i token OTP sono basati sul tempo, sto pensando di memorizzare l'ultimo accesso riuscito sul modello dell'utente e di assicurarmi che ci sia un solo accesso riuscito per 30 secondi. Questo ovviamente significa sincronizzazione sul modello utente. Qualche approccio migliore?

Grazie.

-

PS: poiché questa è una domanda sulla sicurezza, sto cercando una risposta che attinga da fonti credibili e / o ufficiali. Grazie.

Risposte:


0

Per rispondere alla mia domanda, è così che l'ho implementata, dopo ulteriori ricerche.

Ho un fornitore come pojo che implementa AuthenticationProvider. Non è deliberatamente un bean / componente. Altrimenti Spring lo registrerebbe come unico Provider.

public class MfaAuthenticationProvider implements AuthenticationProvider {
    private final AccountService accountService;

    @Override
    public Authentication authenticate(Authentication authentication) {
        // here be code 
        }

In SecurityConfig, lascio che Spring esegua l'autowire AuthenticationManagerBuildere inietti manualmente myMfaAuthenticationProvider

@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);
}

Dopo l'autenticazione standard, se l'utente ha abilitato MFA, vengono pre-autenticati con un'autorità concessa di PRE_AUTHENTICATED_MFA_REQUIRED . Questo permette loro di accedere a un singolo endpoint, /auth/mfa-token. Questo endpoint prende il nome utente dal JWT valido e dal TOTP fornito e lo invia al authenticate()metodo di authenticationManagerBuilder, che sceglie il modo MfaAuthenticationProviderin cui può gestirlo OneTimePasswordAuthenticationToken.

    var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
    var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.