Spring Security

Comprehensive authentication and authorization framework

🔐 What is Spring Security?

Spring Security provides comprehensive security services for Java applications. It handles authentication, authorization, protection against attacks, and integrates seamlessly with Spring applications for robust security implementation.


// Enable security with simple annotation
@EnableWebSecurity
@Configuration
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .build();
    }
}
                                    

Security Features

🔑

Authentication

Verify user identity with multiple methods

@EnableWebSecurity
public class SecurityConfig
🛡️

Authorization

Control access to resources and methods

@PreAuthorize("hasRole('ADMIN')")
public void adminMethod()
🔒

CSRF Protection

Prevent Cross-Site Request Forgery attacks

.csrf(csrf -> csrf.disable())
🌐

Session Management

Control user sessions and concurrent access

.sessionManagement(session -> 
    session.maximumSessions(1))

🔹 Basic Security Configuration

Configure Spring Security for your application:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/", "/home", "/register").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .permitAll()
            )
            .build();
    }
}

Security Rules:

Public: /, /home, /register

Admin only: /admin/**

User/Admin: /user/**

All others: Authenticated users

🔹 User Details Service

Implement custom user authentication:

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true)
    private String username;
    
    private String password;
    
    private String email;
    
    @Enumerated(EnumType.STRING)
    private Role role;
    
    private boolean enabled = true;
    
    // Constructors, getters, setters
}

public enum Role {
    USER, ADMIN, MODERATOR
}

@Service
public class CustomUserDetailsService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
        
        return org.springframework.security.core.userdetails.User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .authorities("ROLE_" + user.getRole().name())
            .accountExpired(false)
            .accountLocked(false)
            .credentialsExpired(false)
            .disabled(!user.isEnabled())
            .build();
    }
}

🔹 Method-Level Security

Secure individual methods with annotations:

@Service
public class UserService {
    
    // Only users with ADMIN role can access
    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    // Users can only access their own data
    @PreAuthorize("hasRole('ADMIN') or #username == authentication.name")
    public User getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    
    // Check after method execution
    @PostAuthorize("returnObject.username == authentication.name or hasRole('ADMIN')")
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    // Secure with custom expression
    @PreAuthorize("@userService.canAccessUser(authentication.name, #userId)")
    public void updateUser(Long userId, User user) {
        // Update user logic
    }
    
    public boolean canAccessUser(String currentUsername, Long userId) {
        User currentUser = userRepository.findByUsername(currentUsername);
        return currentUser.getId().equals(userId) || 
               currentUser.getRole() == Role.ADMIN;
    }
}

🔹 JWT Authentication

Implement JWT-based authentication for APIs:

@Component
public class JwtUtil {
    
    private String secret = "mySecretKey";
    private int jwtExpiration = 86400; // 24 hours
    
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }
    
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
            .setClaims(claims)
            .setSubject(subject)
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration * 1000))
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
    }
    
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private CustomUserDetailsService userDetailsService;
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        try {
            authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(), 
                    loginRequest.getPassword())
            );
        } catch (BadCredentialsException e) {
            throw new BadCredentialsException("Invalid credentials", e);
        }
        
        final UserDetails userDetails = userDetailsService
            .loadUserByUsername(loginRequest.getUsername());
        final String jwt = jwtUtil.generateToken(userDetails);
        
        return ResponseEntity.ok(new JwtResponse(jwt));
    }
}

🔹 Security in Controllers

Access security information in your controllers:

@RestController
@RequestMapping("/api/secure")
public class SecureController {
    
    // Get current authenticated user
    @GetMapping("/profile")
    public ResponseEntity<User> getCurrentUser(Authentication authentication) {
        String username = authentication.getName();
        User user = userService.findByUsername(username);
        return ResponseEntity.ok(user);
    }
    
    // Using SecurityContextHolder
    @GetMapping("/info")
    public ResponseEntity<String> getSecurityInfo() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = auth.getName();
        Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
        
        return ResponseEntity.ok("User: " + username + ", Roles: " + authorities);
    }
    
    // Using @AuthenticationPrincipal
    @GetMapping("/dashboard")
    public ResponseEntity<String> getDashboard(
            @AuthenticationPrincipal UserDetails userDetails) {
        return ResponseEntity.ok("Welcome " + userDetails.getUsername());
    }
}

Response Example:

GET /api/secure/info

"User: john_doe, Roles: [ROLE_USER]"

🧠 Test Your Knowledge

Which annotation is used to enable method-level security?