Aller au contenu

Apiplatform keycloak

De Marmits Wiki

AccessTokenHandler

Si vous utilisez un AccessTokenHandler pour gérer l’authentification avec des tokens d’accès (par exemple, des tokens JWT émis par Keycloak), voici comment configurer votre fichier security.yaml pour intégrer ce composant.



Configuration de security.yaml

Voici un exemple de configuration pour utiliser un AccessTokenHandler avec Symfony :

security:
    enable_authenticator_manager: true  # Activer le système d'authentification moderne de Symfony

    providers:
        # Définir un fournisseur d'utilisateurs
        keycloak:
            jwt:
                issuer: 'https://your-keycloak-server/auth/realms/your-realm'  # URL de Keycloak
                audience: 'your-client-id'  # Client ID configuré dans Keycloak
                public_key: 'your-public-key-from-keycloak'  # Clé publique pour valider les tokens

    firewalls:
        main:
            pattern: ^/v1  # Préfixe des routes API Platform (ajustez selon votre configuration)
            stateless: true  # L'authentification est sans état (stateless)
            access_token:
                token_handler: App\Security\AccessTokenHandler  # Votre AccessTokenHandler personnalisé
            provider: keycloak  # Utiliser le fournisseur d'utilisateurs configuré

    access_control:
        # Protéger les routes API
        - { path: ^/v1, roles: IS_AUTHENTICATED_FULLY }

Explication des sections

  1. providers :
    • Ici, vous définissez un fournisseur d’utilisateurs (keycloak) qui utilise un token JWT.
    • Le fournisseur est configuré pour valider les tokens JWT émis par Keycloak en utilisant :
      • issuer : L’URL de Keycloak (le realm).
      • audience : Le client ID configuré dans Keycloak.
      • public_key : La clé publique pour valider la signature des tokens.
  2. firewalls :
    • Le firewall main est configuré pour protéger les routes commençant par /v1 (ajustez selon votre préfixe API Platform).
    • L’option stateless: true indique que l’authentification est sans état (typique pour les API).
    • Le token_handler pointe vers votre classe AccessTokenHandler personnalisée.
    • Le provider est défini pour utiliser le fournisseur d’utilisateurs keycloak.
  3. access_control :
    • Cette section protège les routes commençant par /v1 et exige que l’utilisateur soit authentifié (IS_AUTHENTICATED_FULLY).



Implémentation de AccessTokenHandler

Voici un exemple d’implémentation de AccessTokenHandler pour valider les tokens JWT émis par Keycloak :

namespace App\Security;

use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Jose\Component\Core\JWKSet;
use Jose\Component\Core\Util\JsonConverter;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;

class AccessTokenHandler implements AccessTokenHandlerInterface
{
    private JWKSet $jwkSet;
    private JWSVerifier $jwsVerifier;

    public function __construct(JWKSet $jwkSet, JWSVerifier $jwsVerifier)
    {
        $this->jwkSet = $jwkSet;
        $this->jwsVerifier = $jwsVerifier;
    }

    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        // Désérialiser le token JWT
        $serializer = new CompactSerializer();
        $jws = $serializer->unserialize($accessToken);

        // Valider la signature du token
        if (!$this->jwsVerifier->verifyWithKeySet($jws, $this->jwkSet, 0)) {
            throw new BadCredentialsException('Invalid token signature.');
        }

        // Extraire le payload du token
        $payload = JsonConverter::decode($jws->getPayload());

        // Récupérer l'identifiant de l'utilisateur (sub)
        $userId = $payload['sub'];

        // Retourner un UserBadge avec l'identifiant de l'utilisateur
        return new UserBadge($userId);
    }
}

Enregistrement des services

Dans config/services.yaml, enregistrez votre AccessTokenHandler et les services nécessaires :

