Anonymous users and permitAll

Spring Security · lesson 18 of 31 · 3 min read

Understand why the context is never empty on a permitAll route, and which check proves a real login.

Open this lesson in the learning hub

Key points

  • An empty context is filled by AnonymousAuthenticationFilter with a token holding ROLE_ANONYMOUS.
  • That token reports isAuthenticated() == true, so calling it alone never proves that anyone logged in.
  • Test !(auth instanceof AnonymousAuthenticationToken), or use SpEL isAuthenticated(), which excludes anonymous.
  • permitAll() still runs the whole chain: CSRF, headers and CORS all apply - only the authorization vote is yes.
  • On a permitAll route SecurityContextHolder holds the anonymous token, not null, so a null check passes.
  • Switching anonymous off leaves a real null in the context and turns the trap into a NullPointerException.

Example

http.authorizeHttpRequests(a -> a
        .requestMatchers("/", "/health").permitAll()
        .anyRequest().authenticated());

// inside a permitAll controller, with nobody logged in
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
auth.getName();                     // "anonymousUser"
auth.isAuthenticated();             // true  <- the trap
auth.getAuthorities();              // [ROLE_ANONYMOUS]

boolean reallyLoggedIn = !(auth instanceof AnonymousAuthenticationToken);

@PreAuthorize("isAuthenticated()")  // SpEL knows the difference: false for anonymous
public List<Order> myOrders() { ... }

On a permitAll route the caller is anonymousUser, not nobody.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Security course, and every lesson in it is listed on the Spring Security contents page.