Back to Blog
spring-bootsecurityspring-securityowasp

Spring Security Misconfigurations That Put Your App at Risk

CSRF disabled, CORS wildcard, permitAll() on everything — these Spring Security mistakes are common, hard to spot, and exploitable. Learn how to detect and fix them.

J

JOptimize Team

April 15, 2026· 9 min read

Spring Security is powerful, but misconfiguring it is dangerously easy. A single wrong line in your SecurityConfig can expose your entire application to attacks — and the worst part is that everything still compiles and runs fine.

In this article, we cover the most common Spring Security misconfigurations, why they're dangerous, and how to fix them properly.

1. Disabling CSRF Protection

This is the single most common mistake in Spring Boot applications:

http.csrf().disable(); // ❌ Never do this in a stateful app

Why it's dangerous: CSRF (Cross-Site Request Forgery) attacks trick authenticated users into executing actions they didn't intend to. When you disable CSRF, any malicious website can send requests to your API on behalf of a logged-in user — transferring money, changing passwords, deleting accounts.

When it's actually OK: Stateless REST APIs that use token-based authentication (JWT, API keys) don't need CSRF protection because they don't rely on cookies. But you need to be intentional about it.

The fix:

// For stateless JWT APIs — explicitly document the reason http.csrf(csrf -> csrf.disable()); // OK — stateless, JWT auth, no session cookies // For stateful apps — keep CSRF enabled (it's the default) // Or configure it properly: http.csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) );

2. CORS Wildcard — Allowing All Origins

config.addAllowedOrigin("*"); // ❌ Allows any website to call your API

Why it's dangerous: With a wildcard CORS policy, any JavaScript running on any domain can make authenticated requests to your API. Combined with credentials, this becomes a serious attack vector.

The fix:

@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of( "https://yourdomain.com", "https://www.yourdomain.com", "http://localhost:3000" // dev only )); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; }

Never use allowedOrigins("*") with allowCredentials(true) — Spring will throw an exception for this combination. But using "*" without credentials is still a bad idea for authenticated APIs.

3. permitAll() on Everything

http.authorizeHttpRequests(auth -> auth .anyRequest().permitAll() // ❌ Every endpoint is public );

Why it's dangerous: This silently makes all your endpoints — including admin routes, user data, payment endpoints — publicly accessible. No authentication required. It's often added as a "quick fix" during development and forgotten in production.

The fix:

http.authorizeHttpRequests(auth -> auth // Public endpoints .requestMatchers("/api/public/**").permitAll() .requestMatchers("/api/webhooks/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll() // Admin only .requestMatchers("/api/admin/**").hasRole("ADMIN") // Everything else requires authentication .anyRequest().authenticated() );

Be explicit. Default to .anyRequest().authenticated() and whitelist only what needs to be public.

4. Missing @PreAuthorize on Write Endpoints

Even with .anyRequest().authenticated(), you can still expose write operations to users who shouldn't have access:

@RestController @RequestMapping("/api/users") public class UserController { @PostMapping // ❌ Any authenticated user can create users public User createUser(@RequestBody User user) { ... } @DeleteMapping("/{id}") // ❌ Any user can delete any account public void deleteUser(@PathVariable Long id) { ... } @PutMapping("/admin/reset-password") // ❌ Should be admin only public void resetPassword(...) { ... } }

The fix:

@RestController @RequestMapping("/api/users") public class UserController { @PostMapping @PreAuthorize("hasRole('ADMIN')") public User createUser(@RequestBody User user) { ... } @DeleteMapping("/{id}") @PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id") public void deleteUser(@PathVariable Long id) { ... } @PutMapping("/admin/reset-password") @PreAuthorize("hasRole('ADMIN')") public void resetPassword(...) { ... } }

Enable method security in your config:

@Configuration @EnableMethodSecurity // Required for @PreAuthorize to work public class SecurityConfig { ... }

5. Security Headers Not Configured

By default, Spring Security adds some security headers, but not all. Missing headers leave your app vulnerable to clickjacking, XSS, and MIME sniffing attacks.

// Check what you're actually sending: // X-Frame-Options: DENY ✓ (Spring adds this) // X-Content-Type-Options: nosniff ✓ (Spring adds this) // Strict-Transport-Security: ✗ (not added by default in dev) // Content-Security-Policy: ✗ (not added by default)

The fix:

http.headers(headers -> headers .frameOptions(frame -> frame.deny()) .contentTypeOptions(Customizer.withDefaults()) .httpStrictTransportSecurity(hsts -> hsts .maxAgeInSeconds(31536000) .includeSubDomains(true) ) .contentSecurityPolicy(csp -> csp .policyDirectives("default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'") ) );

6. Exposing Stack Traces in Error Responses

// ❌ Default behavior — stack traces in production responses { "timestamp": "2026-04-15T10:00:00.000+00:00", "status": 500, "error": "Internal Server Error", "trace": "java.lang.NullPointerException\n\tat com.example.UserService.getUser(UserService.java:45)\n\t...", "path": "/api/users/123" }

Why it's dangerous: Stack traces reveal your internal package structure, class names, library versions, and exact line numbers — giving attackers a detailed map of your application.

The fix:

// application.properties server.error.include-stacktrace=never server.error.include-message=never server.error.include-binding-errors=never

And add a global exception handler:

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<Map<String, String>> handleException(Exception e) { log.error("Unhandled exception", e); // Log internally return ResponseEntity.status(500) .body(Map.of("error", "An unexpected error occurred")); // Generic message to client } @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<Map<String, String>> handleNotFound(ResourceNotFoundException e) { return ResponseEntity.status(404) .body(Map.of("error", "Resource not found")); } }

How JOptimize Detects These Issues

JOptimize automatically scans your Spring Security configuration and flags these misconfigurations:

joptimize analyze . # → [CRITICAL] CSRF disabled — stateful app without CSRF protection # SecurityConfig.java:24 # # → [CRITICAL] CORS wildcard detected — all origins permitted # SecurityConfig.java:51 # # → [CRITICAL] anyRequest().permitAll() — all endpoints are public # SecurityConfig.java:35 # # → [WARNING] Write endpoint 'createUser()' has no access control annotation # UserController.java:18

Each issue comes with an explanation and a concrete fix — no manual security audit required.

Quick Checklist

  • CSRF — only disable if truly stateless (JWT/API keys, no session cookies)
  • CORS — list specific allowed origins, never use "*" in production
  • Authorization — default to .anyRequest().authenticated(), whitelist public routes explicitly
  • Method security — use @PreAuthorize on sensitive endpoints
  • Error responses — never expose stack traces or internal details to clients
  • Security headers — configure HSTS, CSP, X-Frame-Options explicitly

If you want to go deeper on Spring Security, this course covers everything from the basics to OAuth2, JWT, and production-grade configurations:

Spring Security — Zero to Master — one of the most complete Spring Security courses on Udemy, covering authentication, authorization, OAuth2, JWT, and more with hands-on projects.

Scan Your Spring Security Config Now

Run JOptimize to find all security misconfigurations in your Spring Boot project:

npm install -g @joptimize/cli joptimize auth jp_live_your_key joptimize analyze .

Or upload your ZIP at joptimize.io.

Want to go deeper?

Master Spring Boot, security, and Java performance with hands-on courses.

Detect N+1 queries in your project

JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.