Spring Security - permitAll() returning 403 Forbidden

I'm configuring Spring Security in my Spring Boot application, but even though I'm using permitAll() on certain endpoints, I'm still getting 403 Forbidden responses. Here's my security configuration:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/health", "/actuator/health").permitAll()
                .anyRequest().authenticated()
            )
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            );
        
        return http.build();
    }
}

And my application.yml:

server:
  servlet:
    context-path: /api

But when I try to access /api/public/test, I still get 403 Forbidden. What am I doing wrong?

Solution

The issue is caused by a context-path configuration conflict. When you set server.servlet.context-path=/api in your properties, Spring Boot prepends /api to all your endpoints, but your security configuration is still looking for the original paths.

For example:

  • Without context-path: Your endpoint /api/public/test is accessible at http://localhost:8080/api/public/test
  • With context-path=/api: Your endpoint /api/public/test becomes accessible at http://localhost:8080/api/api/public/test

To fix this, you need to remove context-path from your application.yml:

server:
  servlet:
    # context-path: /api  # Remove or comment this out
Debugging

If you're still having issues, enable debug logging to see the actual request paths:

logging:
  level:
    org.springframework.security: DEBUG
    org.springframework.web: DEBUG
Alternative #1

I've encountered this exact issue. The problem is that the context-path is prepended to every request path, so the security configuration is not able to match against it.

If you want to keep using server.servlet.context-path, you will have to update the paths in the code

Replace the /api/public path with /public in the security configuration:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()  // This matches /api/public/** when context-path is /api
                .requestMatchers("/health", "/actuator/health").permitAll()
                .anyRequest().authenticated()
            )
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            );
        
        return http.build();
    }
}

You will also need to update your controller, replacing /api/public/test with /public/test.

You can test by calling the endpoint http://localhost:8080/api/public/test as /api is prepended automatically.

Last modified: July 4, 2025
Stay in the loop
Subscribe to our newsletter to get the latest articles delivered to your inbox