How does authorizeHttpRequests work, and why does the order of its matcher rules matter?
answer
- First matcher that matches wins
- Specific rules before anyRequest()
- anyRequest() = fall-through decision, deny-by-default
- AuthorizationFilter replaced FilterSecurityInterceptor
- hasRole adds ROLE_ prefix
basics
~10 sauthorizeHttpRequests defines authorization rules by matching request paths to access rules like permitAll() or authenticated(). Rules are evaluated top-to-bottom and the first match wins, so specific rules must come before broad ones like anyRequest().
solid answer
~40 sauthorizeHttpRequests configures the AuthorizationFilter, which decides if an authenticated principal may access a request. Inside its lambda you list rules pairing a matcher (requestMatchers("/admin/**"), or the catch-all anyRequest()) with an access decision (permitAll(), authenticated(), hasRole("ADMIN"), hasAuthority(...), access(customManager)). Rules are evaluated in declaration order and the first matcher that matches wins — later rules for the same path are ignored. So you place the most specific rules first and anyRequest() last. anyRequest() is mandatory-ish: if a request matches no rule, Spring Security 6 denies it by default (deny-by-default via AuthorizationFilter). authorizeHttpRequests (backed by AuthorizationFilter) superseded the older authorizeRequests (backed by FilterSecurityInterceptor), which is deprecated. requestMatchers replaced antMatchers/mvcMatchers/regexMatchers.
code
java · 7 lineshttp.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/css/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, "/api/**").hasAuthority("SCOPE_read")
.anyRequest().authenticated());
// Reversing the first two lines would make /admin/** unreachable-as-public? No:
// putting anyRequest().authenticated() FIRST would shadow every later rule.go deeper
Knows rules match paths to permitAll/authenticated.
Explains first-match-wins ordering and anyRequest() placement.
Knows AuthorizationFilter/AuthorizationManager, deny-by-default, requestMatchers migration, hasRole prefix.
Reasons about matcher ambiguity, servlet-path pitfalls, and layering URL authz with method security.
**What it configures.** `authorizeHttpRequests(...)` sets up the **`AuthorizationFilter`**, the filter that performs *authorization* — deciding whether the current (already authenticated or anonymous) principal is allowed to proceed with a request. It replaced the older `authorizeRequests(...)` which wired the legacy `FilterSecurityInterceptor`; the old method is deprecated in favor of the new one, which uses the unified `AuthorizationManager` API. **The rule model.** Inside the lambda you register a sequence of rules. Each rule is: **a matcher** + **an access rule**. - Matchers: `requestMatchers("/admin/**")`, `requestMatchers(HttpMethod.POST, "/api/**")`, or the terminal `anyRequest()`. - Access rules: `permitAll()`, `denyAll()`, `authenticated()`, `anonymous()`, `hasRole("ADMIN")`, `hasAnyRole(...)`, `hasAuthority("SCOPE_read")`, or `access(AuthorizationManager)` for custom logic. ```java http.authorizeHttpRequests(auth -> auth .requestMatchers("/", "/public/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers(HttpMethod.POST, "/api/**").authenticated() .anyRequest().authenticated()); ``` **Why order matters — first match wins.** Rules are evaluated **top to bottom**, and the **first matcher that matches the request determines the outcome**; remaining rules are not consulted. Consequences: - Put **specific** matchers **before** broad ones. If you write `anyRequest().authenticated()` first, it matches everything and your later `/public/**` permitAll never runs — locking out public pages. - Overlapping patterns are resolved by declaration order, not by specificity. Spring does *not* pick the "most specific" rule automatically. **Deny-by-default.** In Spring Security 6, if a request matches **no** rule, the `AuthorizationFilter` denies it (403). This is why `anyRequest()` is effectively required — it makes the fall-through decision explicit. Omitting `anyRequest()` is a common cause of unexpected 403s. **`hasRole` vs `hasAuthority`.** `hasRole("ADMIN")` checks for the authority `ROLE_ADMIN` — Spring prepends the `ROLE_` prefix. `hasAuthority("ROLE_ADMIN")` is the explicit equivalent. Passing `"ROLE_ADMIN"` to `hasRole` is a classic bug (it would look for `ROLE_ROLE_ADMIN`). **`requestMatchers` migration.** In 6.x, `antMatchers`, `mvcMatchers`, and `regexMatchers` were removed in favor of the unified **`requestMatchers`**, which auto-selects an `MvcRequestMatcher` when Spring MVC is present (respecting servlet path, suffix matching, etc.) and falls back to an Ant matcher otherwise. **Gotchas.** - Path traversal / servlet-path subtleties: with a non-root servlet path you may need to be explicit; ambiguous mappings throw at startup by design (a safety feature). - `permitAll()` still runs the filter chain (CSRF, etc.); it only skips the authorization check. - Static-resource ignoring should generally use authorization `permitAll` rather than `WebSecurityCustomizer.ignoring()`, which bypasses the whole chain. **When to use.** Every app that needs URL-based authorization. Method-level security (`@PreAuthorize`) complements but does not replace it.
- What happens if a request matches none of your authorizeHttpRequests rules and you omitted anyRequest()?In Spring Security 6 the AuthorizationFilter denies it by default (deny-by-default), typically a 403. That is why anyRequest() is included to make the fall-through explicit.
- What is the difference between hasRole("ADMIN") and hasAuthority("ADMIN")?hasRole automatically prepends ROLE_, so it checks for authority ROLE_ADMIN. hasAuthority checks the exact string ADMIN with no prefix. Passing ROLE_ADMIN to hasRole is a bug because it looks for ROLE_ROLE_ADMIN.
saying these in an interview costs you the question
- Believing Spring picks the most specific matcher regardless of order
- Passing ROLE_ prefix into hasRole
- Thinking permitAll skips the whole filter chain
- Assuming unmatched requests are permitted by default