Quiero agregar autenticación multifactor con tokens de software TOTP a una aplicación Angular & Spring, manteniendo todo lo más cerca posible de los valores predeterminados de Spring Boot Security Starter .
La validación de token se realiza localmente (con la biblioteca aerogear-otp-java), sin proveedor de API de terceros.
Configurar tokens para un usuario funciona, pero validarlos aprovechando Spring Security Authentication Manager / Providers no lo hace.
TL; DR
- ¿Cuál es la forma oficial de integrar un AuthenticationProvider adicional en un sistema configurado Spring Boot Security Starter ?
- ¿Cuáles son las formas recomendadas para prevenir ataques de repetición?
Versión larga
La API tiene un punto final /auth/token
desde el cual la interfaz puede obtener un token JWT al proporcionar un nombre de usuario y contraseña. La respuesta también incluye un estado de autenticación, que puede ser AUTHENTICATED o PRE_AUTHENTICATED_MFA_REQUIRED .
Si el usuario requiere MFA, el token se emite con una única autoridad otorgada PRE_AUTHENTICATED_MFA_REQUIRED
y un tiempo de vencimiento de 5 minutos. Esto permite al usuario acceder al punto final /auth/mfa-token
donde puede proporcionar el código TOTP desde su aplicación Authenticator y obtener el token totalmente autenticado para acceder al sitio.
Proveedor y token
He creado mi costumbre MfaAuthenticationProvider
que 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);
}
Y una OneTimePasswordAuthenticationToken
que se extiende AbstractAuthenticationToken
para contener el nombre de usuario (tomado del JWT firmado) y el código OTP.
Config
Tengo mi costumbre WebSecurityConfigurerAdapter
, donde agrego mi AuthenticationProvider
vía personalizada http.authenticationProvider()
. Según JavaDoc, este parece ser el lugar correcto:
Permite agregar un AuthenticationProvider adicional para ser utilizado
Las partes relevantes de mi se SecurityConfig
ven así.
@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
Controlador
El AuthController
tiene el AuthenticationManagerBuilder
inyectado y lo está juntando todo.
@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
Sin embargo, publicar en contra /auth/mfa-token
conduce a este error:
"error": "Forbidden",
"message": "Access Denied",
"trace": "org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for de.....OneTimePasswordAuthenticationToken
¿Por qué Spring Security no recoge mi proveedor de autenticación? La depuración del controlador me muestra que DaoAuthenticationProvider
es el único proveedor de autenticación en AuthenticationProviderManager
.
Si expongo mi MfaAuthenticationProvider
como bean, es el único proveedor que está registrado, por lo que obtengo lo contrario:
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken.
Entonces, ¿cómo consigo ambos?
Mi pregunta
¿Cuál es la forma recomendada de integrar un sistema adicional AuthenticationProvider
en un sistema configurado Spring Boot Security Starter , de modo que obtenga tanto DaoAuthenticationProvider
mi como mi propia costumbre MfaAuthenticationProvider
? Quiero mantener los valores predeterminados de Spring Boot Scurity Starter y tener mi propio proveedor adicionalmente.
Prevención del ataque de repetición
Sé que el algoritmo OTP no protege por sí solo contra ataques de repetición dentro del intervalo de tiempo en el que el código es válido; RFC 6238 deja esto claro
El verificador NO DEBE aceptar el segundo intento de la OTP después de que la validación exitosa se haya emitido para la primera OTP, lo que garantiza el uso único de una OTP.
Me preguntaba si hay una forma recomendada de implementar la protección. Dado que los tokens OTP están basados en el tiempo, estoy pensando en almacenar el último inicio de sesión exitoso en el modelo del usuario y asegurarme de que solo haya un inicio de sesión exitoso por cada 30 segundos. Por supuesto, esto significa sincronización en el modelo de usuario. ¿Algún mejor enfoque?
Gracias.
-
PD: dado que esta es una pregunta sobre seguridad, estoy buscando una respuesta basada en fuentes confiables y / o oficiales. Gracias.