Understand the Spring Security filter chain, authentication flow, and how to configure HTTP security.
Published March 1, 2025
Spring Security is the standard security framework for Spring Boot applications. It handles authentication (who are you?) and authorization (what are you allowed to do?) through a chain of servlet filters.
Every HTTP request passes through a chain of filters before reaching your controller:
HTTP Request
↓
SecurityContextPersistenceFilter (load/save SecurityContext)
↓
UsernamePasswordAuthenticationFilter (form login)
↓
BearerTokenAuthenticationFilter (JWT/OAuth2)
↓
ExceptionTranslationFilter (handle 401/403)
↓
AuthorizationFilter (check permissions)
↓
Your Controller
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // disable for REST APIs
.sessionManagement(sm ->
sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**").permitAll() // public endpoints
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.anyRequest().authenticated() // everything else needs auth
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public AuthenticationManager authManager(
AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Spring Security loads user details through UserDetailsService:
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.map(user -> User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRoles().toArray(new String[0]))
.accountExpired(!user.isActive())
.build())
.orElseThrow(() ->
new UsernameNotFoundException("User not found: " + email));
}
}
// Get the current authenticated user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
// In a controller — inject directly
@GetMapping("/me")
public UserDto getMe(@AuthenticationPrincipal UserDetails user) {
return userService.findByEmail(user.getUsername());
}
@Configuration
@EnableMethodSecurity // enables @PreAuthorize, @PostAuthorize
public class MethodSecurityConfig {}
@Service
public class AdminService {
@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() { ... }
@PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
public User getUser(String userId) { ... }
@PostAuthorize("returnObject.ownerId == authentication.principal.id")
public Document getDocument(String docId) { ... }
}
STATELESS session management means no HttpSession is created — every request must carry credentials (JWT).@PreAuthorize uses Spring Expression Language (SpEL) — you can access the authentication object and method arguments.