1. java
  2. /spring
  3. /spring-security

Master Spring Security and Application Security

Spring Security

Spring Security is a powerful and highly customizable authentication and access-control framework for Java applications. It provides comprehensive security features including authentication, authorization, protection against attacks, and integration with various security standards.

Core Concepts

Spring Security operates on several key concepts:

Authentication: Verifying who the user is (identity verification) Authorization: Determining what an authenticated user can do (access control) Principal: The currently logged-in user Authority/Role: Permissions granted to a principal Security Context: Holds authentication details for the current thread

// Basic Security Configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            )
            .csrf(csrf -> csrf.disable()) // Disable for APIs
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            );
        
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Authentication

Basic Authentication Setup

// User Details Service Implementation
@Service
public class CustomUserDetailsService implements UserDetailsService {
    
    private final UserRepository userRepository;
    
    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
        
        return User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .authorities(getAuthorities(user.getRoles()))
            .accountNonExpired(true)
            .accountNonLocked(!user.isLocked())
            .credentialsNonExpired(true)
            .enabled(user.isActive())
            .build();
    }
    
    private Collection<? extends GrantedAuthority> getAuthorities(Set<Role> roles) {
        return roles.stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
            .collect(Collectors.toList());
    }
}

// Authentication Provider
@Configuration
public class AuthenticationConfig {
    
    private final CustomUserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;
    
    public AuthenticationConfig(CustomUserDetailsService userDetailsService,
                              PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }
    
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) 
            throws Exception {
        return config.getAuthenticationManager();
    }
    
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }
}

JWT Authentication

// JWT Utility Class
@Component
public class JwtUtils {
    
    private final String jwtSecret = "mySecretKey";
    private final int jwtExpirationMs = 86400000; // 24 hours
    
    public String generateJwtToken(Authentication authentication) {
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
        
        return Jwts.builder()
            .setSubject(userPrincipal.getUsername())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
    
    public String getUsernameFromJwtToken(String token) {
        return Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody()
            .getSubject();
    }
    
    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            logger.error("Invalid JWT token: {}", e.getMessage());
        }
        return false;
    }
}

// JWT Authentication Filter
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    private final JwtUtils jwtUtils;
    private final UserDetailsService userDetailsService;
    
    public JwtAuthenticationFilter(JwtUtils jwtUtils, UserDetailsService userDetailsService) {
        this.jwtUtils = jwtUtils;
        this.userDetailsService = userDetailsService;
    }
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                  FilterChain filterChain) throws ServletException, IOException {
        
        String jwt = parseJwt(request);
        
        if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
            String username = jwtUtils.getUsernameFromJwtToken(jwt);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            
            UsernamePasswordAuthenticationToken authentication = 
                new UsernamePasswordAuthenticationToken(userDetails, null, 
                                                      userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        
        filterChain.doFilter(request, response);
    }
    
    private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader("Authorization");
        
        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
            return headerAuth.substring(7);
        }
        
        return null;
    }
}

// Authentication Controller
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    private final AuthenticationManager authenticationManager;
    private final JwtUtils jwtUtils;
    private final UserService userService;
    
    public AuthController(AuthenticationManager authenticationManager,
                         JwtUtils jwtUtils, UserService userService) {
        this.authenticationManager = authenticationManager;
        this.jwtUtils = jwtUtils;
        this.userService = userService;
    }
    
    @PostMapping("/login")
    public ResponseEntity<JwtResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
        
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                loginRequest.getUsername(), 
                loginRequest.getPassword())
        );
        
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.generateJwtToken(authentication);
        
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
        List<String> roles = userPrincipal.getAuthorities().stream()
            .map(item -> item.getAuthority())
            .collect(Collectors.toList());
        
        return ResponseEntity.ok(new JwtResponse(jwt, userPrincipal.getId(), 
                                               userPrincipal.getUsername(), roles));
    }
    
    @PostMapping("/register")
    public ResponseEntity<MessageResponse> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
        
        if (userService.existsByUsername(signUpRequest.getUsername())) {
            return ResponseEntity.badRequest()
                .body(new MessageResponse("Error: Username is already taken!"));
        }
        
        if (userService.existsByEmail(signUpRequest.getEmail())) {
            return ResponseEntity.badRequest()
                .body(new MessageResponse("Error: Email is already in use!"));
        }
        
        User user = userService.createUser(signUpRequest);
        
        return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
    }
}

Authorization

Method-Level Security

// Enable Method Security
@Configuration
@EnableMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {
    // Configuration for method-level security
}

