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
SecurityFilterChainbean usingHttpSecurity - Use
permitAll()for public endpoints andauthenticated()for protected ones - Always encode passwords with
BCryptPasswordEncoder— never store plain text @PreAuthorizeadds fine-grained access control at the method level
