Safeguard
Security Guides•

Spring Security rule order: why does a scan find an open endpoint?

Spring Security applies only the first matching rule, so a broad permitAll above a narrow rule opens routes. How to order rules and end with denyAll.

Safeguard Security Team
3 min read

Spring Security evaluates authorization rules in the order you declare them and applies only the first match. A broad permit rule placed above a narrow restriction silently opens routes, which is a common reason a scan reports an unauthenticated endpoint. Put specific matchers first and end with anyRequest().denyAll().

The rule in the documentation

The Spring Security reference says the AuthorizationFilter "processes these pairs in the order listed, applying only the first match to the request." Once a rule matches, later ones never run for that request. The docs illustrate it with a specific rule for one path followed by anyRequest().authenticated(). Even though anyRequest matches that path too, it never applies, because the earlier rule matched first.

The consequence runs both ways. A specific restriction placed after a broad permit is dead code. A rule placed after anyRequest is never reached.

A misordered example

.authorizeHttpRequests(a -> a
    .requestMatchers("/api/**").permitAll()
    .requestMatchers("/api/admin/**").hasRole("ADMIN")
    .anyRequest().authenticated()
)

Here every path under /api matches the first rule, so the admin restriction never applies. A scanner that requests an admin route without credentials gets a normal response and reports missing authentication.

The corrected order puts the narrow rule first:

.authorizeHttpRequests(a -> a
    .requestMatchers("/api/admin/**").hasRole("ADMIN")
    .requestMatchers("/api/public/**").permitAll()
    .anyRequest().denyAll()
)

Why end with denyAll

The documentation recommends ending with denyAll so unmatched requests are rejected. It gives two reasons: any URL not already matched is denied, which helps if you forget to update your rules, and denying by default "turns the set of rules into an allow list." The alternative, anyRequest().authenticated(), is described as the minimum, since any logged in user then reaches everything not matched earlier.

Final ruleEffect on a new, unlisted route
anyRequest().permitAll()Open to everyone
anyRequest().authenticated()Open to any logged in user
anyRequest().denyAll()Closed until you add a rule

Steps to audit your own rules

  1. List every authorization rule in the order it appears. If you have several SecurityFilterChain beans, list each.
  2. For each rule, ask which later rules it shadows.
  3. Move specific matchers above general ones.
  4. Replace a final permit with authenticated or denyAll.
  5. Write a test per sensitive route that calls it without credentials and with a low privilege user.
  6. Remember that a custom SecurityFilterChain bean makes Spring Boot's actuator defaults back off completely, so you must cover actuator paths yourself. The Spring Boot reference says this explicitly.

For a deeper look at that last point, read custom SecurityFilterChain and actuator security.

What a scan will and will not tell you

A scan can show that a route answered without credentials. It cannot tell you whether that was intended. Keep a short list of routes that are meant to be public, and compare findings against it.

Try it yourself

Click through it at your own pace, or open it full screen. Look for findings that say a route responded without credentials.

Safeguard's SBOM and dependency scanning covers your Spring and Spring Security versions, and a free scan is available.

Quick check

Does Spring Security merge all matching rules?

No. Only the first matching rule applies to a request.

Is authenticated the same as authorized?

No. Authenticated means a logged in identity exists. Role or object level checks are separate.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.