IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Java Discussion :

[JWT] Votre avis concernant l'implémentation de mon api JWT


Sujet :

Java

  1. #1
    Modérateur
    Avatar de Gugelhupf
    Homme Profil pro
    Analyste Programmeur
    Inscrit en
    Décembre 2011
    Messages
    1 320
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Analyste Programmeur

    Informations forums :
    Inscription : Décembre 2011
    Messages : 1 320
    Points : 3 741
    Points
    3 741
    Billets dans le blog
    12
    Par défaut [JWT] Votre avis concernant l'implémentation de mon api JWT
    Bonjour,


    Contexte :
    J'ai créé une API utilitaire qui sérialise et désérialise trois familles de données : binaire (Java, gRPC), chaine de caractères (Base64, JWT, XML), fichier (Excel, CSV).
    J'ai nommé cet API "universal-serializer" (lien Github). J'avais déjà créé un sujet pour présenter ce projet ici.


    Voici un exemple d'utilisation de mon api JWT :
    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    String SECRET = "546T78UINqqsvfzfs<vs<sdv_-('U87Y89YG87";
    JwtSerializer<MyClass> s = new JwtSerializer<>(MyClass.class, Algorithm.HS256, SECRET);
    String jsonWebToken = s.serialize(new MyClass());
    MyClass deserialized = s.deserialize(jsonWebToken);
    La source de la classe d'implémentation JwtSerializer se trouve ici (lien Github). Cette classe d'implémentation utilise javax.crypto.Mac.

    Questions :
    1. Voyez-vous des failles de sécurité dans ma classe d'implémentation JwtSerializer ?
    2. Est-ce que mon API respecte la RFC-7519 ? Y a t-il des choses qui manquent ? Avez-vous des suggestions d'amélioration ?



    Je vous remercie par avance, cordialement,
    N'hésitez pas à consulter la FAQ Java, lire les cours et tutoriels Java, et à poser vos questions sur les forums d'entraide Java

    Ma page Developpez | Mon profil Linkedin | Vous souhaitez me contacter ? Contacter Gokan EKINCI

  2. #2
    Membre expérimenté Avatar de Ivelios
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2008
    Messages
    1 031
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 031
    Points : 1 540
    Points
    1 540
    Par défaut
    Salut,

    Je n'ai regardé que la class : class JwtSerializer<T> extends AbstractStringSerializer<T> .
    Plusieurs remarques :
    • Pourquoi passer le type de classe au constructeur? Autant la passer à la méthode "deserialize" qui est la seule à l'utiliser
    • Pourquoi générer le header dans le constructeur? Autant le générer dans la méthode "serialize" qui est la seule à l'utiliser
    • Pourquoi faire des opérations dans le constructeur? N'est-il pas plus pratique de les faire dans les deux méthodes, dites utilitaires, static ou un singleton ou une fabrique ?
    • Le header est bien généré RAS
    • La signature est bien généré, mais pourquoi signer les bytes? signature = hmac((base64UrlEncodedHeader + "." + base64UrlEncodedPayload).getBytes())
    • JSON Web Token (JWT) is a compact
      Transformer un object en JSON (puis en bytes) c'est pas super compact selon moi, ça dépend de l'objet c'est toujours pareil
    • base64UrlEncodedPayload = encoder.encodeToString(gson.toJson(payloadObject).getBytes()); Pourquoi prendre les bytes pour le body? l'encodage du json en base64 est suffisant en principe
    • Il manque la date d'expiration dans le corps du JWT, il est donc valable advitame eternam ( sauf si elle se trouve dans la class MyClass )

    L'idée que je me fais du JWT, lorsque je l'utilise, c'est plutôt que de passer un Object, passer une MAP de claims avec des clés sous forme de trigramme. Et dans la méthode serialize ajouter tous les claims au JWT + date d'expiration (+date de création), et concaténer avec le header et la signature généré.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    Map<String,String> claims;
    ...
    Enum Claim{ ID_USER("idu"), RIGHT("rgh"), TRUCMUCH("trm"),}
    claims.put(Claim.ID_USER.code,"5685")
    claims.add(Claim.RIGHT.code,"admin")
     
    String JWTEncoded = JWTSerializer.serialise(Algorithm.HS256,"secretKey",claims);
    En espérant avoir été le plus clair possible.

    Bonne continuation.
    Il était une fois [...] Et ils vécurent heureux et eurent beaucoup d'enfants!

  3. #3
    Modérateur
    Avatar de Gugelhupf
    Homme Profil pro
    Analyste Programmeur
    Inscrit en
    Décembre 2011
    Messages
    1 320
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Analyste Programmeur

    Informations forums :
    Inscription : Décembre 2011
    Messages : 1 320
    Points : 3 741
    Points
    3 741
    Billets dans le blog
    12
    Par défaut
    Bonjour Ivelios, merci pour ton retour,


    Pourquoi passer le type de classe au constructeur?
    Pour respecter la signature de l'interface Serializer et former une harmonie avec l'ensemble des implémentations qui ne requierent pas de Class<T>.

    Pourquoi générer le header dans le constructeur? Autant le générer dans la méthode "serialize" qui est la seule à l'utiliser [...] Pourquoi faire des opérations dans le constructeur?
    Par souci d'optimisation. Le constructeur ne sera instanciée qu'une seule fois, la méthode serialize() sera exécutée plusieurs fois.

    La signature est bien généré, mais pourquoi signer les bytes? signature = hmac((base64UrlEncodedHeader + "." + base64UrlEncodedPayload).getBytes()) [...] Transformer un object en JSON (puis en bytes) c'est pas super compact selon moi, ça dépend de l'objet c'est toujours pareil
    D'après l'exemple donné sur le site de JWT (jwt.io), c'est ainsi qu'est constitué le troisième morceau du token JWT :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret
    )
    Que proposes-tu comme alternative ?

    -------

    base64UrlEncodedPayload = encoder.encodeToString(gson.toJson(payloadObject).getBytes()); Pourquoi prendre les bytes pour le body? l'encodage du json en base64 est suffisant en principe
    C'est la méthode encodeToString() qui prend un tableau de byte en paramètre.

    Il manque la date d'expiration dans le corps du JWT, il est donc valable advitame eternam ( sauf si elle se trouve dans la class MyClass )
    Oui tout à fait, le header de ce constructeur est la version la plus basique (contient les clés obligatoires), je pense créer un deuxième constructeur où il sera possible d'ajouter un header plus custom.


    Merci pour tes retours
    N'hésitez pas à consulter la FAQ Java, lire les cours et tutoriels Java, et à poser vos questions sur les forums d'entraide Java

    Ma page Developpez | Mon profil Linkedin | Vous souhaitez me contacter ? Contacter Gokan EKINCI

  4. #4
    Membre expérimenté Avatar de Ivelios
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2008
    Messages
    1 031
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 031
    Points : 1 540
    Points
    1 540
    Par défaut
    Re-Bonjour,

    La signature est bien généré, mais pourquoi signer les bytes?
    Je reformule : pourquoi signer des bytes et pas la chaine de caractère directement. Mais si hmac() attend dès bytes en paramètre... il n'y a pas le choix.
    Fin du quiproquo donc pas d'alternative, il faut signer le bytes(header+payload)

    Autrement, concernant le paramètre "type" du constructeur, Je l'aurais bien enlevé pour plus de simplicité et pour éviter le doublon "MyClass" à l'appel new JwtSerializer<MyClass>(MyClass.class, Algorithm.HS256, SECRET);.

    Je ne sais pas ce que retourne gson.toJson() mais je reste toujours dubitatif quand à la taille qu'aura au final le JWT, en utilisant un Object jsonifié pour remplir ses claims gson.toJson(payloadObject). A voir si ça ne donne pas quelque chose d'énorme à l'utilisation !

    Bonne continuation
    Il était une fois [...] Et ils vécurent heureux et eurent beaucoup d'enfants!

  5. #5
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    Quelques remarques vite fait.

    Pas de support clé publique / clé privée. Ce qui est dommage car un des usage de JWT, c'est d'être émis par une partie pour être vérifié par une autre partie. Le vérificateur n'ayant pas le droit d'émettre.
    Comme déjà mentionné, pas de génération / vérification de la limite de validité.
    Utilisation de getBytes() dangereux. Ca utilise l'encodage de la plateforme par défaut. Tu auras des problèmes sérieux si cet encodage par défaut est UTF-16 ou en tout cas non rétro compatible avec ansi
    La spec dit que tu dois supporter l'algorithme 'none' (aussi étrange que ça puisse paraître).
    Même si ce n'est pas obligatoire, il serait utile d'avoir aussi la date d'émission, l'intended audience et le subject dans le token.

  6. #6
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    Citation Envoyé par Ivelios Voir le message

    Je ne sais pas ce que retourne gson.toJson() mais je reste toujours dubitatif quand à la taille qu'aura au final le JWT, en utilisant un Object jsonifié pour remplir ses claims gson.toJson(payloadObject). A voir si ça ne donne pas quelque chose d'énorme à l'utilisation !

    Bonne continuation
    Je crois que ça dépendre surtout de l'objet. Si c'est une objet avec quelques propriétés, ok, si c'est un arbre gigantesque avec la moitié de la base de donnée, ce sera plus chaud

  7. #7
    Modérateur
    Avatar de Gugelhupf
    Homme Profil pro
    Analyste Programmeur
    Inscrit en
    Décembre 2011
    Messages
    1 320
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Analyste Programmeur

    Informations forums :
    Inscription : Décembre 2011
    Messages : 1 320
    Points : 3 741
    Points
    3 741
    Billets dans le blog
    12
    Par défaut
    @tchize_
    Utilisation de getBytes() dangereux. Ca utilise l'encodage de la plateforme par défaut. Tu auras des problèmes sérieux si cet encodage par défaut est UTF-16 ou en tout cas non rétro compatible avec ansi
    Ah là ça m'intéresse, mais je ne vois pas le niveau de "danger" en fait , si on reçoit une chaine UTF-8 et que notre système par défaut gère de l'UTF-16, alors il y aura au pire une incompatibilité (ce qui résultera par une SignatureDeserializationException), mais sachant que les méthodes de sérialisation et désérialisation utilisent le même encodage par défaut alors il ne devrait pas y avoir de problème. Que préconises-tu ? d'utiliser getBytes(StandardCharsets.UTF_8) à chaque fois ?
    Je vais voir pour le support de RSA, et autres paramètres facultatifs liées au header plus tard.


    @Ivelios,
    Autrement, concernant le paramètre "type" du constructeur, Je l'aurais bien enlevé pour plus de simplicité et pour éviter le doublon "MyClass" à l'appel new JwtSerializer<MyClass>(MyClass.class, Algorithm.HS256, SECRET);
    Le passage de ce paramètre est nécessaire en Java (cf: type erasure).


    Merci pour vos retours
    N'hésitez pas à consulter la FAQ Java, lire les cours et tutoriels Java, et à poser vos questions sur les forums d'entraide Java

    Ma page Developpez | Mon profil Linkedin | Vous souhaitez me contacter ? Contacter Gokan EKINCI

  8. #8
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    Le problème c'est surtout que tu risque de générer un token non valide.
    Citation Envoyé par Gugelhupf Voir le message
    Que préconises-tu ? d'utiliser getBytes(StandardCharsets.UTF_8) à chaque fois ?
    Ce n'est pas ce que je préconise, c'est ce que le RFC JWT impose:
    Let the Message be the octets of the UTF-8 representation of the JWT Claims Set.

Discussions similaires

  1. [PHP 5.1] Votre avis sur la Conception de mon site
    Par iviewclear dans le forum Langage
    Réponses: 2
    Dernier message: 12/01/2011, 11h04
  2. Votre avis sur le design de mon site
    Par hamlesbettraves dans le forum Mon site
    Réponses: 2
    Dernier message: 10/01/2011, 20h43
  3. Votre avis concernant un cms pour portail communautaire
    Par scent dans le forum EDI, CMS, Outils, Scripts et API
    Réponses: 1
    Dernier message: 01/10/2008, 13h11
  4. Réponses: 35
    Dernier message: 03/04/2007, 16h32

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo