Spring Security Fundamentals

1 minute read

Spring Security is the standard for securing Java applications. Add one dependency and everything is locked down — then you configure what to open.


Setup

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

What happens immediately:

  • All endpoints require authentication
  • A login page appears at /login
  • A default user user is created with a random password (printed in logs)
  • CSRF protection is enabled
  • Session-based authentication is active

How Spring Security Works

HTTP Request
    │
    ▼
┌────────────────────────────────────────┐
│          Security Filter Chain           │
│                                          │
│  1. CorsFilter          ← CORS headers  │
│  2. CsrfFilter          ← CSRF token    │
│  3. AuthenticationFilter ← Who are you?  │
│  4. AuthorizationFilter  ← Can you?      │
│                                          │
└────────────────────────────────────────┘
    │
    ▼
  Controller (if allowed)

Authentication vs Authorization

Concept Question Example
Authentication Who are you? Login with username/password
Authorization What can you do? Admin can delete, user can only read

SecurityFilterChain Configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())                  // disable for REST APIs
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()       // public
                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/books/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()                      // everything else
            );

        return http.build();
    }
}

Authorization Rules

Method Meaning
permitAll() No auth required
authenticated() Must be logged in
hasRole("ADMIN") Must have ADMIN role
hasAnyRole("ADMIN", "MANAGER") Any of these roles
hasAuthority("BOOK_WRITE") Specific permission

CORS Configuration

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        // ... other config
    ;
    return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("http://localhost:3000", "https://bookstore.com"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
    config.setAllowedHeaders(List.of("*"));
    config.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/**", config);
    return source;
}

CSRF

Scenario CSRF
REST API (stateless, JWT) Disable
Server-rendered pages (sessions) Keep enabled
// Disable for stateless REST APIs
.csrf(csrf -> csrf.disable())

Password Encoder

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

Never store plain-text passwords. BCrypt hashes are one-way and include a salt.


Key Takeaways

  1. Adding spring-boot-starter-security locks down everything by default.
  2. SecurityFilterChain defines which URLs are public vs protected.
  3. CSRF: disable for REST APIs, keep for server-rendered pages.
  4. CORS must be configured for frontend-backend communication.
  5. Always use BCryptPasswordEncoder — never store plain passwords.

What’s Next?

Next: Basic Authentication — in-memory users, UserDetailsService, and password encoding.

Updated: