skip to content

What RequestMatcher does a SecurityFilterChain use if you never call securityMatcher, and why does that matter?

level: middleimportance: should knowfreq 30%

answer

  1. no securityMatcher => AnyRequestMatcher (matches all)
  2. fine for single chain
  3. catch-all must be ordered last
  4. securityMatcher != anyRequest()
  5. two default chains = bug

basics

~10 s

It uses AnyRequestMatcher, so the chain matches every request. That makes it a catch-all, which must be ordered last or it will shadow more specific chains.

solid answer

~40 s

If you never call http.securityMatcher(...), the resulting SecurityFilterChain is built with AnyRequestMatcher.INSTANCE, meaning matches(request) always returns true — the chain applies to every request. That's the intended behavior for a single-chain app (one chain secures everything). But with multiple chains it makes this chain a catch-all: since FilterChainProxy uses first-match, an AnyRequestMatcher chain placed before a specific /api/** chain will match /api/** itself and prevent the specific chain from ever running. So an unmatched (default) chain must be ordered last. Note the distinction: securityMatcher sets this chain-selection matcher; it's different from anyRequest() inside authorizeHttpRequests, which is an authorization rule within an already-selected chain.

code

java · 15 lines
java
// No securityMatcher -> AnyRequestMatcher -> matches every request (catch-all).
@Bean @Order(2)
SecurityFilterChain catchAll(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(a -> a.anyRequest().authenticated()) // authZ rule, NOT chain selection
        .formLogin(Customizer.withDefaults());
    return http.build();
}

// With securityMatcher -> only /actuator/** selects this chain.
@Bean @Order(1)
SecurityFilterChain actuator(HttpSecurity http) throws Exception {
    http.securityMatcher("/actuator/**")
        .authorizeHttpRequests(a -> a.anyRequest().hasRole("ADMIN"));
    return http.build();
}

go deeper

for a junior

Know that with no securityMatcher a chain secures every request.

for a middle

Explain AnyRequestMatcher default and why the catch-all must be last.

for a senior

Distinguish the three matcher layers and diagnose shadowing from logs.

for a principal

Treat catch-all-last as an architectural invariant across many chains.

## The default matcher `HttpSecurity.securityMatcher(...)` sets the `RequestMatcher` that `FilterChainProxy` uses to decide whether the built `SecurityFilterChain` applies. If you **never call it**, `http.build()` produces a `DefaultSecurityFilterChain` whose matcher is `AnyRequestMatcher.INSTANCE`. `AnyRequestMatcher.matches(...)` always returns `true`, so the chain matches **every** request. ### Why this is fine for one chain Most simple apps declare a single `SecurityFilterChain`. With one chain, you *want* it to secure everything, so the `AnyRequestMatcher` default is exactly right and you never call `securityMatcher`. ### Why it's dangerous with multiple chains `FilterChainProxy` selects the **first** matching chain. A chain that matches everything is a catch-all. If ordered before a more specific chain, it wins for all paths and the specific chain becomes dead code. Therefore: - Chains **with** a `securityMatcher` (specific) go first, ordered most-specific-first. - The chain **without** a `securityMatcher` (catch-all) goes **last**. ### The three easily-confused matchers 1. `securityMatcher(...)` — selects whether the whole chain applies (chain selection). Default: `AnyRequestMatcher`. 2. `authorizeHttpRequests(a -> a.requestMatchers(...))` — authorization rules for endpoints *within* a selected chain. 3. `authorizeHttpRequests(a -> a.anyRequest()...)` — the fallback authorization rule inside a selected chain (NOT chain selection). A common mistake is thinking `anyRequest().authenticated()` controls which chain runs — it does not; it controls access *after* the chain is chosen. ### Gotchas - Two chains both defaulting to `AnyRequestMatcher` (both missing `securityMatcher`) is almost always a bug: the earlier one wins for everything. - Startup logs print each chain's matcher; a chain listed as `any request` is your catch-all — confirm it's last. - `securityMatcher` accepts patterns (`/api/**`), a `RequestMatcher`, or HTTP-method-scoped matchers, letting you scope a chain by path and/or method.

  • Is anyRequest() inside authorizeHttpRequests the same as not calling securityMatcher?
    No. anyRequest() is an authorization rule applied after a chain is selected. Not calling securityMatcher sets the chain's selection matcher to AnyRequestMatcher, deciding whether the chain applies at all. Different layers: chain selection vs access control.
  • What's the symptom of two SecurityFilterChain beans that both omit securityMatcher?
    Both default to AnyRequestMatcher, so the lower-@Order one wins for every request and the other never runs — its configuration silently has no effect.

saying these in an interview costs you the question

  • Thinking a chain without securityMatcher matches nothing (it matches everything).
  • Placing the default/catch-all chain before specific chains.
  • Conflating securityMatcher (chain selection) with anyRequest() (authorization).

context