services:
    # Enregistrer l'AccessTokenHandler
    App\Security\AccessTokenHandler:
        arguments:
            $jwkSet: '@web_token.key_set.keycloak'  # Le JWKSet pour Keycloak
            $jwsVerifier: '@jose.jws_verifier'  # Le service de vérification JWS
        tags: ['security.access_token_handler']

    # Configuration de web-token/jwt-bundle
    web_token.key_set.keycloak:
        class: Jose\Component\Core\JWKSet
        factory: ['Jose\Component\Core\JWKSet', 'createFromKeyData']
        arguments:
            - { url: 'https://your-keycloak-server/auth/realms/your-realm/protocol/openid-connect/certs' }
            - true  # is_public

    jose.jws_verifier:
        class: Jose\Component\Signature\JWSVerifier
        arguments:
            - '@jose.algorithm_manager'  # Gestionnaire d'algorithmes

Tester l’authentification

  1. Obtenir un token JWT :
    • Utilisez Keycloak pour obtenir un token JWT valide pour votre client.
  2. Envoyer une requête à l’API :
    • Envoyez une requête à votre API avec le token dans l’en-tête Authorization :

      Authorization: Bearer <votre-token-jwt>
  3. Vérifier la réponse :
    • Si le token est valide, l’utilisateur sera authentifié.
    • Si le token est invalide, une erreur 401 Unauthorized sera retournée.



Conclusion

Avec cette configuration, vous pouvez utiliser un AccessTokenHandler personnalisé pour valider les tokens JWT émis par Keycloak. Le fichier security.yaml est configuré pour utiliser ce handler, et les tokens sont validés à l’aide de web-token/jwt-bundle. Cette approche est flexible et s’intègre bien avec Symfony et API Platform.

web-token/jwt-bundle

Si vous utilisez le bundle web-token/jwt-bundle (aussi connu sous le nom de web-token/jwt-framework) au lieu de lexik/jwt-authentication-bundle, la configuration sera légèrement différente. Ce bundle est plus flexible et permet de gérer des tokens JWT de manière plus avancée, mais il nécessite une configuration plus détaillée.

Voici comment configurer Symfony avec web-token/jwt-bundle pour utiliser Keycloak dans un contexte OIDC :



1. Installer le bundle

Si vous ne l’avez pas déjà fait, installez le bundle via Composer :

composer require web-token/jwt-bundle

2. Configurer le bundle

Le bundle web-token/jwt-bundle nécessite une configuration pour valider les tokens JWT émis par Keycloak. Voici comment procéder :

a. Configuration de base (config/packages/web_token.yaml)

web_token:
    key_sets:
        keycloak:
            jwkset:
                url: 'https://your-keycloak-server/auth/realms/your-realm/protocol/openid-connect/certs'
                is_public: true
                tags: ['jwt.verifier.key_set']

    checkers:
        keycloak_checker:
            claims:
                - iss: 'https://your-keycloak-server/auth/realms/your-realm'
                - aud: 'your-client-id'
            tags: ['jwt.checker.claims']

    loaders:
        keycloak_loader:
            serializers:
                - jwt_compact
            tags: ['jwt.loader']

b. Configuration de sécurité (config/packages/security.yaml)

Configurez le firewall pour utiliser le bundle web-token/jwt-bundle :

security:
    enable_authenticator_manager: true
    providers:
        keycloak:
            jwt:
                issuer: 'https://your-keycloak-server/auth/realms/your-realm'
                audience: 'your-client-id'
                public_key: 'your-public-key-from-keycloak'  # Ou utilisez jwks_url

    firewalls:
        main:
            pattern: ^/v1  # Assurez-vous que cela correspond au préfixe d'API Platform
            stateless: true
            jwt: ~
            provider: keycloak

3. Créer un JWTTokenAuthenticator

Le bundle web-token/jwt-bundle ne fournit pas d’authentificateur par défaut, donc vous devez en créer un.

a. Créer l’authentificateur

namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Jose\Component\Core\JWKSet;
use Jose\Component\Core\Util\JsonConverter;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;

class JWTTokenAuthenticator extends AbstractAuthenticator
{
    private JWKSet $jwkSet;
    private JWSVerifier $jwsVerifier;

    public function __construct(JWKSet $jwkSet, JWSVerifier $jwsVerifier)
    {
        $this->jwkSet = $jwkSet;
        $this->jwsVerifier = $jwsVerifier;
    }

