Logging POST Requests in a Spring Web Application

2018-09-11

Tags:
Categories:

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