// Service with Method Security
@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    @PreAuthorize("hasRole('ADMIN') or #username == authentication.name")
    public User getUserByUsername(String username) {
        return userRepository.findByUsername(username)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
    }
    
    @PreAuthorize("hasRole('USER')")
    @PostAuthorize("returnObject.username == authentication.name or hasRole('ADMIN')")
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
    }
    
    @PreAuthorize("@userService.isOwnerOrAdmin(#id, authentication.name)")
    public void updateUser(Long id, UpdateUserRequest request) {
        User user = getUserById(id);
        // Update logic
        userRepository.save(user);
    }
    
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
    
    public boolean isOwnerOrAdmin(Long userId, String currentUsername) {
        User user = userRepository.findById(userId).orElse(null);
        if (user == null) return false;
        
        return user.getUsername().equals(currentUsername) || 
               SecurityContextHolder.getContext().getAuthentication()
                   .getAuthorities().stream()
                   .anyMatch(auth -> auth.getAuthority().equals("ROLE_ADMIN"));
    }
}

Custom Security Expressions

// Custom Security Expression Root
@Component("customSecurity")
public class CustomSecurityExpressionRoot {
    
    private final UserRepository userRepository;
    
    public CustomSecurityExpressionRoot(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public boolean isOwner(Long resourceId, String resourceType) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String currentUsername = auth.getName();
        
        switch (resourceType.toLowerCase()) {
            case "user":
                return userRepository.findById(resourceId)
                    .map(user -> user.getUsername().equals(currentUsername))
                    .orElse(false);
            // Add more resource types as needed
            default:
                return false;
        }
    }
    
    public boolean hasPermission(String permission) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return auth.getAuthorities().stream()
            .anyMatch(authority -> authority.getAuthority().equals(permission));
    }
    
    public boolean isInSameOrganization(Long userId) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String currentUsername = auth.getName();
        
        User currentUser = userRepository.findByUsername(currentUsername).orElse(null);
        User targetUser = userRepository.findById(userId).orElse(null);
        
        if (currentUser == null || targetUser == null) return false;
        
        return currentUser.getOrganizationId().equals(targetUser.getOrganizationId());
    }
}

// Usage in Service
@Service
public class DocumentService {
    
    @PreAuthorize("@customSecurity.isOwner(#documentId, 'document') or hasRole('ADMIN')")
    public Document getDocument(Long documentId) {
        // Implementation
        return null;
    }
    
    @PreAuthorize("@customSecurity.hasPermission('DOCUMENT_CREATE')")
    public Document createDocument(CreateDocumentRequest request) {
        // Implementation
        return null;
    }
}

Web Security Configuration

Advanced Security Configuration

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final CustomUserDetailsService userDetailsService;
    
    public WebSecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
                           JwtAuthenticationFilter jwtAuthenticationFilter,
                           CustomUserDetailsService userDetailsService) {
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        this.userDetailsService = userDetailsService;
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .csrf(csrf -> csrf.disable())
            .exceptionHandling(ex -> ex.authenticationEntryPoint(jwtAuthenticationEntryPoint))
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(authz -> authz
                // Public endpoints
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/actuator/health").permitAll()
                
                // Admin endpoints
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
                
                // User endpoints
                .requestMatchers(HttpMethod.GET, "/api/users/me").hasRole("USER")
                .requestMatchers(HttpMethod.PUT, "/api/users/me").hasRole("USER")
                
                // API endpoints
                .requestMatchers("/api/**").authenticated()
                
                // All other requests
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
    
    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

// JWT Authentication Entry Point
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
    
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                        AuthenticationException authException) throws IOException, ServletException {
        
        logger.error("Unauthorized error: {}", authException.getMessage());
        
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        
        final ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(response.getOutputStream(), 
            Map.of("error", "Unauthorized", "message", authException.getMessage()));
    }
}

OAuth2 and Social Login

// OAuth2 Configuration
@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig {
    
    private final CustomOAuth2UserService customOAuth2UserService;
    private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
    private final OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/", "/login", "/oauth2/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(customOAuth2UserService)
                )
                .successHandler(oAuth2AuthenticationSuccessHandler)
                .failureHandler(oAuth2AuthenticationFailureHandler)
            );
        
        return http.build();
    }
}