    public function supports(Request $request): ?bool
    {
        return $request->headers->has('Authorization');
    }

    public function authenticate(Request $request): Passport
    {
        $token = $request->headers->get('Authorization');
        if (null === $token || !preg_match('/^Bearer\s+(.*?)$/', $token, $matches)) {
            throw new AuthenticationException('Invalid token.');
        }

        $jwt = $matches[1];
        $serializer = new CompactSerializer();
        $jws = $serializer->unserialize($jwt);

        if (!$this->jwsVerifier->verifyWithKeySet($jws, $this->jwkSet, 0)) {
            throw new AuthenticationException('Invalid token signature.');
        }

        $payload = JsonConverter::decode($jws->getPayload());
        $userId = $payload['sub'];  // L'identifiant utilisateur Keycloak

        return new SelfValidatingPassport(new UserBadge($userId));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        return new Response('Authentication failed.', Response::HTTP_UNAUTHORIZED);
    }
}

b. Enregistrer l’authentificateur

Dans config/services.yaml :

services:
    App\Security\JWTTokenAuthenticator:
        arguments:
            $jwkSet: '@web_token.key_set.keycloak'
            $jwsVerifier: '@jose.jws_verifier'
        tags: ['security.authenticator']

4. Configurer le UserProvider

Si vous avez besoin de mapper les utilisateurs Keycloak à des entités locales, créez un UserProvider personnalisé comme expliqué précédemment.



5. Tester l’authentification

  • Assurez-vous que Keycloak émet des tokens JWT valides.
  • Envoyez une requête à votre API avec un token JWT dans l’en-tête Authorization: Bearer <token>.
  • Vérifiez que l’utilisateur est bien authentifié et que les routes sont protégées.



6. (Optionnel) Utiliser un AccessTokenHandler

Si vous utilisez un AccessTokenHandler pour valider les tokens, vous pouvez l’adapter pour utiliser web-token/jwt-bundle :

namespace App\Security;

use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Jose\Component\Core\JWKSet;
use Jose\Component\Core\Util\JsonConverter;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;

class AccessTokenHandler implements AccessTokenHandlerInterface
{
    private JWKSet $jwkSet;
    private JWSVerifier $jwsVerifier;

    public function __construct(JWKSet $jwkSet, JWSVerifier $jwsVerifier)
    {
        $this->jwkSet = $jwkSet;
        $this->jwsVerifier = $jwsVerifier;
    }

    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        $serializer = new CompactSerializer();
        $jws = $serializer->unserialize($accessToken);

        if (!$this->jwsVerifier->verifyWithKeySet($jws, $this->jwkSet, 0)) {
            throw new BadCredentialsException('Invalid token signature.');
        }

        $payload = JsonConverter::decode($jws->getPayload());
        $userId = $payload['sub'];  // L'identifiant utilisateur Keycloak

        return new UserBadge($userId);
    }
}

Enregistrez-le dans config/services.yaml :

services:
    App\Security\AccessTokenHandler:
        arguments:
            $jwkSet: '@web_token.key_set.keycloak'
            $jwsVerifier: '@jose.jws_verifier'
        tags: ['security.access_token_handler']

Conclusion

Avec web-token/jwt-bundle, vous avez une configuration plus flexible pour gérer les tokens JWT émis par Keycloak. Vous devez créer un authentificateur personnalisé et configurer le bundle pour valider les tokens. Cette approche est plus complexe que lexik/jwt-authentication-bundle, mais elle offre plus de contrôle sur le processus de validation des tokens.

Compatiblité


1. Compatibilité avec Symfony 6.4

  • Système d’authentification moderne :
    • Symfony 6.4 utilise le système d’authentification moderne basé sur les authenticators, qui est pleinement supporté par la configuration proposée.
    • Le AccessTokenHandler est une fonctionnalité introduite dans Symfony 5.4 et est toujours supportée dans Symfony 6.4.
  • web-token/jwt-bundle :
    • Ce bundle est compatible avec Symfony 6.4. Il fournit des outils pour valider et manipuler les tokens JWT, ce qui est essentiel pour interagir avec Keycloak.
  • Configuration de sécurité :
    • La structure du fichier security.yaml utilisée dans l’exemple est valide pour Symfony 6.4. Les options comme enable_authenticator_manager, access_token, et stateless sont supportées.



