J'utilise Tomcat 8.5.59 et j'ai le domaine suivant dans mon context.xml :

<Realm className="org.apache.catalina.realm.LockOutRealm" >
    <Realm className="org.apache.catalina.realm.DataSourceRealm" dataSourceName="jdbc/MyName" localDataSource="true">
      <CredentialHandler className="org.apache.catalina.realm.NestedCredentialHandler">
        <CredentialHandler className="org.apache.catalina.realm.SecretKeyCredentialHandler" />
        <CredentialHandler className="org.apache.catalina.realm.MessageDigestCredentialHandler" algorithm="SHA-512" />
      </CredentialHandler>
    </Realm>
</Realm>

J'aimerais obtenir le CredentialHandler dans mon application Java pour hacher et stocker les mots de passe. Selon la présentation de Christopher Schultz sur http://people.apache.org/~schultz/ApacheCon%20NA%202016/Seamless%20Upgrades%20for%20Credential%20Security%20in%20Apache%20Tomcat.pdf Je comprends ça comme ça :

CredentialHandler ch = (CredentialHandler) application.getAttribute(Globals.CREDENTIAL_HANDLER);

Le problème est qu'il renvoie un gestionnaire d'informations d'identification vide/par défaut, pas celui que j'ai configuré.

Si je supprime la définition LockoutRealm, cela fonctionne bien, il semble donc que le fait d'avoir un domaine imbriqué (LockoutRealm, DataSourceRealm) entraîne son échec. En parcourant le code Tomcat, il semble que le code qui appelle setAttribute(Globals.CREDENTIAL_HANDLER) ne prenne pas en compte CombinedRealm.

Comment puis-je obtenir le gestionnaire d'informations d'identification configuré dans mon application afin que je puisse appeler les méthodes .matches() et .mutate() ? Je préfère ne pas supprimer le Lockout Realm car cela compromettrait la sécurité.


Éditer:

Ce cas d'utilisation est courant : l'application doit pouvoir enregistrer le mot de passe muté dans la base de données afin que Tomcat puisse effectuer une authentification basée sur un formulaire lorsque les utilisateurs se connectent. Chaque fois qu'un nouveau compte utilisateur est créé, le mot de passe de l'utilisateur doit être muté et enregistré dans la base de données.

De plus, lorsqu'un utilisateur souhaite changer son mot de passe, un formulaire demande les mots de passe actuel et nouveau - .matches() est utilisé pour confirmer que le mot de passe "actuel" correspond à son mot de passe existant avant de le remplacer par le nouveau mot de passe (qui encore une fois doit être muté).

L'utilisation du gestionnaire d'informations d'identification défini dans le contexte est importante pour garantir que le mot de passe est muté de la manière exacte requise par Tomcat. L'alternative serait que chaque application fournisse ses propres bibliothèques correspondantes, ce qui semble inutile et sujet aux erreurs étant donné que Tomcat les possède déjà.

Tout cela est décrit dans le lien de Christopher Schultz au début de ma question.

Je pense que tout cela est assez standard et Tomcat fournit CredentialHandler ch = (CredentialHandler) application.getAttribute(Globals.CREDENTIAL_HANDLER) à cette fin. Le problème est que l'implémentation ne tient pas compte de l'utilisation de LockoutRealm - elle suppose que le gestionnaire d'informations d'identification est défini directement sous le domaine de niveau supérieur, donc je me demande s'il s'agit d'un oubli/bogue dans Tomcat ou si cela fonctionne comme ça par conception et s'il existe un moyen d'accéder aux fonctions .mutate() et .matches() du CredentialHandler défini tout en utilisant également le LockOutRealm.

De plus, tomcat fournissait RealmBase.Digest(String credentials, String algorithm, String encoding) pour ce cas d'utilisation, mais cette méthode est dépréciée dans 8.5 et supprimée dans 9, donc je comprends que CredentialHandler ch = (CredentialHandler) application.getAttribute(Globals.CREDENTIAL_HANDLER) est la nouvelle façon dont nous sommes censés le faire.


Modifier 2:

getAttribute(Globals.CREDENTIAL_HANDLER) renvoie un CredentialHandler par défaut qui ne crypte pas du tout le mot de passe. Le débogueur montre que la classe est StandardContext. En parcourant le code, tout semble correct, mais il n'explore pas le DataSourceRealm pour obtenir le NestedCredentialHandler défini, à la place, il regarde LockoutRealm, ne trouve pas de réponse immédiate child CredentialHandler, donc crée et renvoie un par défaut. Je pense que c'est comme documenté dans https://tomcat.apache.org /tomcat-8.5-doc/config/credentialhandler.html :

Un élément CredentialHandler DOIT être imbriqué dans un composant Realm. S'il n'est pas inclus, un CredentialHandler par défaut sera créé à l'aide de MessageDigestCredentialHandler.

1
Edward 8 nov. 2020 à 03:28

1 réponse

Meilleure réponse

Maintenant que les cas d'utilisation sont clairs. Ce que vous mentionnez dans vos modifications est correct. Cela commence par RealmBase classe (voir : méthode protected void startInternal()). Ceci, si le gestionnaire d'informations d'identification est null (ce qui dans votre cas est sur LockoutRealm), définit le gestionnaire d'informations d'identification par défaut comme MessageDigestCredentialHandler sans aucun algorithme, le comportement du gestionnaire d'informations d'identification si aucun algorithme n'est spécifié est d'utiliser plainText.

Ensuite, regardez le StandardContext (voir : méthode protected void startInternal(), ligne 5016-5027), les lignes 5019 et 5024 sont importantes. Ici, le domaine est récupéré avec l'appel 'getRealmInternal()', qui renvoie le domaine configuré, qui dans votre cas est LockoutRealm et le 'getCredentialHandler()' suivant renvoie le gestionnaire d'informations d'identification par défaut comme mentionné ci-dessus (car la méthode est envoyée à RealmBase, un point à noter, le CombinedRealm ou le LockoutRealm ne remplace pas cette méthode).

C'est pourquoi le mot de passe n'est pas muté.

Ce dont vous avez besoin semble possible. Il ressort de ce qui précède que lorsque le 'getCredentialHandler()' est appelé sur le domaine, le domaine doit le gérer au lieu de l'envoyer à RealmBase.

Par conséquent, vous pouvez effectuer les opérations ci-dessous :

  1. Étendez LockoutRealm avec votre classe de royaume (disons XRealm)
  2. Remplacer la méthode getCredentialHandler
  3. Dans la méthode, accédez aux « domaines de liste finale protégés » de CombinedRealm, votre DataSourceRealm doit être le 0ème élément
  4. Appelez le getCredentialHandler sur le DataSourceRealm et retournez
  5. Lorsque StandardContext appelle getCredentialHandler, il doit obtenir le NestedCredentialHandler que vous avez configuré

Ce qui précède devrait résoudre le problème.

0
Ironluca 10 nov. 2020 à 18:16