For the latest stable version, please use Spring Security 6.1.11!

ServerWebExchangeFirewall

There are various ways a request can be created by malicious users that can exploit applications. Spring Security provides the ServerWebExchangeFirewall to allow rejecting requests that look malicious. The default implementation is StrictServerWebExchangeFirewall which rejects malicious requests.

For example a request could contain path-traversal sequences (such as /../) or multiple forward slashes (//) that could also cause pattern-matches to fail. Some containers normalize these out before performing the servlet mapping, but others do not. To protect against issues like these, WebFilterChainProxy uses a ServerWebExchangeFirewall strategy to check and wrap the request. By default, un-normalized requests are automatically rejected, and path parameters are removed for matching purposes. (So, for example, an original request path of /secure;hack=1/somefile.html;hack=2 is returned as /secure/somefile.html.) It is, therefore, essential that a WebFilterChainProxy is used.

In practice, we recommend that you use method security at your service layer, to control access to your application, rather than rely entirely on the use of security constraints defined at the web-application level. URLs change, and it is difficult to take into account all the possible URLs that an application might support and how requests might be manipulated. You should restrict yourself to using a few simple patterns that are simple to understand. Always try to use a “deny-by-default” approach, where you have a catch-all wildcard (/ or ) defined last to deny access.

Security defined at the service layer is much more robust and harder to bypass, so you should always take advantage of Spring Security’s method security options.

You can customize the ServerWebExchangeFirewall by exposing it as a Bean.

Allow Matrix Variables
  • Java

  • Kotlin

@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
    StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
    val firewall = StrictServerWebExchangeFirewall()
    firewall.setAllowSemicolon(true)
    return firewall
}

To protect against Cross Site Tracing (XST) and HTTP Verb Tampering, the StrictServerWebExchangeFirewall provides an allowed list of valid HTTP methods that are allowed. The default valid methods are DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT. If your application needs to modify the valid methods, you can configure a custom StrictServerWebExchangeFirewall bean. The following example allows only HTTP GET and POST methods:

Allow Only GET & POST
  • Java

  • Kotlin

@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
    StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
    firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
    return firewall;
}
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
    val firewall = StrictServerWebExchangeFirewall()
    firewall.setAllowedHttpMethods(listOf("GET", "POST"))
    return firewall
}

If you must allow any HTTP method (not recommended), you can use StrictServerWebExchangeFirewall.setUnsafeAllowAnyHttpMethod(true). Doing so entirely disables validation of the HTTP method.

StrictServerWebExchangeFirewall also checks header names and values and parameter names. It requires that each character have a defined code point and not be a control character.

This requirement can be relaxed or adjusted as necessary by using the following methods:

  • StrictServerWebExchangeFirewall#setAllowedHeaderNames(Predicate)

  • StrictServerWebExchangeFirewall#setAllowedHeaderValues(Predicate)

  • StrictServerWebExchangeFirewall#setAllowedParameterNames(Predicate)

Parameter values can be also controlled with setAllowedParameterValues(Predicate).

For example, to switch off this check, you can wire your StrictServerWebExchangeFirewall with Predicate instances that always return true:

Allow Any Header Name, Header Value, and Parameter Name
  • Java

  • Kotlin

@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
    StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
    firewall.setAllowedHeaderNames((header) -> true);
    firewall.setAllowedHeaderValues((header) -> true);
    firewall.setAllowedParameterNames((parameter) -> true);
    return firewall;
}
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
    val firewall = StrictServerWebExchangeFirewall()
    firewall.setAllowedHeaderNames { true }
    firewall.setAllowedHeaderValues { true }
    firewall.setAllowedParameterNames { true }
    return firewall
}

Alternatively, there might be a specific value that you need to allow.

For example, iPhone Xʀ uses a User-Agent that includes a character that is not in the ISO-8859-1 charset. Due to this fact, some application servers parse this value into two separate characters, the latter being an undefined character.

You can address this with the setAllowedHeaderValues method:

Allow Certain User Agents
  • Java

  • Kotlin

@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
    StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
    Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
    Pattern userAgent = ...;
    firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
    return firewall;
}
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
    val firewall = StrictServerWebExchangeFirewall()
    val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*")
    val userAgent = Pattern.compile(...)
    firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }
    return firewall
}

In the case of header values, you may instead consider parsing them as UTF-8 at verification time:

Parse Headers As UTF-8
  • Java

  • Kotlin

firewall.setAllowedHeaderValues((header) -> {
    String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
    return allowed.matcher(parsed).matches();
});
firewall.setAllowedHeaderValues {
    val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
    return allowed.matcher(parsed).matches()
}