Spring Security 403 Forbidden Fix — JWT, CORS, and Filter Chain Debugging Guide
A 403 in Spring Security means the request reached the security layer but authorization failed. The fix depends on whether the cause is JWT parsing, role mapping, CORS, CSRF, or filter order.
Start with the Filter Chain
JWT filters should run before UsernamePasswordAuthenticationFilter, CORS should be configured before authorization decisions, and stateless APIs should usually disable CSRF unless browser sessions are involved.
@BeanSecurityFilterChain apiSecurity(HttpSecurity http) throws Exception { return http .cors(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .build();}CORS, CSRF, and Role Prefix Issues
CORS failures often appear before your controller runs. Permit OPTIONS requests and explicitly allow the frontend origin. Role bugs usually come from using hasRole('ADMIN') while storing authorities as ADMIN instead of ROLE_ADMIN, or using hasAuthority when the code expects hasRole.
@BeanCorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://www.prabhat.online")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("Authorization", "Content-Type")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source;}Debugging Checklist
Check that the Authorization header starts with Bearer, the token subject maps to a real user, authorities are loaded correctly, the endpoint matcher is not too broad, and logs show the JWT filter setting SecurityContextHolder authentication.
JWT expired or signed with the wrong key
Missing ROLE_ prefix for hasRole checks
OPTIONS preflight blocked by authentication
CSRF enabled on stateless REST APIs
Work with Prabhat
Build production-grade backend systems, AI workflows, cloud automation, and high-signal engineering products with a developer who ships from architecture to deployment.
Work with PrabhatContact