Maison > Java > javaDidacticiel > Implémentation de l'authentification par jeton unique avec Spring Security

Implémentation de l'authentification par jeton unique avec Spring Security

DDD
Libérer: 2024-12-04 17:11:11
original
659 Les gens l'ont consulté

Implementing One-Time Token Authentication with Spring Security

Dans le paysage numérique actuel, il est crucial de fournir des méthodes d'authentification sécurisées et conviviales. L'une de ces méthodes qui gagne en popularité est l'authentification One-Time Token (OTT), souvent mise en œuvre sous forme de « liens magiques » envoyés par courrier électronique. Spring Security 6.4.0 offre une prise en charge intégrée robuste de l'authentification OTT, y compris des implémentations prêtes à l'emploi. Dans ce guide complet, nous explorerons comment mettre en œuvre une authentification OTT sécurisée à l'aide de solutions intégrées et d'implémentations personnalisées.

Comprendre les jetons à usage unique et les mots de passe à usage unique

Avant de plonger dans la mise en œuvre, il est important de comprendre que les jetons à usage unique (OTT) diffèrent des mots de passe à usage unique (OTP). Alors que les systèmes OTP nécessitent généralement une configuration initiale et s'appuient sur des outils externes pour la génération de mots de passe, les systèmes OTT sont plus simples du point de vue de l'utilisateur : ils reçoivent un jeton unique (généralement par e-mail) qu'ils peuvent utiliser pour s'authentifier.

Les principales différences incluent :

  • OTT ne nécessite pas de configuration initiale de l'utilisateur
  • Les jetons sont générés et délivrés par votre application
  • Chaque jeton est généralement valable pour un usage unique et expire après une durée définie

Implémentations intégrées disponibles

Spring Security fournit deux implémentations de OneTimeTokenService :

  1. InMemoryOneTimeTokenService :

    • Stocke les jetons en mémoire
    • Idéal pour le développement et les tests
    • Ne convient pas aux environnements de production ou en cluster
    • Les jetons sont perdus au redémarrage de l'application
  2. JdbcOneTimeTokenService :

    • Stocke les jetons dans une base de données
    • Convient à une utilisation en production
    • Fonctionne dans des environnements en cluster
    • Stockage persistant avec nettoyage automatique

Utilisation d'InMemoryOneTimeTokenService