// Custom OAuth2 User Service
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
    
    private final UserRepository userRepository;
    
    public CustomOAuth2UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oauth2User = super.loadUser(userRequest);
        
        try {
            return processOAuth2User(userRequest, oauth2User);
        } catch (Exception ex) {
            throw new OAuth2AuthenticationException("Error processing OAuth2 user");
        }
    }
    
    private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oauth2User) {
        OAuth2UserInfo oauth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(
            userRequest.getClientRegistration().getRegistrationId(), 
            oauth2User.getAttributes()
        );
        
        if (oauth2UserInfo.getEmail() == null || oauth2UserInfo.getEmail().isEmpty()) {
            throw new OAuth2AuthenticationException("Email not found from OAuth2 provider");
        }
        
        Optional<User> userOptional = userRepository.findByEmail(oauth2UserInfo.getEmail());
        User user;
        
        if (userOptional.isPresent()) {
            user = userOptional.get();
            if (!user.getProvider().equals(AuthProvider.valueOf(
                    userRequest.getClientRegistration().getRegistrationId().toUpperCase()))) {
                throw new OAuth2AuthenticationException("User already exists with different provider");
            }
            user = updateExistingUser(user, oauth2UserInfo);
        } else {
            user = registerNewUser(userRequest, oauth2UserInfo);
        }
        
        return UserPrincipal.create(user, oauth2User.getAttributes());
    }
    
    private User registerNewUser(OAuth2UserRequest userRequest, OAuth2UserInfo oauth2UserInfo) {
        User user = new User();
        user.setProvider(AuthProvider.valueOf(
            userRequest.getClientRegistration().getRegistrationId().toUpperCase()));
        user.setProviderId(oauth2UserInfo.getId());
        user.setName(oauth2UserInfo.getName());
        user.setEmail(oauth2UserInfo.getEmail());
        user.setImageUrl(oauth2UserInfo.getImageUrl());
        
        return userRepository.save(user);
    }
    
    private User updateExistingUser(User existingUser, OAuth2UserInfo oauth2UserInfo) {
        existingUser.setName(oauth2UserInfo.getName());
        existingUser.setImageUrl(oauth2UserInfo.getImageUrl());
        
        return userRepository.save(existingUser);
    }
}

Security Testing

// Security Test Configuration
@TestConfiguration
public class TestSecurityConfig {
    
    @Bean
    @Primary
    public UserDetailsService testUserDetailsService() {
        UserDetails user = User.builder()
            .username("testuser")
            .password("password")
            .authorities("ROLE_USER")
            .build();
        
        UserDetails admin = User.builder()
            .username("testadmin")
            .password("password")
            .authorities("ROLE_ADMIN")
            .build();
        
        return new InMemoryUserDetailsManager(user, admin);
    }
}

// Controller Security Tests
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerSecurityTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void shouldAllowPublicAccess() throws Exception {
        mockMvc.perform(get("/api/public/info"))
            .andExpect(status().isOk());
    }
    
    @Test
    void shouldRequireAuthenticationForProtectedEndpoints() throws Exception {
        mockMvc.perform(get("/api/users"))
            .andExpect(status().isUnauthorized());
    }
    
    @Test
    @WithMockUser(roles = "USER")
    void shouldAllowUserAccessWithUserRole() throws Exception {
        mockMvc.perform(get("/api/users/me"))
            .andExpect(status().isOk());
    }
    
    @Test
    @WithMockUser(roles = "USER")
    void shouldDenyAdminAccessWithUserRole() throws Exception {
        mockMvc.perform(get("/api/admin/users"))
            .andExpected(status().isForbidden());
    }
    
    @Test
    @WithMockUser(roles = "ADMIN")
    void shouldAllowAdminAccess() throws Exception {
        mockMvc.perform(get("/api/admin/users"))
            .andExpect(status().isOk());
    }
    
    @Test
    void shouldValidateJwtToken() throws Exception {
        String validJwt = generateValidJwtToken();
        
        mockMvc.perform(get("/api/users/me")
                .header("Authorization", "Bearer " + validJwt))
            .andExpect(status().isOk());
    }
    
    @Test
    void shouldRejectInvalidJwtToken() throws Exception {
        String invalidJwt = "invalid.jwt.token";
        
        mockMvc.perform(get("/api/users/me")
                .header("Authorization", "Bearer " + invalidJwt))
            .andExpected(status().isUnauthorized());
    }
    
    private String generateValidJwtToken() {
        // Implementation to generate valid JWT for testing
        return "valid.jwt.token";
    }
}

// Method Security Tests
@SpringBootTest
@Import(TestSecurityConfig.class)
class UserServiceSecurityTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    @WithMockUser(roles = "ADMIN")
    void shouldAllowAdminToGetAllUsers() {
        assertDoesNotThrow(() -> userService.getAllUsers());
    }
    
    @Test
    @WithMockUser(roles = "USER")
    void shouldDenyUserToGetAllUsers() {
        assertThrows(AccessDeniedException.class, () -> userService.getAllUsers());
    }
    
    @Test
    @WithMockUser(username = "testuser", roles = "USER")
    void shouldAllowUserToGetOwnProfile() {
        assertDoesNotThrow(() -> userService.getUserByUsername("testuser"));
    }
    
    @Test
    @WithMockUser(username = "testuser", roles = "USER")
    void shouldDenyUserToGetOtherUserProfile() {
        assertThrows(AccessDeniedException.class, 
            () -> userService.getUserByUsername("otheruser"));
    }
}

