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.
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 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.
Related articles
- Java Spring Data JPA Tutorial: Repositories, Queries, and Transactions
Learn Spring Data JPA from the ground up. Understand repositories, derived queries, JPQL, pagination, and transaction boundaries in real Java apps.
- Java Spring Boot Dependency Injection Explained
How Spring Boot wires your beans: component scanning, constructor injection, qualifiers, profiles, and the testing strategies that follow from each choice.
- Java Spring MVC vs Spring WebFlux
Compare Spring's two web stacks: thread-per-request Spring MVC versus reactive non-blocking WebFlux, and how to choose between them.
- Django Django Permissions and Authorization
Move beyond is_authenticated. Learn how to model groups, object-level permissions, and DRF permission classes cleanly.