[JAVA] Masquons-nous correctement lors de la sortie du résultat de la liaison de ressort (erreurs) pour journaliser

Dans une application Web qui utilise Spring (Spring MVC), les informations d'erreur de vérification d'entrée sont exprimées par l'interface BindingResult et peuvent être reçues en tant qu'argument de la méthode de gestionnaire du contrôleur comme indiqué ci-dessous.

@PostMapping
public String change(@AuthenticationPrincipal AccountUserDetails userDetails,
    @Validated PasswordChangeForm form, BindingResult result) { // 
  if (result.hasErrors()) {
    return changeForm();
  }
  accountService.changePassword(userDetails.getAccount(), form.getNewPassword());
  return "welcome/home";
}

Il n'y a aucun problème avec le code ci-dessus, mais ajoutons le code suivant et sortons les informations d'erreur dans la console.

System.out.println(result);

Les informations suivantes sont envoyées à la console.

org.springframework.validation.BeanPropertyBindingResult: 6 errors
Field error in object 'passwordChangeForm' on field 'confirmPassword': rejected value [ccc]; codes [com.example.validation.Confirm.message.passwordChangeForm.confirmPassword,com.example.validation.Confirm.message.confirmPassword,com.example.validation.Confirm.message.java.lang.String,com.example.validation.Confirm.message]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.confirmPassword,confirmPassword]; arguments []; default message [confirmPassword],org.springframework.validation.beanvalidation.SpringValidatorAdapter$ResolvableAttribute@41663a9a,PROPERTY,EQUAL,false,org.springframework.validation.beanvalidation.SpringValidatorAdapter$ResolvableAttribute@7dd16c6]; default message [must same value with "newPassword"]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [bb]; codes [Password.passwordChangeForm.newPassword,Password.newPassword,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.newPassword,newPassword]; arguments []; default message [newPassword],true]; default message [must contain 1 or more special characters.]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [bb]; codes [Password.passwordChangeForm.newPassword,Password.newPassword,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.newPassword,newPassword]; arguments []; default message [newPassword],true]; default message [must be 8 or more characters in length.]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [bb]; codes [Password.passwordChangeForm.newPassword,Password.newPassword,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.newPassword,newPassword]; arguments []; default message [newPassword],true]; default message [must contain 1 or more uppercase characters.]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [bb]; codes [Password.passwordChangeForm.newPassword,Password.newPassword,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.newPassword,newPassword]; arguments []; default message [newPassword],true]; default message [must contain 1 or more digit characters.]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [bb]; codes [error.passwordChangeForm.newPassword,error.newPassword,error.java.lang.String,error]; arguments []; default message [error!]

L'erreur "champ", "valeur d'entrée réelle", "code d'erreur", "argument du message", "message par défaut", etc. sont sortis, mais ** "valeur d'entrée réelle (valeur rejetée [ccc];" partie Veuillez noter que ")" est affiché **. Dans l'exemple ci-dessus, ** la valeur de "mot de passe" qui doit être traitée comme des informations confidentielles est sortie telle quelle. ** ** ** En d'autres termes ... S'il est nécessaire de consigner une erreur de vérification d'entrée, le simple fait d'utiliser BindingResult # toString () aboutira à une application qui ne répond pas aux exigences de sécurité. ** **

Note:

Des informations similaires peuvent être enregistrées dans des journaux, etc. sans implémenter explicitement le code qui appelle BindingResult # toString () comme dans l'exemple ci-dessus. Par exemple ... Si vous ne spécifiez pas BindingResult dans l'argument de la méthode de gestionnaire de Controller (= omis), Spring généreraBindException ou MethodArgumentNotValidException. Puisque ces messages d'exception contiennent le même contenu que BindingResult # toString (), si l'implémentation est telle que le journal est sorti sans condition avec un gestionnaire d'exceptions, etc., des informations confidentielles seront affichées involontairement. Il sera affiché dans le journal.

Que devrais-je faire?