Best Practices

// Security Best Practices Configuration
@Configuration
public class SecurityBestPracticesConfig {
    
    // 1. Password Policy
    @Bean
    public PasswordEncoder strongPasswordEncoder() {
        return new BCryptPasswordEncoder(12); // Higher strength
    }
    
    // 2. Account Lockout
    @Component
    public class AccountLockoutService {
        
        private final Map<String, AttemptInfo> attemptMap = new ConcurrentHashMap<>();
        private final int maxAttempts = 5;
        private final long lockoutDurationMs = 15 * 60 * 1000; // 15 minutes
        
        public void recordFailedAttempt(String username) {
            AttemptInfo info = attemptMap.computeIfAbsent(username, 
                k -> new AttemptInfo());
            info.incrementAttempts();
        }
        
        public boolean isAccountLocked(String username) {
            AttemptInfo info = attemptMap.get(username);
            if (info == null) return false;
            
            if (info.getAttempts() >= maxAttempts) {
                if (System.currentTimeMillis() - info.getLastAttemptTime() > lockoutDurationMs) {
                    attemptMap.remove(username);
                    return false;
                }
                return true;
            }
            return false;
        }
        
        public void clearAttempts(String username) {
            attemptMap.remove(username);
        }
        
        private static class AttemptInfo {
            private int attempts = 0;
            private long lastAttemptTime = System.currentTimeMillis();
            
            void incrementAttempts() {
                attempts++;
                lastAttemptTime = System.currentTimeMillis();
            }
            
            int getAttempts() { return attempts; }
            long getLastAttemptTime() { return lastAttemptTime; }
        }
    }
    
    // 3. Security Headers
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .frameOptions().deny()
                .contentTypeOptions().and()
                .xssProtection().and()
                .referrerPolicy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
                .httpStrictTransportSecurity(hstsConfig -> hstsConfig
                    .maxAgeInSeconds(31536000)
                    .includeSubdomains(true)
                )
            );
        
        return http.build();
    }
    
    // 4. CSRF Protection for web apps
    @Bean
    public CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }
}

// 5. Input Validation
@RestController
@RequestMapping("/api/users")
@Validated
public class SecureUserController {
    
    @PostMapping
    public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest request) {
        // Validation annotations in DTO
        return ResponseEntity.ok(userService.createUser(request));
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable @Min(1) Long id) {
        // Path variable validation
        return ResponseEntity.ok(userService.getUserById(id));
    }
}

// Request DTO with validation
public class CreateUserRequest {
    
    @NotBlank(message = "Username is required")
    @Size(min = 3, max = 20, message = "Username must be 3-20 characters")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username can only contain letters, numbers, and underscores")
    private String username;
    
    @NotBlank(message = "Email is required")
    @Email(message = "Invalid email format")
    private String email;
    
    @NotBlank(message = "Password is required")
    @Size(min = 8, message = "Password must be at least 8 characters")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]", 
             message = "Password must contain uppercase, lowercase, digit and special character")
    private String password;
    
    // Getters and setters
}

Summary

Spring Security provides comprehensive security features:

Key Features:

  • Authentication: Multiple authentication mechanisms
  • Authorization: Role-based and method-level access control
  • Protection: CSRF, XSS, and other attack protections
  • Integration: OAuth2, LDAP, JWT support
  • Customization: Highly configurable and extensible

Core Components:

  • SecurityFilterChain: Main security configuration
  • UserDetailsService: User authentication data source
  • AuthenticationManager: Coordinates authentication
  • SecurityContext: Holds current security information

Best Practices:

  • Strong Password Policies: Use BCrypt with high rounds
  • Account Lockout: Prevent brute force attacks
  • Input Validation: Validate all user inputs
  • Security Headers: Enable protective HTTP headers
  • Method Security: Use @PreAuthorize/@PostAuthorize
  • Testing: Comprehensive security testing

Common Patterns:

  • JWT Authentication: Stateless API authentication
  • Role-Based Access: RBAC with roles and authorities
  • OAuth2 Integration: Social login capabilities
  • Custom Security: Tailored security expressions

Spring Security is essential for building secure Java applications with enterprise-grade security features.