2. Compatibilité avec API Platform 4.1

  • Authentification stateless :
    • API Platform 4.1 est conçu pour fonctionner avec des authentifications stateless, ce qui correspond à la configuration proposée (utilisation de tokens JWT).
  • Intégration avec Symfony Security :
    • API Platform s’appuie sur le système de sécurité de Symfony pour gérer l’authentification et l’autorisation. La configuration du AccessTokenHandler et du firewall dans security.yaml est donc parfaitement compatible.
  • Préfixe des routes :
    • API Platform 4.1 utilise un préfixe de route par défaut (/api), mais vous pouvez le modifier (comme montré dans l’exemple avec /v1). Cela n’affecte pas la compatibilité.



3. Points à vérifier pour une intégration réussie

a. Vérifier les dépendances

Assurez-vous que les dépendances suivantes sont installées et à jour :

composer require symfony/security-bundle web-token/jwt-bundle

b. Configuration de Keycloak

  • Vérifiez que Keycloak est correctement configuré pour émettre des tokens JWT valides.
  • Assurez-vous que l’URL du serveur Keycloak, le realm, le client ID et la clé publique sont correctement configurés dans security.yaml.

c. Vérifier les logs

Si l’authentification ne fonctionne pas, consultez les logs Symfony (var/log/dev.log ou var/log/prod.log) pour identifier les erreurs potentielles. Les erreurs courantes incluent : - Des tokens JWT invalides ou mal formatés. - Des problèmes de configuration de la clé publique ou du JWKSet. - Des erreurs dans le AccessTokenHandler.

d. Tester avec un token JWT

Pour vérifier que tout fonctionne correctement, testez avec un token JWT valide émis par Keycloak. Vous pouvez utiliser un outil comme jwt.io pour inspecter le token et vous assurer qu’il contient les claims attendus (iss, aud, sub, etc.).



4. Exemple complet de configuration

Voici un rappel de la configuration complète pour Symfony 6.4 et API Platform 4.1 :

config/packages/security.yaml

security:
    enable_authenticator_manager: true
    providers:
        keycloak:
            jwt:
                issuer: 'https://your-keycloak-server/auth/realms/your-realm'
                audience: 'your-client-id'
                public_key: 'your-public-key-from-keycloak'  # Ou utilisez jwks_url

    firewalls:
        main:
            pattern: ^/v1  # Préfixe des routes API Platform
            stateless: true
            access_token:
                token_handler: App\Security\AccessTokenHandler
            provider: keycloak

    access_control:
        - { path: ^/v1, roles: IS_AUTHENTICATED_FULLY }

config/packages/web_token.yaml

web_token:
    key_sets:
        keycloak:
            jwkset:
                url: 'https://your-keycloak-server/auth/realms/your-realm/protocol/openid-connect/certs'
                is_public: true
                tags: ['jwt.verifier.key_set']

    checkers:
        keycloak_checker:
            claims:
                - iss: 'https://your-keycloak-server/auth/realms/your-realm'
                - aud: 'your-client-id'
            tags: ['jwt.checker.claims']

    loaders:
        keycloak_loader:
            serializers:
                - jwt_compact
            tags: ['jwt.loader']

config/services.yaml

services:
    App\Security\AccessTokenHandler:
        arguments:
            $jwkSet: '@web_token.key_set.keycloak'
            $jwsVerifier: '@jose.jws_verifier'
        tags: ['security.access_token_handler']

5. Conclusion

La méthode décrite est entièrement compatible avec Symfony 6.4 et API Platform 4.1. Elle utilise les fonctionnalités modernes de Symfony pour l’authentification et s’intègre parfaitement avec Keycloak via le bundle web-token/jwt-bundle. Si vous suivez les étapes et les vérifications mentionnées, vous devriez pouvoir configurer une authentification JWT robuste pour votre API.

source:DeepSeek