Facile à répondre! N'utilisez pas "BindingResult # toString ()" pour la sortie des journaux dans un environnement commercial! !! à propos de ça. Une application Web pour l'interface utilisateur génère une erreur de vérification d'entrée dans le journal! Il semble qu'il n'y ait pas beaucoup d'exigences spécifiques, mais ... Je pense qu'il y a des cas où il est nécessaire de générer des journaux pour l'analyse des erreurs dans les applications Web (API Web, API REST) pour la coopération intersystème. Dans un tel cas, n'utilisez pas simplement "BindingResult # toString ()", mais créez un message de journal approprié à partir des informations d'erreur de BindingResult. Essayez également de gérer séparément «BindException» et «MethodArgumentNotValidException» dans l'implémentation de votre gestionnaire d'exceptions.

Essayez de masquer les informations confidentielles

ici,

Assemblons un message de journal avec l'exigence.

final StringJoiner joiner = new StringJoiner("\n")
    .add(result.getClass().getName() + ":" + result.getErrorCount() + " errors");
result.getGlobalErrors().forEach(error -> joiner.add(error.toString()));
result.getFieldErrors().forEach(error -> {
  if (error.getField().toLowerCase().contains("password")) {
    String message = error.toString();
    int sIndex = message.indexOf("rejected value [") + 16;
    int eIndex = message.indexOf("]; codes");
    joiner.add(message.substring(0, sIndex) + IntStream.range(0, eIndex - sIndex)
        .mapToObj(value -> "*").collect(Collectors.joining()) + message.substring(eIndex));
  } else {
    joiner.add(error.toString());
  }
});
System.out.println(joiner.toString());

J'ai l'impression qu'il s'agit d'un petit dribble forcé (implémentation), mais le message assemblé avec la logique ci-dessus est le suivant.

org.springframework.validation.BeanPropertyBindingResult:6 errors
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [**]; codes [Password.passwordChangeForm.newPassword,Password.newPassword,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.newPassword,newPassword]; arguments []; default message [newPassword],true]; default message [must contain 1 or more uppercase characters.]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [**]; codes [Password.passwordChangeForm.newPassword,Password.newPassword,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.newPassword,newPassword]; arguments []; default message [newPassword],true]; default message [must be 8 or more characters in length.]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [**]; codes [Password.passwordChangeForm.newPassword,Password.newPassword,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.newPassword,newPassword]; arguments []; default message [newPassword],true]; default message [must contain 1 or more digit characters.]
Field error in object 'passwordChangeForm' on field 'confirmPassword': rejected value [***]; codes [com.example.validation.Confirm.message.passwordChangeForm.confirmPassword,com.example.validation.Confirm.message.confirmPassword,com.example.validation.Confirm.message.java.lang.String,com.example.validation.Confirm.message]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.confirmPassword,confirmPassword]; arguments []; default message [confirmPassword],org.springframework.validation.beanvalidation.SpringValidatorAdapter$ResolvableAttribute@6d154140,PROPERTY,EQUAL,false,org.springframework.validation.beanvalidation.SpringValidatorAdapter$ResolvableAttribute@41678dbe]; default message [must same value with "newPassword"]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [**]; codes [Password.passwordChangeForm.newPassword,Password.newPassword,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordChangeForm.newPassword,newPassword]; arguments []; default message [newPassword],true]; default message [must contain 1 or more special characters.]
Field error in object 'passwordChangeForm' on field 'newPassword': rejected value [**]; codes [error.passwordChangeForm.newPassword,error.newPassword,error.java.lang.String,error]; arguments []; default message [error!]

Résumé

Selon le système, il peut être nécessaire de masquer des informations confidentielles lors de la sortie d'un message (message?) Dans le journal, mais le journal de l'application est également le même. C'était une histoire. Dans cet article, j'ai présenté un exemple d'implémentation utilisant une technique appelée masquage, mais le masquage n'est pas le seul moyen de protéger les informations confidentielles, alors faisons-le d'une manière qui convient aux caractéristiques et aux exigences du système! !!

Recommended Posts

Masquons-nous correctement lors de la sortie du résultat de la liaison de ressort (erreurs) pour journaliser
Pour éviter les erreurs lors du démarrage de miChecker