package com.lions.dev.service; import com.lions.dev.entity.users.Users; import com.lions.dev.util.UserRoles; import io.smallrye.jwt.build.Jwt; import jakarta.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.HashSet; import java.util.Set; /** * Service d'émission de JWT au login. * * Le token contient : * - sub (subject) : userId de l'utilisateur * - groups : rôles de l'utilisateur (pour @RolesAllowed) * - iss (issuer) : "afterwork" * - iat (issued at) : timestamp de création * - exp (expiration) : timestamp d'expiration * * La validation des tokens est assurée automatiquement par SmallRye JWT * lorsque les endpoints sont protégés avec @RolesAllowed. * * @since 2.0 - Implémentation sécurité JWT production-ready */ @ApplicationScoped public class JwtService { private static final Logger LOG = Logger.getLogger(JwtService.class); private static final String ISSUER = "afterwork"; @ConfigProperty(name = "afterwork.jwt.secret", defaultValue = "afterwork-jwt-secret-min-32-bytes-for-hs256!") String secret; @ConfigProperty(name = "smallrye.jwt.new-token.lifespan", defaultValue = "86400") long tokenLifespanSeconds; /** * Génère un JWT pour l'utilisateur authentifié. * * @param user L'utilisateur après login réussi * @return Le token JWT signé (à envoyer au client dans la réponse d'authentification) */ public String generateToken(Users user) { if (user == null || user.getId() == null) { throw new IllegalArgumentException("User et id obligatoires pour générer le JWT"); } // Construire les groupes (rôles) pour @RolesAllowed Set groups = buildGroups(user); SecretKey key = secretKeyFromConfig(); String token = Jwt.claims() .issuer(ISSUER) .subject(user.getId().toString()) .groups(groups) .issuedAt(java.time.Instant.now()) .expiresIn(Duration.ofSeconds(tokenLifespanSeconds)) .jws() .algorithm(io.smallrye.jwt.algorithm.SignatureAlgorithm.HS256) .sign(key); LOG.info("JWT généré pour l'utilisateur " + user.getId() + " avec rôles: " + groups); return token; } /** * Construit l'ensemble des groupes (rôles) pour le token JWT. * Inclut le rôle principal et les rôles implicites selon la hiérarchie. * * Hiérarchie : SUPER_ADMIN > ADMIN > MANAGER > USER * * @param user L'utilisateur * @return Set des groupes à inclure dans le token */ private Set buildGroups(Users user) { Set groups = new HashSet<>(); String role = user.getRole() != null ? user.getRole().toUpperCase() : UserRoles.USER; // Ajouter le rôle principal groups.add(role); // Ajouter les rôles implicites selon la hiérarchie switch (role) { case UserRoles.SUPER_ADMIN: groups.add(UserRoles.ADMIN); groups.add(UserRoles.MANAGER); groups.add(UserRoles.USER); break; case UserRoles.ADMIN: groups.add(UserRoles.MANAGER); groups.add(UserRoles.USER); break; case UserRoles.MANAGER: groups.add(UserRoles.USER); break; case UserRoles.USER: default: // USER n'a pas de rôles implicites supplémentaires break; } return groups; } /** * Crée la clé secrète à partir de la configuration. * Assure que la clé fait au moins 32 bytes pour HS256. */ private SecretKey secretKeyFromConfig() { byte[] decoded = secret.getBytes(StandardCharsets.UTF_8); if (decoded.length < 32) { byte[] padded = new byte[32]; System.arraycopy(decoded, 0, padded, 0, decoded.length); decoded = padded; } return new SecretKeySpec(decoded, "HmacSHA256"); } }