Skip to content
C Codeloom
Java

Spring Security Basics: Authentication, Authorization, and Filters

A practical introduction to Spring Security. Understand the filter chain, configure authentication, set up authorization rules, and avoid common mistakes.

·4 min read · By Codeloom
Intermediate 8 min read

What you'll learn

  • How the Spring Security filter chain works
  • Configuring authentication providers
  • Authorization with role and method security
  • Stateless JWT vs session-based auth
  • Hardening your Spring Boot app

Prerequisites

  • Basic Java
  • Spring Boot basics
  • HTTP fundamentals

What and Why

Spring Security is the de facto authentication and authorization framework for Spring applications. It sits in front of your controllers as a chain of servlet filters, intercepting requests before they reach your code. The framework gives you defaults that are secure out of the box and extension points for the unusual cases.

The reason to use it is that rolling your own auth is dangerous. CSRF protection, password hashing, session fixation, secure cookies, and timing-safe comparison are all problems someone else has already solved correctly. You want to focus on what makes your product unique, not on rediscovering how to invalidate a session.

Mental Model

Picture every HTTP request entering a tunnel of filters. Each filter has one job: parse a bearer token, build an Authentication object, check authorization rules, log an audit entry. If any filter rejects the request, it short-circuits with a 401 or 403. If all filters pass, the request reaches your controller with a populated SecurityContext.

Two abstractions matter most. AuthenticationManager answers “who is this?” using a list of AuthenticationProviders. AccessDecisionManager (now AuthorizationManager in modern versions) answers “are they allowed?” given the authenticated principal and the request.

Hands-on Example

Here is a minimal Spring Boot 3 configuration with form login and method security.

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**", "/login").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form.loginPage("/login").permitAll())
            .logout(logout -> logout.logoutSuccessUrl("/"));
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Then a service method that requires a specific role:

@Service
public class ReportService {
    @PreAuthorize("hasRole('ANALYST')")
    public Report generate(Long id) { ... }
}
HTTP Request
  |
  v
SecurityContextPersistenceFilter
  |
  v
UsernamePasswordAuthenticationFilter -- builds Authentication
  |
  v
AuthorizationFilter ------------------ checks rules
  |
  v
ExceptionTranslationFilter ----------- 401 / 403 on failure
  |
  v
DispatcherServlet -> Controller
Request flow through the Spring Security filter chain

The filter chain is ordered. Authentication happens before authorization. Exceptions thrown from your controller bubble up through ExceptionTranslationFilter, which is what converts an AccessDeniedException into a 403 response.

Common Pitfalls

Disabling CSRF “to make Postman work” is the most common security regression. CSRF protection should stay on for session-based apps. For stateless APIs using bearer tokens, you can safely disable it because the attack model does not apply.

Storing passwords with NoOpPasswordEncoder happens in tutorials and leaks into production. Always use BCryptPasswordEncoder or Argon2PasswordEncoder with sensible work factors.

Order of requestMatchers matters. The first match wins. Putting anyRequest().authenticated() before a permitAll() rule silently blocks the public endpoints.

For JWT setups, forgetting to validate the signature, expiration, and issuer is a critical bug. Use a library like nimbus-jose-jwt or Spring’s JwtDecoder rather than parsing tokens by hand.

Practical Tips

Test your security config. Spring Security Test gives you @WithMockUser and MockMvc integration so you can assert “anonymous users get 401” and “USER role cannot reach /admin” in unit tests.

Use method security for fine-grained rules. URL-based rules are coarse; @PreAuthorize lets you express things like “users can only edit their own resources” with SpEL.

Prefer stateless JWT for APIs serving SPAs or mobile clients, session cookies for server-rendered apps. Mixing both creates confusion about where state lives.

Log authentication failures. A spike in failed logins is often the first sign of credential stuffing.

Wrap-up

Spring Security is large because the security problem is large. The trick to learning it is to internalize the filter chain mental model first, then layer on the configuration DSL. Once you understand that every request flows through ordered filters, that authentication produces a principal, and that authorization is a separate decision, the rest of the framework starts to feel coherent.

Start simple with form login and roles, then evolve to JWT and method security as your application grows.