I am trying to add my custom StrictHttpFirewall to be able to reject requests in case of headers are not matching my pattern.
I have the following configuration of Spring security:
@Configuration
@EnableWebSecurity
class SecurityConfig {
companion object {
private val HEADER_NAME_PATTERN = "^[a-z1-9_-]{1,512}$".toRegex(RegexOption.IGNORE_CASE)
private val HEADER_VALUE_BASE_PATTERN = "[\\p{IsAssigned}&&[^\\p{IsControl}]]*".toRegex()
private val HEADER_VALUE_ERROR_PATTERN = "[<>]".toRegex()
}
@Bean
fun requestRejectedHandler(): RequestRejectedHandler = ErrorResponseRequestRejectedHandler()
@Bean
fun httpFirewall(): HttpFirewall {
val firewall = StrictHttpFirewall()
val headerNames = { header: String -> HEADER_NAME_PATTERN.matches(header) }
val headerValues = { value: String ->
HEADER_VALUE_BASE_PATTERN.containsMatchIn(value).and(
!HEADER_VALUE_ERROR_PATTERN.containsMatchIn(value)
)
}
firewall.setAllowedHeaderNames(headerNames)
firewall.setAllowedHeaderValues(headerValues)
firewall.setAllowedHttpMethods(
listOf(GET.name(), POST.name(), PUT.name(), PATCH.name(), DELETE.name())
)
return firewall
}
@Bean
@Throws(Exception::class)
fun filterChain(http: HttpSecurity): SecurityFilterChain =
http
.authorizeHttpRequests { it.anyRequest().permitAll() }
.sessionManagement {
it
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.disable()
}
.headers {
it
.frameOptions { option -> option.sameOrigin() }
.xssProtection(Customizer.withDefaults())
.cacheControl { cache -> cache.disable() }
.contentSecurityPolicy(Customizer.withDefaults())
}
.cors { it.disable() }
.csrf { it.disable() }
.requestCache { it.disable() }
.build()
}
But unfortunately it's not working, I am still able to pass headers that is not match my regex. I have a long debugging session, and it seams like request itself (made by mockMVC) having all the headers, but spring security is not checking the headers from request itself. Interesting note: it's validating the header that I specified as mandatory on my controller method parameter level - like @RequestHeader(value = "my-header", required = true). Would be appreciate for any help.
Already tried:
request.headerNames.toList()
because the request itself is wrapped internally with HttpFirewallRequest implementation, that have overriden method getHeaders
with the validation that I actually expected.mockMvc
but also RestTemplate
and just make the request via postman - no successStrictHttpFirewall
checks for a header but lazily. Also, you have to notice that StrictHttpFirewall
wraps the request and response both into Wrapper
.
With this, StrictHttpFirewall
won't actually reject the request unless down the filterChain
, a request is made to access one of the black listed headers. It means even if you have a blacklisted header, but nobody in filter chain or controller is trying to read from that header, your request won't be rejected until it is attempted to be read.
In PR where such protection was introduced, a discussion was held related to performance of such header check as this check would be executed multiple times.
To address your requirement, I don't think this can be handled by StrictHttpFirewall
. To reject a request eagerly, you will have to write a filter that sits at the beginning of filter chain, or at the mvc layer, checks for presence of such headers and rejects it.
One advantage that you can have by introducing request in filter chain is FilterChainProxy
accepts a RequestReject(ed)Handler
that will be able to handle RequestReject(ed)Exception
should you choose to throw it.