Description
Updated
When the HTTP Method is rejected MethodRejectedException will be thrown.
NOTE: We are not going to do custom exceptions for every type of rejection. Instead we will start with HTTP Methods since that is the requirement in this issue.
Original
Expected Behavior
The StrictHttpFirewall
should throw specialized RequestRejectedException
exceptions for each type of rejection, making it easier to map rejections to appropriate HTTP response status codes by configuring RequestRejectedHandler
s.
I believe the best approach would be to introduce new exceptions that extend the existing RequestRejectedException
. If this approach is taken, it might be appropriate to introduce a new type of RequestRejectedHandler
that accepts a generic <? extends RequestRejectedException>
parameter.
private void rejectForbiddenHttpMethod(HttpServletRequest request) {
if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
return;
}
if (!this.allowedHttpMethods.contains(request.getMethod())) {
throw new ForbiddenHttpMethodException(
"The request was rejected because the HTTP method \"" + request.getMethod()
+ "\" was not included within the list of allowed HTTP methods " + this.allowedHttpMethods);
}
}
public class ForbiddenHttpMethodException extends RequestRejectedException {
public ForbiddenHttpMethodException(String message) {
super(message);
}
}
@Bean
public TypedRequestRejectedHandler<ForbiddenHttpMethodException> forbiddenMethodHandler() {
return (request, response, exception) ->
response.setStatus(Status.NOT_IMPLEMENTED.getStatusCode());
}
Another approach would be to introduce a flag in RequestRejectedException
indicating the rejection reason, allowing RequestRejectedHandler
to return the correct HTTP status according to the rejection reason of the exception.
class RequestRejectedException extends RuntimeException {
private RequestRejectedReason reason;
public RequestRejectedException(String message) {
this(message, RequestRejectedReason.GENERIC);
}
public RequestRejectedException(String message, RequestRejectedReason reason) {
super(message);
this.reason = reason;
}
public RequestRejectedReason getReason() {
return reason;
}
public enum RequestRejectedReason {
FORBIDDEN_METHOD,
BLOCKLISTED_URL,
UNTRUSTED_HOST,
NOT_NORMALIZED,
NOT_PRINTABLE_ASCII_CHARS,
INVALID_HEADER_NAME,
INVALID_HEADER_VALUE,
INVALID_PARAM_NAME,
INVALID_PARAM_VALUE,
GENERIC
}
}
Current Behavior
The StrictHttpFirewall
currently throws RequestRejectedException
for all types rejections. This makes it a bit difficult to target specific types of request rejections for specific HTTP response status codes.
If we introduce a RequestRejectedHandler
, the handler catches all types of rejections (forbidden methods, blocklisted URLs, untrusted hosts, etc.). If we needed to map only one rejection type (e.g. forbidden method) to a different HTTP status (404, 501, etc), we would need to introduce logic that compares against the exception message, which would be brittle.
Context
My team is developing a server implementation of the IEEE-2030.5-2018 communication protocol. To meet standardized conformance tests of the Common Smart Inverter Profile (CSIP), our application must respond with a 501 Not Implemented
when an invalid HTTP method (e.g. FOO) is used.
Currently there's no elegant way to target this specific type of firewall rejection. For now, we simply register a handler that maps all rejections to a status of 501
.
@Bean
public RequestRejectedHandler requestRejectedHandler() {
return (request, response, exception) -> response.setStatus(Status.NOT_IMPLEMENTED.getStatusCode());
}