Why Spring Security Locks All Endpoints Just By Adding the Dependency
"I just added spring-boot-starter-security to my pom.xml and now ALL my endpoints return 401 Unauthorized! I didn't write any security code - why is this happening?" This is Spring Security's "secure by default" philosophy at work. Let's understand why this design choice makes sense and how to configure it.
The Surpriseβ
You add one dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Restart your server, and suddenly:
curl http://localhost:8080/api/products
# Before: 200 OK β
# After: 401 Unauthorized β
What just happened?
Spring Security Auto-Configurationβ
When you add spring-boot-starter-security, Spring Boot's auto-configuration kicks in and automatically configures security for your entire application.
What Spring Does Behind the Scenesβ
Spring Security creates something equivalent to this configuration automatically:
@Configuration
public class DefaultSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated() // β ALL requests require authentication
.and()
.httpBasic(); // β Use HTTP Basic Authentication
return http.build();
}
}
Breaking it down:
.authorizeRequests()- Start defining authorization rules.anyRequest().authenticated()- Every single request must be authenticated.httpBasic()- Use HTTP Basic Auth (username/password in headers)
Result: All endpoints now require authentication by default!
Why "Secure by Default"?β
This might seem inconvenient, but it's a brilliant security design:
Security Principle: Fail-Safe Defaultsβ
Better to start restrictive and open up, rather than start open and forget to secure things.
The Alternative (Dangerous)β
Imagine if Spring Security started with "allow everything":
// β If Spring Security did this (it doesn't!)
http
.authorizeRequests()
.anyRequest().permitAll() // Everything open by default
What could go wrong:
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/users")
public List<User> getAllUsers() {
return userRepository.findAll(); // Exposes all user data!
}
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
userRepository.deleteById(id); // Anyone can delete users!
}
@GetMapping("/admin/reports")
public Report getFinancialReport() {
return reportService.getFinancialData(); // Sensitive data exposed!
}
}
If you forget to add @PreAuthorize or security config, these endpoints would be wide open!
With "Secure by Default"β
// Spring Security's approach
http
.authorizeRequests()
.anyRequest().authenticated() // Everything locked by default
Now:
- You must explicitly open endpoints you want public
- You can't accidentally expose sensitive data
- Forgotten endpoints remain protected
Security mindset: Whitelist (allow specific things) > Blacklist (block specific things)
The Complete Auto-Configurationβ
When you add spring-boot-starter-security, Spring Boot auto-configures:
1. Security Filter Chainβ
All HTTP requests pass through security filters:
Client Request
β
SecurityFilterChain (intercepts)
β
Check: Is user authenticated?
β
YES β Allow request β Controller
NO β Return 401 Unauthorized
2. Default User Credentialsβ
Spring generates a random password on startup:
Console output:
Using generated security password: 8e557245-73e2-4286-969a-ff57fe326d53
Default credentials:
- Username:
user - Password:
8e557245-73e2-4286-969a-ff57fe326d53(random, changes every restart)
3. HTTP Basic Authenticationβ
Authentication header format:
Authorization: Basic dXNlcjpwYXNzd29yZA==
(Base64 encoding of user:password)
Testing Protected Endpointsβ
Before Spring Securityβ
curl http://localhost:8080/api/products
# 200 OK
# [{"id":1,"name":"Laptop",...}]
After Spring Securityβ
# Without credentials - 401 Unauthorized
curl http://localhost:8080/api/products
# 401 Unauthorized
# With credentials - 200 OK
curl -u user:8e557245-73e2-4286-969a-ff57fe326d53 \
http://localhost:8080/api/products
# 200 OK
# [{"id":1,"name":"Laptop",...}]
In Postmanβ
- Select Authorization tab
- Type: Basic Auth
- Username:
user - Password:
8e557245-73e2-4286-969a-ff57fe326d53(from console) - Send request β 200 OK β
Customizing Security Configurationβ
Option 1: Set Fixed Credentialsβ
application.properties:
spring.security.user.name=admin
spring.security.user.password=admin123
Now you can use admin:admin123 instead of the random password.
Note: Only for development! Never use hardcoded passwords in production.
Option 2: Allow Public Endpointsβ
Create a SecurityConfig class to customize which endpoints are public:
package com.ecommerce.app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/test").permitAll() // β
Public endpoint
.antMatchers("/api/products").permitAll() // β
Public endpoint
.antMatchers("/api/admin/**").authenticated() // π Requires auth
.anyRequest().authenticated() // π Everything else requires auth
.and()
.httpBasic();
return http.build();
}
}
Now:
/api/testβ Public (no auth needed)/api/productsβ Public (no auth needed)/api/admin/reportsβ Protected (auth required)- Everything else β Protected (auth required)
Option 3: Disable Security Completely (Not Recommended)β
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll() // β οΈ Allow everything (DANGEROUS!)
.and()
.csrf().disable();
return http.build();
}
}
Warning: Only use this for local development/testing. Never in production!
Common Configurationsβ
Public API + Protected Admin Endpointsβ
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public/**").permitAll() // Public API
.antMatchers("/api/admin/**").hasRole("ADMIN") // Admin only
.anyRequest().authenticated() // Everything else
.and()
.httpBasic();
return http.build();
}
Public Read, Protected Writeβ
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/**").permitAll() // Public reads
.antMatchers(HttpMethod.POST, "/api/**").authenticated() // Protected writes
.antMatchers(HttpMethod.PUT, "/api/**").authenticated() // Protected updates
.antMatchers(HttpMethod.DELETE, "/api/**").authenticated() // Protected deletes
.anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}
Disable CSRF for REST APIsβ
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable() // Disable CSRF for stateless REST APIs
.authorizeRequests()
.antMatchers("/api/test").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}
Note: CSRF protection is important for browser-based apps but can be disabled for stateless REST APIs.
What Happens Step-by-Stepβ
1. Add Dependencyβ
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. Spring Boot Auto-Configuration Detects Itβ
Application startup
β
Spring Boot scans classpath
β
Finds spring-boot-starter-security
β
Triggers SecurityAutoConfiguration
β
Creates default SecurityFilterChain bean
β
Applies default security rules
3. Security Filters Activateβ
HTTP Request
β
Servlet Filter Chain
β
Spring Security Filters (intercept)
β
Authentication Check
β
Authorization Check
β
Controller (if authorized)
4. Default Configuration Appliedβ
// Automatically configured by Spring
- All endpoints require authentication
- HTTP Basic Auth enabled
- Random password generated
- Username: "user"
- Password: printed to console
Real-World Exampleβ
Before Spring Securityβ
@RestController
@RequestMapping("/api")
public class ProductController {
@GetMapping("/products")
public List<Product> getAllProducts() {
return productService.findAll();
// Accessible to everyone β
}
@PostMapping("/products")
public Product createProduct(@RequestBody Product product) {
return productService.save(product);
// Accessible to everyone β οΈ (dangerous!)
}
@DeleteMapping("/products/{id}")
public void deleteProduct(@PathVariable Long id) {
productService.delete(id);
// Accessible to everyone β οΈ (dangerous!)
}
}
Problem: Anyone can create and delete products!
After Spring Security (Default)β
@RestController
@RequestMapping("/api")
public class ProductController {
@GetMapping("/products")
public List<Product> getAllProducts() {
return productService.findAll();
// Requires authentication π
}
@PostMapping("/products")
public Product createProduct(@RequestBody Product product) {
return productService.save(product);
// Requires authentication π
}
@DeleteMapping("/products/{id}")
public void deleteProduct(@PathVariable Long id) {
productService.delete(id);
// Requires authentication π
}
}
Result: All endpoints protected by default!
After Custom Configurationβ
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/products").permitAll() // β
Public
.antMatchers(HttpMethod.POST, "/api/products").authenticated() // π Protected
.antMatchers(HttpMethod.DELETE, "/api/products/**").authenticated() // π Protected
.anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}
}
Result:
GET /api/productsβ Public (anyone can view)POST /api/productsβ Protected (only authenticated users)DELETE /api/products/{id}β Protected (only authenticated users)
Debugging Security Issuesβ
Check Security Configurationβ
Enable security logging in application.properties:
logging.level.org.springframework.security=DEBUG
Console output:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
LogoutFilter
BasicAuthenticationFilter β Handles HTTP Basic Auth
...
]
Test Authenticationβ
# Test without auth - should fail
curl -v http://localhost:8080/api/products
# Test with auth - should succeed
curl -v -u admin:admin123 http://localhost:8080/api/products
Best Practicesβ
β DOβ
-
Keep default "secure by default" behavior
.anyRequest().authenticated() // Good starting point -
Explicitly allow public endpoints
.antMatchers("/api/public/**").permitAll() -
Use strong passwords in production
// Use environment variables, not hardcoded values
@Value("${ADMIN_PASSWORD}")
private String adminPassword; -
Disable CSRF for REST APIs
.csrf().disable() // Only for stateless APIs
β DON'Tβ
-
Don't disable security completely
// β Dangerous!
.authorizeRequests().anyRequest().permitAll() -
Don't hardcode passwords
// β Bad practice
spring.security.user.password=admin123 -
Don't forget to configure public endpoints
// β Forces auth on login page too!
.anyRequest().authenticated() -
Don't expose sensitive endpoints
// β Admin endpoints should not be public
.antMatchers("/api/admin/**").permitAll()
Summaryβ
The Questionβ
"Why do my endpoints require authentication? I just added the dependency!"
The Answerβ
Spring Security's "secure by default" philosophy:
- Auto-configuration triggers when
spring-boot-starter-securityis detected - Default behavior: All endpoints require authentication (
.anyRequest().authenticated()) - Why: Better to start restrictive and open up than start open and forget to secure
- Result: 401 Unauthorized for all endpoints until you configure security
Key Takeawaysβ
| Aspect | Behavior |
|---|---|
| Default state | All endpoints protected |
| Authentication | HTTP Basic Auth enabled |
| Credentials | Username: user, Password: random (printed to console) |
| Philosophy | Fail-safe defaults (secure by default) |
| Configuration | Customize via SecurityConfig class |
Configuration Optionsβ
// Option 1: Allow specific endpoints
.antMatchers("/api/test").permitAll()
// Option 2: Set fixed credentials
spring.security.user.name=admin
spring.security.user.password=admin123
// Option 3: Custom SecurityFilterChain
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) { ... }
Bottom line: Spring Security protects you from accidentally exposing sensitive endpoints. It forces you to think about security from day one, which is exactly what good security design should do! π‘οΈ
