2018-09-11
Problem
You want to log all POST requests, with all their parameters, but without leaking any passwords.
Solution
Spring has an abstract filter named AbstractRequestLoggingFilter
, with two concrete implementations, but both of them log all requests. This means on every page load we get one log entry for each CSS / JS / PNG resource file (that is, every GET request). Being interested only in POST requests, we make our own implementation, which overrides the shouldLog
method.
Also, our application may have some forms where users may change their passwords. We certainly don't want to log these, so if a request has a "password" parameter we'll want to apply some censorship.
public class PostRequestLoggingFilter extends AbstractRequestLoggingFilter {
private static final String POST = RequestMethod.POST.toString();
@Override
protected boolean shouldLog(HttpServletRequest request) {
return logger.isDebugEnabled() && POST.equals(request.getMethod());
}
@Override
protected void afterRequest(HttpServletRequest request, String message) {
String censoredMessage = passwordRemover.removePassword(message);
logger.debug(censoredMessage);
}
}
An implementation of the PasswordRemover
required in our logging filter may look like this:
public class RegExPasswordRemover implements PasswordRemover {
/**
* Characters that may not appear unescaped in an URI:
*
* <pre>
* ! * ' ( ) ; : @ & = + $ , / ? # [ ]
* </pre>
*
* See <a href="https://tools.ietf.org/html/rfc3986#section-2.2">RFC 3986
* section 2.2 Reserved Characters</a>.
*/
private static final String URI_RESERVED_CHARACTERS = "!*'();:@&=+$,/?#[]";
private static final Pattern PATTERN = Pattern
.compile("(password|passwordAgain)=([^" + Pattern.quote(URI_RESERVED_CHARACTERS) + "]*)");
private static final String REPLACEMENT = "***";
@Override
public String removePassword(String uri) {
/*
* In an URI, when encountering a parameter named "password" or "passwordAgain",
* replace its value with three stars (*).
*/
return PATTERN.matcher(uri).replaceAll("$1=" + REPLACEMENT);
}
}
For example, if the user submits a form with two fields: one containing their email address, someone@example.com
and the other containing their password, secret
, then the relevant part of the original log message would be:
uri=/my-account;payload=email=someone%40example.com&password=secret
The above implementation of the password remover would transform this into:
uri=/my-account;payload=email=someone%40example.com&password=***
Show me the code
There's a complete example for the solution described above.
Sources
- Spring's default logging filter
- https://stackoverflow.com/a/30652024/479288
- The list of reserved characters in an URI
- https://en.wikipedia.org/wiki/Percent-encoding