Spring Boot Security Basics

Spring Security protects your application by controlling who can access what. Add the dependency and your entire app is locked down immediately — every endpoint requires authentication by default.

Adding Spring Security

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

The moment you add this, Spring generates a random password at startup and prints it to the console. Every request requires the username user and that generated password.

How Spring Security Intercepts Requests

  Incoming HTTP Request
          │
          ▼
  Security Filter Chain
  ┌──────────────────────────────────────────────────┐
  │                                                  │
  │  Filter 1: Read JWT token from header            │
  │  Filter 2: Authenticate user                     │
  │  Filter 3: Check authorization (has permission?) │
  │                                                  │
  └──────────────────────────────────────────────────┘
          │
          ▼ passed? → Your Controller
          │
          ▼ failed? → 401 Unauthorized or 403 Forbidden

Configuring Security Rules

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())            ← Disable for REST APIs
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()   ← Public endpoints
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()                  ← Everything else requires login
            )
            .httpBasic(Customizer.withDefaults());   ← Use HTTP Basic auth

        return http.build();
    }
}

Authorization Rules Explained

Rule                              Meaning
──────────────────────────────    ────────────────────────────────────────
.permitAll()                      Anyone can access — no login needed
.authenticated()                  Must be logged in
.hasRole("ADMIN")                 Must be logged in AND have ADMIN role
.hasAnyRole("ADMIN","MANAGER")    Must have at least one of these roles
.denyAll()                        No one can access

In-Memory User Store (for Testing)

@Bean
public UserDetailsService userDetailsService() {
    UserDetails admin = User.builder()
        .username("admin")
        .password(passwordEncoder().encode("admin123"))
        .roles("ADMIN")
        .build();

    UserDetails user = User.builder()
        .username("alice")
        .password(passwordEncoder().encode("pass123"))
        .roles("USER")
        .build();

    return new InMemoryUserDetailsManager(admin, user);
}

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

Why BCrypt?

Plain text:     "secret123"
MD5 (weak):     5ebe2294ecd0e0f08eab7690d2a6ee69
BCrypt (strong): $2a$10$N9qo8uLOickgx2ZMRZoMye... (different every time)

BCrypt is a one-way hashing algorithm designed to be slow. Slow means it takes enormous time to crack through brute force. Never store plain-text passwords.

Database-Backed User Authentication

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepo;

    @Override
    public UserDetails loadUserByUsername(String email) {
        User user = userRepo.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));

        return org.springframework.security.core.userdetails.User
            .withUsername(user.getEmail())
            .password(user.getPassword())           ← Must be BCrypt encoded
            .roles(user.getRole())
            .build();
    }
}

Securing Methods with Annotations

Enable method-level security with @EnableMethodSecurity:

@Configuration
@EnableMethodSecurity
public class SecurityConfig { ... }
@Service
public class ReportService {

    @PreAuthorize("hasRole('ADMIN')")             ← Only ADMINs can call this
    public List<Report> getAllReports() { ... }

    @PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
    public Report getMyReport(Long userId) { ... }  ← User can only see their own
}

Security Architecture Diagram

  Request
     │
     ▼
  DelegatingFilterProxy
     │
     ▼
  FilterChainProxy
     │
     ▼ checks each filter in order
  UsernamePasswordAuthenticationFilter  ← handles login form
  BearerTokenAuthenticationFilter       ← handles JWT tokens
  ExceptionTranslationFilter            ← handles 401/403
  FilterSecurityInterceptor             ← checks permissions
     │
     ▼
  Controller (if all filters pass)

Summary

  • Adding the security starter immediately secures all endpoints
  • Configure rules in a SecurityFilterChain bean using HttpSecurity
  • Use permitAll() for public endpoints and authenticated() for protected ones
  • Always encode passwords with BCryptPasswordEncoder — never store plain text
  • @PreAuthorize adds fine-grained access control at the method level

Leave a Comment

Your email address will not be published. Required fields are marked *