🛡️ Spring Security: The Practical Guide

“Security is not a feature. It’s a mindset.”


📌 What is Spring Security?

Spring Security is a powerful framework that handles:

  • Authentication (Who are you?)

  • Authorization (What can you do?)

It provides out-of-the-box mechanisms like:

  • Username/password login

  • JWT token verification

  • Role-based access control (@PreAuthorize)

  • Session management

  • Custom filters

  • OAuth2, API key support, and more


🚀 Getting Started

1. Add Dependencies

<!-- Maven -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

🏗️ Basic Concepts

ConceptMeaning
AuthenticationVerifying identity (who the request is from)
AuthorizationGranting access (based on roles/permissions)
PrincipalThe authenticated user or system making the call
GrantedAuthorityRepresents a role or permission (e.g. ROLE_ADMIN)

🔐 Define Security Configuration

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true) // Enables @PreAuthorize, @Secured
public class SecurityConfig {
 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated())
            .httpBasic(); // or .formLogin(), or custom filters
        return http.build();
    }
}

🔑 Common Authentication Types

Form Login

  • Traditional login with username/password and session cookie
http.formLogin(); // default login page

JWT Token (Bearer Auth)

Authorization: Bearer <token>

Add a custom OncePerRequestFilter to validate and set the authentication:

SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()));

API Key Authentication

X-API-KEY: your-api-key

Hash and store the API key in DB, then match on request using a custom filter. Add role as SimpleGrantedAuthority.


🔓 Method-Level Security

Enable it:

@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)

Use it:

@PreAuthorize("hasRole('ADMIN')")
public void adminOnlyMethod() {}

👥 UserDetails & AuthenticationPrincipal

UserDetails Interface

Your User or ApiKey model can implement this:

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return List.of(new SimpleGrantedAuthority("ROLE_ADMIN"));
}

Access Authenticated User

@GetMapping("/users/me")
public ResponseEntity<User> getCurrent(@AuthenticationPrincipal User user) {
    return ResponseEntity.ok(user);
}

🧩 Custom Filters

Custom filters can be plugged into the filter chain:

http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);

Good for:

  • JWT parsing

  • API key validation

  • Request logging, etc.


📤 Exception Handling

Use Spring’s built-in mechanisms:

throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token");

Or define a global @ControllerAdvice with @ExceptionHandler.


🗃️ Summary

NeedSolution
Session-based loginhttp.formLogin()
Token-based auth (stateless)Custom JWT filter + SecurityContext
Role-based access@PreAuthorize, getAuthorities()
System/API authenticationX-API-KEY with a hashed lookup
Secure endpointsrequestMatchers("/admin/**").hasRole("ADMIN")

✅ Best Practices

  • Always hash and never store raw API keys or passwords.

  • Minimize use of permitAll() — only for login or health checks.

  • Use @PreAuthorize for service-level protection.

  • Keep filters small, single-responsibility.

  • Add expiration or revocation to API keys.


🔧 Optional Enhancements

  • Add rate-limiting (e.g., Bucket4j)

  • Enable CORS properly

  • Log authentication attempts

  • Add 2FA or OAuth2

  • Use a tool like Keycloak if auth becomes complex


💬 Final Thought

Spring Security is like a bouncer at the club:
It checks who you are, what you’re allowed to do, and then lets you in — or throws you out.
Treat it with respect and your app will be safe.