Authorizing Requests

The Rule Methods

MethodMeaning
permitAll()anyone, logged in or not
authenticated()any logged-in user
denyAll()nobody (block an endpoint completely)
anonymous()only users who are NOT logged in (e.g. a register page)
hasRole("ADMIN")logged-in users with that role (roles in depth: medium level)
.authorizeHttpRequests(auth -> auth
    .requestMatchers(HttpMethod.GET, "/api/products/**", "/api/categories/**").permitAll()
    .requestMatchers("/api/auth/register").anonymous()
    .requestMatchers("/api/internal/**").denyAll()
    .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()  // /css, /js, /images, favicon
    .anyRequest().authenticated())

First Match Wins

Example

Rules are checked top to bottom and the first matching rule decides. Put specific rules first and anyRequest() last:

import java.util.*;
import java.util.function.Predicate;

public class Main {
    record Rule(String pattern, String decision) {}

    static boolean matches(String pattern, String path) {
        if (pattern.equals("anyRequest")) return true;
        if (pattern.endsWith("/**")) return path.startsWith(pattern.substring(0, pattern.length() - 3));
        return pattern.equals(path);
    }

    static String decide(List<Rule> rules, String path) {
        for (Rule r : rules) if (matches(r.pattern(), path)) return r.decision() + "  (rule " + r.pattern() + ")";
        return "no rule";
    }

    public static void main(String[] args) {
        List<Rule> good = List.of(new Rule("/api/products/**", "permitAll"), new Rule("anyRequest", "authenticated"));
        List<Rule> bad  = List.of(new Rule("anyRequest", "authenticated"), new Rule("/api/products/**", "permitAll"));

        for (String path : List.of("/api/products/7", "/api/orders")) {
            System.out.println("GOOD order  " + path + " -> " + decide(good, path));
            System.out.println("WRONG order " + path + " -> " + decide(bad, path));
        }
    }
}

Output

GOOD order  /api/products/7 -> permitAll  (rule /api/products/**)
WRONG order /api/products/7 -> authenticated  (rule anyRequest)
GOOD order  /api/orders -> authenticated  (rule anyRequest)
WRONG order /api/orders -> authenticated  (rule anyRequest)

Note: Spring Security 6 refuses to start if anyRequest() is not the last rule: "Can't configure requestMatchers after anyRequest". Order mistakes between two specific rules (for example /api/** before /api/admin/**) are not detected, so be careful.

Matching by HTTP Method

ShopEase lets everyone read products, but changing them needs a login. Pass an HttpMethod as the first argument:

.authorizeHttpRequests(auth -> auth
    .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
    .requestMatchers(HttpMethod.POST, "/api/products").authenticated()
    .anyRequest().authenticated())

Result

curl -s -o /dev/null -w "GET    %{http_code}\n" localhost:8080/api/products/1
curl -s -o /dev/null -w "POST   %{http_code}\n" -X POST localhost:8080/api/products
curl -s -o /dev/null -w "DELETE %{http_code}\n" -X DELETE localhost:8080/api/products/1

Example output

GET    200
POST   401
DELETE 401

Tip: Default to secure: end with .anyRequest().authenticated() (or denyAll()), so a new endpoint someone forgets to list is protected, not public.