Je veux ajouter une authentification multifacteur avec des jetons logiciels TOTP à une application Angular & Spring, tout en gardant le plus près possible des valeurs par défaut de Spring Boot Security Starter .
La validation de jeton se produit localement (avec la bibliothèque aerogear-otp-java), aucun fournisseur d'API tiers.
La configuration des jetons pour un utilisateur fonctionne, mais pas leur validation en utilisant Spring Security Authentication Manager / Providers.
TL; DR
- Quelle est la manière officielle d'intégrer un AuthenticationProvider supplémentaire dans un système configuré Spring Boot Security Starter ?
- Quels sont les moyens recommandés pour empêcher les attaques par rejeu?
Version longue
L'API a un point /auth/token
de terminaison à partir duquel le frontend peut obtenir un jeton JWT en fournissant un nom d'utilisateur et un mot de passe. La réponse comprend également un état d'authentification, qui peut être AUTHENTICATED ou PRE_AUTHENTICATED_MFA_REQUIRED .
Si l'utilisateur a besoin de l'authentification multifacteur, le jeton est émis avec une seule autorisation accordée PRE_AUTHENTICATED_MFA_REQUIRED
et un délai d'expiration de 5 minutes. Cela permet à l'utilisateur d'accéder au point de terminaison /auth/mfa-token
où il peut fournir le code TOTP à partir de son application Authenticator et obtenir le jeton entièrement authentifié pour accéder au site.
Fournisseur et jeton
J'ai créé ma coutume MfaAuthenticationProvider
qui implémente AuthenticationProvider
:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// validate the OTP code
}
@Override
public boolean supports(Class<?> authentication) {
return OneTimePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
Et un OneTimePasswordAuthenticationToken
qui s'étend AbstractAuthenticationToken
pour contenir le nom d'utilisateur (tiré du JWT signé) et le code OTP.
Config
J'ai ma coutume WebSecurityConfigurerAdapter
, où j'ajoute ma coutume AuthenticationProvider
via http.authenticationProvider()
. Selon le JavaDoc, cela semble être le bon endroit:
Permet d'ajouter un fournisseur d'authentification supplémentaire à utiliser
Les parties pertinentes de mon SecurityConfig
apparence ressemblent à ceci.
@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
Manette
Le AuthController
a AuthenticationManagerBuilder
injecté et tire tout cela ensemble.
@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
Cependant, la publication contre /auth/mfa-token
conduit à cette erreur:
"error": "Forbidden",
"message": "Access Denied",
"trace": "org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for de.....OneTimePasswordAuthenticationToken
Pourquoi Spring Security ne récupère pas mon fournisseur d'authentification? Le débogage du contrôleur me montre que DaoAuthenticationProvider
c'est le seul fournisseur d'authentification dans AuthenticationProviderManager
.
Si j'expose mon MfaAuthenticationProvider
as bean, c'est le seul fournisseur enregistré, donc j'obtiens le contraire:
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken.
Alors, comment puis-je obtenir les deux?
Ma question
Quelle est la méthode recommandée pour intégrer un élément supplémentaire AuthenticationProvider
dans un système configuré Spring Boot Security Starter , afin que j'obtienne à la fois la DaoAuthenticationProvider
et ma propre personnalisation MfaAuthenticationProvider
? Je veux conserver les valeurs par défaut de Spring Boot Scurity Starter et avoir mon propre fournisseur en plus.
Prévention des attaques par rejeu
Je sais que l'algorithme OTP ne protège pas à lui seul contre les attaques de rejeu dans la tranche de temps dans laquelle le code est valide; La RFC 6238 le montre clairement
Le vérificateur NE DOIT PAS accepter la deuxième tentative de l'OTP après que la validation réussie a été émise pour le premier OTP, ce qui garantit une utilisation unique et unique d'un OTP.
Je me demandais s'il existe un moyen recommandé de mettre en œuvre la protection. Étant donné que les jetons OTP sont basés sur le temps, je pense à stocker la dernière connexion réussie sur le modèle de l'utilisateur et à m'assurer qu'il n'y a qu'une seule connexion réussie par tranche de 30 secondes. Cela signifie bien sûr une synchronisation sur le modèle utilisateur. De meilleures approches?
Je vous remercie.
-
PS: puisqu'il s'agit d'une question de sécurité je recherche une réponse tirée de sources crédibles et / ou officielles. Je vous remercie.