In Web application using Spring (Spring MVC), input check error information is expressed by BindingResult
interface and can be received as an argument of Controller's handler method as shown below.
@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";
}
There is no problem with the above code, but let's add the following code and output the error information to the console.
System.out.println(result);
The following information is output to the 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!]
The error "field", "actual input value", "error code", "message argument", "default message", etc. are output, but ** "actual input value (rejected value [ccc];" part Please note that ")" is output **. In the above example, ** the value of "password" that should be treated as confidential information is output as it is. ** **
** In other words ... If there is a requirement to log an input check error, simply using BindingResult # toString ()
will result in an application that does not meet the security requirements. ** **
Note:
Similar information may be recorded in logs etc. without implementing the code that explicitly calls
BindingResult # toString ()
as in the above example. For example ... If you don't specifyBindingResult
in the argument of Controller's handler method (= omitted), Spring will generateBindException
orMethodArgumentNotValidException
. Since these exception messages contain the same content asBindingResult # toString ()
, if the implementation is such that the log is output unconditionally with an exception handler etc., confidential information will be unintentionally displayed. It will be output to the log.
Easy to answer! Do not use "BindingResult # toString ()
" for logs output in a commercial environment! !! about it.
The input check error is output to the log in the web application for UI! It seems that there are not many specific requirements, but ... I think that there are cases where it is required to output logs for error analysis in Web applications (Web API, REST API) for inter-system cooperation.
In such a case, instead of simply using "BindingResult # toString ()
", let's create an appropriate log message from the error information of BindingResult
.
Also, make sure to handle BindException
and MethodArgumentNotValidException
separately in the exception handler implementation.
here,
BindingResult # toString ()
"*
"Let's assemble a log message with the requirement.
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());
I feel that it is a little forcible dribbling (implementation), but the message assembled with the above logic is as follows.
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!]
Depending on the system, it may be required to mask confidential information when outputting a message (telegram?) To the log, but the application log is also the same. It was a story. In this post, I introduced an implementation example using a technique called masking, but masking is not the only way to protect confidential information, so let's do it in a way that suits the characteristics and requirements of the system! !!