Voici comment implémenter la solution en mémoire la plus simple :

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login/**", "/ott/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin(Customizer.withDefaults());  // Uses InMemoryOneTimeTokenService by default

        return http.build();
    }
}
Copier après la connexion
Copier après la connexion

Utilisation de JdbcOneTimeTokenService

Pour les environnements de production, utilisez l'implémentation JDBC :

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Bean
    public OneTimeTokenService oneTimeTokenService() {
        return new JdbcOneTimeTokenService(jdbcTemplate);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login/**", "/ott/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin(Customizer.withDefaults());

        return http.build();
    }
}
Copier après la connexion
Copier après la connexion

Structure de table requise pour JdbcOneTimeTokenService :

CREATE TABLE one_time_tokens (
    token_value VARCHAR(255) PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    issued_at TIMESTAMP NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    used BOOLEAN NOT NULL
);
Copier après la connexion

Implémentation personnalisée

Pour plus de contrôle sur le processus de génération et de validation des jetons, vous pouvez créer une implémentation personnalisée :

1. Entité de jeton et référentiel

@Entity
@Table(name = "one_time_tokens")
public class OneTimeToken {
    @Id
    @GeneratedValue
    private Long id;

    private String tokenValue;
    private String username;
    private LocalDateTime createdAt;
    private LocalDateTime expiresAt;
    private boolean used;

    // Getters and setters omitted for brevity
}

@Repository
public interface OneTimeTokenRepository extends JpaRepository<OneTimeToken, Long> {
    Optional<OneTimeToken> findByTokenValueAndUsedFalse(String tokenValue);
    void deleteByExpiresAtBefore(LocalDateTime dateTime);
}
Copier après la connexion

2. Service de jetons personnalisés

@Service
@Transactional
public class PersistentOneTimeTokenService implements OneTimeTokenService {
    private static final int TOKEN_VALIDITY_MINUTES = 15;

    @Autowired
    private OneTimeTokenRepository tokenRepository;

    @Override
    public OneTimeToken generate(GenerateOneTimeTokenRequest request) {
        String tokenValue = UUID.randomUUID().toString();
        LocalDateTime now = LocalDateTime.now();

        OneTimeToken token = new OneTimeToken();
        token.setTokenValue(tokenValue);
        token.setUsername(request.getUsername());
        token.setCreatedAt(now);
        token.setExpiresAt(now.plusMinutes(TOKEN_VALIDITY_MINUTES));
        token.setUsed(false);

        return return new DefaultOneTimeToken(token.getTokenValue(),token.getUsername(), Instant.now());
    }

    @Override
    public Authentication consume(ConsumeOneTimeTokenRequest request) {
        OneTimeToken token = tokenRepository.findByTokenValueAndUsedFalse(request.getTokenValue())
            .orElseThrow(() -> new BadCredentialsException("Invalid or expired token"));

        if (token.getExpiresAt().isBefore(LocalDateTime.now())) {
            throw new BadCredentialsException("Token has expired");
        }

        token.setUsed(true);
        tokenRepository.save(token);

        UserDetails userDetails = loadUserByUsername(token.getUsername());
        return new UsernamePasswordAuthenticationToken(
            userDetails, null, userDetails.getAuthorities());
    }
}
Copier après la connexion

Implémentation de la livraison de jetons

Spring Security ne gère pas la livraison des jetons, vous devrez donc l'implémenter :

@Component
public class EmailMagicLinkHandler implements OneTimeTokenGenerationSuccessHandler {
    @Autowired
    private JavaMailSender mailSender;

    private final OneTimeTokenGenerationSuccessHandler redirectHandler = 
        new RedirectOneTimeTokenGenerationSuccessHandler("/ott/check-email");

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, 
                      OneTimeToken token) throws IOException, ServletException {
        String magicLink = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
            .replacePath(request.getContextPath())
            .replaceQuery(null)
            .fragment(null)
            .path("/login/ott")
            .queryParam("token", token.getTokenValue())
            .toUriString();

        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(getUserEmail(token.getUsername()));
        message.setSubject("Your Sign-in Link");
        message.setText("Click here to sign in: " + magicLink);

        mailSender.send(message);
        redirectHandler.handle(request, response, token);
    }
}
Copier après la connexion

Personnalisation des URL et des pages

Spring Security propose plusieurs options de personnalisation :

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login/**", "/ott/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin(Customizer.withDefaults());  // Uses InMemoryOneTimeTokenService by default

        return http.build();
    }
}
Copier après la connexion
Copier après la connexion

Considérations relatives à la production

Lors du déploiement de l'authentification OTT en production :

  1. Choisissez la bonne mise en œuvre

    • Utilisez JdbcOneTimeTokenService ou une implémentation personnalisée pour la production
    • InMemoryOneTimeTokenService ne doit être utilisé que pour le développement/test
  2. Configurer la livraison des e-mails

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Bean
    public OneTimeTokenService oneTimeTokenService() {
        return new JdbcOneTimeTokenService(jdbcTemplate);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login/**", "/ott/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin(Customizer.withDefaults());

        return http.build();
    }
}
Copier après la connexion
Copier après la connexion
  1. Bonnes pratiques de sécurité
    • Définissez les délais d'expiration des jetons appropriés (15 minutes recommandées)
    • Implémenter une limitation de débit pour la génération de jetons
    • Utilisez HTTPS pour tous les points de terminaison
    • Surveiller les tentatives d'authentification ayant échoué
    • Assurez-vous que les jetons sont à usage unique et invalidés immédiatement après utilisation
    • Mettre en œuvre le nettoyage automatique des jetons expirés
    • Utilisez la génération sécurisée de jetons aléatoires pour éviter les devinettes

Comment ça marche

  1. L'utilisateur demande un jeton en soumettant son adresse e-mail
  2. Le système génère un jeton sécurisé et envoie un lien magique par e-mail
  3. L'utilisateur clique sur le lien et est redirigé vers la page de soumission de jetons
  4. Le système valide le token et authentifie l'utilisateur s'il est valide

Conclusion

La prise en charge OTT de Spring Security fournit une base solide pour la mise en œuvre d'une authentification sécurisée et conviviale. Que vous choisissiez les implémentations intégrées ou créiez une solution personnalisée, vous pouvez offrir à vos utilisateurs une option de connexion sans mot de passe tout en maintenant des normes de sécurité élevées.

Lors de la mise en œuvre de l'authentification OTT, n'oubliez pas de :

  • Choisissez l'implémentation appropriée pour votre environnement
  • Mettre en œuvre la livraison sécurisée de jetons
  • Configurer l'expiration appropriée du jeton
  • Suivez les meilleures pratiques de sécurité
  • Créez une gestion des erreurs et des redirections conviviales
  • Implémentez des modèles d'e-mails appropriés pour un look professionnel

En suivant ce guide, vous pouvez mettre en œuvre un système d'authentification OTT sécurisé et convivial qui répond aux besoins de votre application tout en tirant parti des fonctionnalités de sécurité robustes de Spring Security.

Référence : https://docs.spring.io/spring-security/reference/servlet/authentication/onetimetoken.html

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal