Ever pushed a Spring Boot app to production, only to get that gut-wrenching feeling you might have left a back door open? Maybe you spent days hunting a subtle misconfiguration, or worse—got a call about a data leak. Security in Spring Boot can feel deceptively simple at first, but the devil’s in the details. The smallest oversight can leave your application exposed or cost you countless hours debugging weird authentication issues.
1. Relying on Default Security Configurations
Spring Boot makes it easy to get started—sometimes too easy. By default, Spring Security applies a basic form of security, but those defaults won’t fit real-world needs. Many developers assume out-of-the-box settings are “secure enough” for anything beyond a pet project. They’re not.
Why Defaults Fall Short
- Default HTTP Basic Auth is enabled for all endpoints.
- CSRF protection might break your API clients, leading you to disable it entirely rather than configure it.
- All endpoints are protected, which is fine until you add a public health endpoint and wonder why it’s 401ing.
Example: Custom Security Configuration
Here’s how you can get a tight, explicit security setup for a Spring Boot API:
// src/main/java/com/example/security/SecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@org.springframework.context.annotation.Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// Only /api/** endpoints require authentication
.authorizeHttpRequests(auth -> auth
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
)
// Use form login for browser, but you could use HTTP Basic for APIs
.formLogin()
.and()
.csrf().disable(); // Only disable CSRF if you know why!
return http.build();
}
}
Key points:
- Only
/api/**is secured, everything else is public. - CSRF is disabled here for brevity—never do this in a browser-based app without understanding the risks.
- Replace
.formLogin()with.httpBasic()if you’re building a pure API.
Takeaway: Never trust the defaults. Explicitly declare what’s public, what’s protected, and why.
2. Storing Secrets in Plaintext (application.properties)
Raise your hand if you’ve ever put a password or API key in application.properties. No shame—we’ve all been there. But storing secrets in config files leads to accidental leaks (think: git push) and makes rotating credentials a pain.
Smarter Ways to Manage Secrets
- Use environment variables for secrets.
- Integrate with secret management tools (like HashiCorp Vault or AWS Secrets Manager) for production.
Example: Loading Secrets from Environment Variables
# application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
Now, you can set these environment variables outside your codebase:
export DB_USERNAME=realuser
export DB_PASSWORD=supersecret
Key points:
- No secrets in source control.
- Credentials are injected at runtime.
Pro tip: For local development, use a .env file and a tool like direnv or your IDE’s run configuration.
3. Misconfigured CORS (Cross-Origin Resource Sharing)
CORS can be a headache, especially if you’re building a frontend-backend split (React + Spring Boot, for example). Many developers, out of frustration, just allow everything:
// DO NOT DO THIS IN PRODUCTION
http.cors().and().csrf().disable();
This is a security risk. You might unintentionally expose sensitive endpoints to the entire internet.
Proper CORS Configuration
Allow only trusted domains and methods. Here’s a practical example:
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@org.springframework.context.annotation.Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://your-frontend.example.com"); // Only allow your frontend
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return new CorsFilter(source);
}
}
Key points:
- Only allows your deployed frontend to access
/api/**endpoints. - Wildcarding origins (
*) is dangerous if credentials are involved.
4. Forgetting to Update Dependencies
Spring Boot’s flexibility means you can mix and match libraries, but that also means you’re responsible for keeping them up-to-date. Vulnerable dependencies are one of the most common attack vectors, according to the Stack Overflow 2024 survey.
How to Stay Up-to-Date
- Use the Spring Boot Dependency Management Plugin if you’re on Gradle or the parent POM for Maven.
- Run
mvn versions:display-dependency-updatesor./gradlew dependencyUpdatesregularly. - Subscribe to Spring Security advisories.
Trade-off: Updating dependencies can sometimes break your code. Use a staging environment and automated tests to catch regressions early.
5. Overlooking Method-Level Security
Securing endpoints in your configuration is great, but what if someone calls a business method from elsewhere in your code? Method-level security annotations like @PreAuthorize and @Secured help you lock things down.
Example: Method-Level Security
First, enable global method security:
// src/main/java/com/example/security/MethodSecurityConfig.java
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
// No additional code needed unless customizing
}
Now, protect your service methods:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class AdminService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(String username) {
// Only admins can delete users
// business logic here
}
}
Key points:
- Even if a controller exposes a method, only users with the
ADMINrole can executedeleteUser. - Method-level security guards against both web and internal misuse.
Trade-off: Method-level annotations add a bit of overhead and can clutter your service layer, but they’re your safety net against privilege escalation.
Common Mistakes
Disabling Security for Convenience
It’s tempting to comment out security code to “get things working,” but this is a recipe for disaster. Always use profiles (like@Profile("dev")) to separate dev and prod configs.Assuming HTTPS Is Handled Elsewhere
Don’t assume your cloud provider or load balancer enforces HTTPS. Always add server-side HTTPS enforcement in Spring Boot (e.g., redirect all HTTP to HTTPS) for an extra layer of defense.Neglecting to Log Security Events
If you don’t log failed logins, suspicious access, or config errors, you’ll be blind to attacks in progress. Integrate with Spring Security’s event system and your logging framework.
Key Takeaways
- Never trust default security settings—customize your security configuration for your app’s needs.
- Keep secrets out of your codebase and use environment variables or a secrets manager.
- Configure CORS as restrictively as possible; avoid using wildcards in production.
- Stay on top of dependency updates to prevent vulnerabilities from creeping in.
- Use method-level security to protect business logic from improper access.
Securing a Spring Boot app isn’t a one-and-done job. It’s a mindset and a habit. Invest a little extra time up front—your future self and your users will thank you.
If you found this helpful, check out more programming tutorials on our blog. We cover Python, JavaScript, Java, Data Science, and more.
Top comments (0)