A:
- Authentication: Verifies identity (who you are) - login process
- Authorization: Determines permissions (what you can access) - access control
- Example: Login with username/password (authentication) → Access admin panel (authorization)
A: Ordered sequence of filters that process every request:
Request → SecurityContextPersistenceFilter → UsernamePasswordAuthenticationFilter →
FilterSecurityInterceptor → Controller
Each filter can authenticate, authorize, or modify the request.
A:
| Aspect | Basic Auth | JWT | API Keys |
|---|---|---|---|
| State | Stateless | Stateless | Stateless |
| Storage | None | Client-side | Client-side |
| Expiration | No | Yes | No (manual) |
| Scalability | Good | Excellent | Good |
| Security | Medium | High | Medium |
| Use Case | Internal APIs | Web/Mobile apps | Service-to-service |
A: JWT has 3 parts: Header.Payload.Signature
// Generation
String token = Jwts.builder()
.subject("admin")
.claim("role", "ADMIN")
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(secretKey)
.compact();
// Validation
Claims claims = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();A:
- Token theft: Store securely, use HTTPS
- Secret key compromise: Rotate keys regularly
- Token size: Larger than session IDs
- Cannot revoke: Use short expiration + refresh tokens
- Replay attacks: Include timestamp, use HTTPS
A:
@Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
String token = extractToken(request);
if (isValidToken(token)) {
Authentication auth = createAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
}A: OAuth2 is an authorization framework that enables applications to obtain limited access to user accounts. It works by delegating user authentication to the service that hosts the user account.
Flow:
- Client redirects user to authorization server
- User authenticates with authorization server
- Authorization server redirects back with authorization code
- Client exchanges code for access token
- Client uses access token to access protected resources
A:
// Add dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
// Configure in SecurityConfig
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/login?error=true")
)
// Handle OAuth2 user
@GetMapping("/api/oauth2/user")
public ResponseEntity<?> getUser(@AuthenticationPrincipal OAuth2User principal) {
return ResponseEntity.ok(Map.of(
"name", principal.getAttribute("name"),
"email", principal.getAttribute("email")
));
}A:
- OAuth2: Authorization framework/protocol
- JWT: Token format that can be used with OAuth2
- OAuth2: Defines how to get tokens
- JWT: Defines token structure and validation
- Can be combined: OAuth2 can use JWT as access token format
A:
- @PreAuthorize: Checks before method execution
- @PostAuthorize: Checks after method execution (can access return value)
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { }
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) { }A:
- hasRole('ADMIN'): Checks for "ROLE_ADMIN" authority (adds ROLE_ prefix)
- hasAuthority('ROLE_ADMIN'): Checks exact authority string
- hasAuthority('READ'): Checks for "READ" permission
A:
@EnableMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {
@PreAuthorize("hasRole('ADMIN') and authentication.name == 'admin'")
public String sensitiveOperation() {
return "Sensitive data";
}
@PreAuthorize("#userId == authentication.principal.id")
public User getUserProfile(Long userId) {
return userService.findById(userId);
}
}A:
- Prevent abuse: Stop malicious attacks
- Resource protection: Avoid server overload
- Fair usage: Ensure equal access for all users
- Cost control: Limit expensive operations
A:
- Fixed Window: 10 requests per minute (resets at minute boundary)
- Sliding Window: 10 requests in any 60-second period
- Token Bucket: Refill tokens at fixed rate
- Leaky Bucket: Process requests at steady rate
A:
@Service
public class RateLimitService {
private final ConcurrentHashMap<String, UserRequestInfo> requestCounts = new ConcurrentHashMap<>();
public boolean isAllowed(String clientId) {
long currentTime = System.currentTimeMillis();
requestCounts.compute(clientId, (key, userInfo) -> {
if (userInfo == null || isWindowExpired(userInfo, currentTime)) {
return new UserRequestInfo(currentTime, new AtomicInteger(1));
} else {
userInfo.requestCount.incrementAndGet();
return userInfo;
}
});
return requestCounts.get(clientId).requestCount.get() <= maxRequests;
}
}A:
if (!rateLimitService.isAllowed(clientId)) {
response.setStatus(429); // Too Many Requests
response.setHeader("Retry-After", "60");
response.setHeader("X-RateLimit-Limit", "10");
response.setHeader("X-RateLimit-Remaining", "0");
response.setHeader("X-RateLimit-Reset", String.valueOf(resetTime));
return; // Stop processing
}A:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}A:
// In custom filter
if (authHeader != null && authHeader.startsWith("Bearer ")) {
// JWT Authentication
handleJwtAuth(request);
} else if (apiKey != null) {
// API Key Authentication
handleApiKeyAuth(request);
}
// Basic Auth handled by Spring Security's built-in filterA:
- Enables method-level security annotations
prePostEnabled = true: Enables @PreAuthorize/@PostAuthorizesecuredEnabled = true: Enables @Securedjsr250Enabled = true: Enables @RolesAllowed
A:
- Salt included: Prevents rainbow table attacks
- Adaptive: Can increase rounds as hardware improves
- Slow by design: Makes brute force attacks impractical
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // 12 rounds
}A:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities("ROLE_" + user.getRole().name())
.build();
}
}A:
- Stateless (JWT): No server-side session, token contains all info
- Stateful (Session): Server stores user state, client has session ID
- Scalability: Stateless scales better (no session storage)
- Security: Stateful easier to revoke, stateless harder to invalidate
A:
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // For JWT
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // For OAuth2
.maximumSessions(1) // Limit concurrent sessions
.maxSessionsPreventsLogin(false) // Allow new login, expire old
)A:
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write(
"{\"error\":\"Unauthorized\",\"message\":\"" +
authException.getMessage() + "\"}"
);
}
}A:
- BadCredentialsException: Wrong username/password
- UsernameNotFoundException: User doesn't exist
- AccountExpiredException: Account expired
- CredentialsExpiredException: Password expired
- DisabledException: Account disabled
- LockedException: Account locked
- AccessDeniedException: Insufficient permissions
A:
@SpringBootTest
@AutoConfigureTestMvc
class SecurityTest {
@Test
@WithMockUser(roles = "ADMIN")
void adminEndpoint_WithAdminRole_Success() throws Exception {
mockMvc.perform(get("/api/admin/data"))
.andExpect(status().isOk());
}
@Test
void protectedEndpoint_WithoutAuth_Unauthorized() throws Exception {
mockMvc.perform(get("/api/protected"))
.andExpect(status().isUnauthorized());
}
}A:
@Test
void jwtEndpoint_WithValidToken_Success() throws Exception {
String token = jwtUtil.generateToken("admin", "ADMIN");
mockMvc.perform(get("/api/jwt/data")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk());
}A:
- Filter Chain: Each request goes through security filters
- Password Hashing: BCrypt is intentionally slow (good for security)
- Session Storage: Memory usage for stateful authentication
- Database Queries: UserDetailsService queries on each login
- Optimization: Use caching, connection pooling, stateless auth
A:
// 1. Cache UserDetails
@Cacheable("users")
public UserDetails loadUserByUsername(String username) { }
// 2. Use stateless authentication
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 3. Optimize BCrypt rounds
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10); // Lower rounds for better performance
}
// 4. Skip security for static resources
.requestMatchers("/css/**", "/js/**").permitAll()A:
- CSRF: Cross-Site Request Forgery attack
- Protection: Spring Security generates CSRF tokens
- Stateless APIs: Disable CSRF for REST APIs
- Configuration:
.csrf().disable()for APIs, enabled for web forms
A:
public class CustomSecurityFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
// Custom security logic
String customHeader = request.getHeader("X-Custom-Auth");
if (isValid(customHeader)) {
// Set authentication
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
}
// Add to security config
.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class)A:
- Use HTTPS: Encrypt data in transit
- Strong passwords: Enforce password policies
- Regular updates: Keep dependencies updated
- Principle of least privilege: Minimal required permissions
- Input validation: Validate all user inputs
- Secure headers: Use security headers (HSTS, CSP)
- Audit logging: Log security events
- Rate limiting: Prevent brute force attacks
- Token expiration: Use short-lived tokens
- Secure storage: Encrypt sensitive data at rest
This guide covers all major Spring Security concepts implemented in the demo:
- Authentication methods: Basic, JWT, API Key, OAuth2
- Authorization: Role-based, method-level security
- Rate limiting: Request throttling and protection
- Security configuration: Filter chains, password encoding
- Error handling: Custom exception handling
- Testing: Security test strategies
- Performance: Optimization techniques
- Best practices: Security recommendations
Use this guide to prepare for Spring Security interviews and understand the complete security implementation in the demo application. { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(user.getRole().name())
.build();
}
}
---
## Error Handling & Security
### Q22: How do you handle authentication failures?
**A:**
```java
@ControllerAdvice
public class SecurityExceptionHandler {
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<?> handleAuthenticationException(AuthenticationException ex) {
return ResponseEntity.status(401)
.body(Map.of("error", "Authentication failed", "message", ex.getMessage()));
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<?> handleAccessDeniedException(AccessDeniedException ex) {
return ResponseEntity.status(403)
.body(Map.of("error", "Access denied", "message", "Insufficient privileges"));
}
}
A:
http.headers(headers -> headers
.frameOptions().deny()
.contentTypeOptions().and()
.httpStrictTransportSecurity(hstsConfig -> hstsConfig
.maxAgeInSeconds(31536000)
.includeSubdomains(true))
.and()
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)));A: Client-side approach:
// Remove token from storage
localStorage.removeItem('jwt-token');
sessionStorage.removeItem('jwt-token');Server-side approach (Token Blacklist):
@Service
public class TokenBlacklistService {
private final Set<String> blacklistedTokens = ConcurrentHashMap.newKeySet();
public void blacklistToken(String token) {
blacklistedTokens.add(token);
}
public boolean isBlacklisted(String token) {
return blacklistedTokens.contains(token);
}
}A:
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(@RequestBody RefreshTokenRequest request) {
if (refreshTokenService.isValidRefreshToken(request.getRefreshToken())) {
String newAccessToken = jwtUtil.generateAccessToken(user);
String newRefreshToken = refreshTokenService.generateRefreshToken(user);
return ResponseEntity.ok(new TokenResponse(newAccessToken, newRefreshToken));
}
return ResponseEntity.status(401).body("Invalid refresh token");
}A:
- Service-to-service: Use API keys or mutual TLS
- Gateway pattern: Authenticate at API gateway
- JWT propagation: Pass JWT between services
- Service mesh: Use Istio/Linkerd for security
A:
| Aspect | Stateful (Sessions) | Stateless (JWT) |
|---|---|---|
| Storage | Server-side | Client-side |
| Scalability | Limited | Excellent |
| Memory | Uses server memory | No server memory |
| Logout | Easy (destroy session) | Complex (blacklist) |
| Security | Session hijacking risk | Token theft risk |
A:
@SpringBootTest
@AutoConfigureTestDatabase
class SecurityTest {
@Test
@WithMockUser(roles = "ADMIN")
void testAdminEndpoint() {
mockMvc.perform(get("/api/admin/data"))
.andExpect(status().isOk());
}
@Test
void testJwtAuthentication() {
String token = jwtUtil.generateToken("admin", "ADMIN");
mockMvc.perform(get("/api/jwt/profile")
.header("Authorization", "Bearer " + token))
.andExpected(status().isOk());
}
@Test
void testRateLimit() {
// Make 10 requests
for (int i = 0; i < 10; i++) {
mockMvc.perform(get("/api/rate-limit/public"))
.andExpect(status().isOk());
}
// 11th request should be rate limited
mockMvc.perform(get("/api/rate-limit/public"))
.andExpect(status().isTooManyRequests());
}
}A:
- Cache user details: Avoid repeated database queries
- Efficient token validation: Use fast algorithms
- Connection pooling: For database connections
- Rate limiting: Prevent resource exhaustion
@Cacheable("users")
public UserDetails loadUserByUsername(String username) {
return userRepository.findByUsername(username);
}A:
@EventListener
public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
log.info("User {} authenticated successfully", event.getAuthentication().getName());
}
@EventListener
public void handleAuthenticationFailure(AbstractAuthenticationFailureEvent event) {
log.warn("Authentication failed for user {}: {}",
event.getAuthentication().getName(), event.getException().getMessage());
}- Use HTTPS everywhere
- Implement proper authentication
- Apply principle of least privilege
- Validate all inputs
- Use parameterized queries
- Implement rate limiting
- Log security events
- Handle errors securely
- Keep dependencies updated
- Use security headers
- Use strong secret keys (256+ bits)
- Set appropriate expiration times
- Implement refresh token rotation
- Store tokens securely on client
- Validate tokens on every request
- Use HTTPS for token transmission
- Choose appropriate limits
- Implement different limits for different endpoints
- Provide clear error messages
- Use distributed rate limiting for microservices
- Monitor rate limit metrics
- Implement graceful degradation
- Storing passwords in plain text
- Using weak JWT secrets
- Not validating JWT expiration
- Exposing sensitive data in logs
- Not implementing rate limiting
- Using default configurations
- Not handling security exceptions properly
- Mixing authentication methods incorrectly
- Always use HTTPS in production
- Implement defense in depth
- Follow OWASP guidelines
- Regular security audits
- Keep frameworks updated
- Use security scanning tools
- Implement proper logging and monitoring
A: Follow layered architecture:
com.example.security/
├── config/ # Security configurations
├── controller/ # REST endpoints
├── service/ # Business logic
├── repository/ # Data access
├── model/ # Entities
├── filter/ # Custom security filters
└── util/ # Utilities (JWT, etc.)
A:
@SpringBootApplication
@ComponentScan(basePackages = "com.example.security")
public class SecurityApplication {
// Ensures all security components are discovered
}A:
@Configuration
public class MultiAuthConfig {
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.and()
.build();
}
}A:
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/public/**").permitAll()
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.requestMatchers("/api/v2/**").hasRole("USER")
.anyRequest().authenticated())A:
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}A:
@Service
public class GracefulSecurityService {
public boolean authenticateWithFallback(String token) {
try {
return primaryAuthService.validate(token);
} catch (Exception e) {
log.warn("Primary auth failed, using fallback");
return fallbackAuthService.validate(token);
}
}
}A:
# application-dev.properties
logging.level.org.springframework.security=DEBUG
rate-limit.enabled=false
# application-prod.properties
logging.level.org.springframework.security=WARN
rate-limit.enabled=true
rate-limit.max-requests=100A:
@Component
public class SecurityHealthIndicator implements HealthIndicator {
@Override
public Health health() {
try {
// Check JWT service
jwtUtil.generateToken("health-check", "USER");
// Check rate limiting
rateLimitService.isAllowed("health-check");
return Health.up()
.withDetail("jwt", "operational")
.withDetail("rate-limiting", "operational")
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}A:
@Component
public class SecurityMetrics {
private final Counter authSuccessCounter = Counter.builder("auth.success")
.description("Successful authentications")
.register(Metrics.globalRegistry);
private final Counter authFailureCounter = Counter.builder("auth.failure")
.description("Failed authentications")
.register(Metrics.globalRegistry);
@EventListener
public void onAuthSuccess(AuthenticationSuccessEvent event) {
authSuccessCounter.increment();
}
@EventListener
public void onAuthFailure(AbstractAuthenticationFailureEvent event) {
authFailureCounter.increment();
}
}A:
- Enable debug logging:
logging.level.org.springframework.security=DEBUG- Check filter chain order:
@Bean
public FilterRegistrationBean<CustomFilter> customFilterRegistration() {
FilterRegistrationBean<CustomFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new CustomFilter());
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}- Debug authentication:
@GetMapping("/debug/auth")
public ResponseEntity<?> debugAuth(Authentication auth, HttpServletRequest request) {
return ResponseEntity.ok(Map.of(
"authenticated", auth != null,
"principal", auth != null ? auth.getPrincipal() : "none",
"authorities", auth != null ? auth.getAuthorities() : "none",
"headers", Collections.list(request.getHeaderNames())
));
}This comprehensive guide covers all major Spring Boot Security concepts with practical examples and real-world scenarios you'll encounter in interviews.