Spring Security¶
Spring Security provides authentication and authorization for Spring applications. It uses a filter chain that intercepts every request. Key concepts: SecurityFilterChain (defines security rules), UserDetailsService (loads user data), PasswordEncoder (bcrypt), JWT-based stateless auth, method-level security (@PreAuthorize), and CORS/CSRF configuration.
Security Filter Chain¶
Every HTTP request passes through a chain of security filters: SecurityContextPersistenceFilter → CsrfFilter → UsernamePasswordAuthenticationFilter (or custom JWT filter) → ExceptionTranslationFilter → FilterSecurityInterceptor (authorization). Configure via SecurityFilterChain bean using the DSL builder pattern.
Deep Dive: Configuration (Spring Boot 3+)
Request → DelegatingFilterProxy → FilterChainProxy
→ SecurityContextPersistenceFilter
→ CsrfFilter
→ UsernamePasswordAuthenticationFilter (or JwtFilter)
→ ExceptionTranslationFilter
→ FilterSecurityInterceptor (authorization)
→ Controller
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated())
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Authentication Flow¶
Flow: request with credentials → AuthenticationFilter creates token → AuthenticationManager delegates to AuthenticationProvider → uses UserDetailsService to load user → PasswordEncoder verifies password → on success, SecurityContextHolder stores authentication. Implement UserDetailsService to load users from your database.
Deep Dive: Custom UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userRepository.findByEmail(username)
.orElseThrow(() ->
new UsernameNotFoundException("User not found: " + username));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword()) // Already encoded
.roles(user.getRoles().stream()
.map(Role::getName).toArray(String[]::new))
.build();
}
}
JWT Authentication¶
JWT flow: 1) Client sends credentials to /api/auth/login. 2) Server validates, generates JWT, returns it. 3) Client includes JWT in Authorization: Bearer <token> header. 4) Custom JwtAuthenticationFilter (extends OncePerRequestFilter) validates the token on each request and sets SecurityContext. Stateless — no server-side sessions.
Deep Dive: JWT Utility & Filter
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
return extractUsername(token).equals(userDetails.getUsername())
&& !isTokenExpired(token);
}
}
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
chain.doFilter(request, response); return;
}
String token = authHeader.substring(7);
String username = jwtUtil.extractUsername(token);
if (username != null &&
SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.isTokenValid(token, userDetails)) {
var authToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
chain.doFilter(request, response);
}
}
Method-Level Security¶
Enable with @EnableMethodSecurity. Use @PreAuthorize to restrict method access based on roles or SpEL expressions. @PreAuthorize("hasRole('ADMIN')") checks roles. @PreAuthorize("#userId == authentication.principal.id") checks ownership. @PostAuthorize checks after the method returns.
Deep Dive: SpEL Examples
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig { }
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { ... }
@PreAuthorize("#userId == authentication.principal.id")
public User getProfile(Long userId) { ... }
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public void updateUser(Long userId, UserUpdateRequest request) { ... }
@PostAuthorize("returnObject.owner == authentication.principal.username")
public Document getDocument(Long docId) { ... }
}
CORS & CSRF Configuration¶
CORS (Cross-Origin Resource Sharing) — configure via WebMvcConfigurer.addCorsMappings() or in SecurityFilterChain. Specify allowed origins, methods, headers. CSRF (Cross-Site Request Forgery) — enabled by default for form-based apps. Disable for stateless JWT APIs (no sessions = no CSRF risk). Keep for session-based apps.
Deep Dive: Configuration
// CORS via WebMvcConfigurer
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000", "https://myapp.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}
// CSRF — disable for stateless API
http.csrf(csrf -> csrf.disable());
// CSRF — enable with cookie token (for SPA + session)
http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));
Common Interview Questions¶
Common Interview Questions
- How does Spring Security work at a high level?
- What is the Security Filter Chain?
- How does authentication work in Spring Security?
- What is
UserDetailsService? - How do you implement JWT authentication?
- What is the difference between authentication and authorization?
- What is
@PreAuthorize? Give examples. - What is CSRF? When should you disable it?
- How do you configure CORS?
- What is
PasswordEncoder? Why use BCrypt? - What is
SecurityContextHolder?