Search code examples
javaspring-bootspring-security

Spring Boot throwing 401 Unauthorized on POST request


I am working on a spring-boot web app and have configured Spring Security with JWT. My issue is that when I make a POST request in Postman, I receive a 401 Unauthorized error. However, when I make a GET request, it returns data with a 200 status. This seems to be a common issue; I've checked different threads on stackoverflow, and most suggest disabling CSRF in the security config. I've already done this, but it’s still not working.

Request "/courses/**" with permitAll(), should be accessible without authentication.

SecurityConfig.java

@Configuration 
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final CustomUserDetailsService userDetailsService;
private final JwtAuthEntryPoint jwtAuthEntryPoint;

@Bean
public AuthTokenFilter authenticationTokenFilter(){
    return new AuthTokenFilter();
}

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
    return authConfig.getAuthenticationManager();
}

@Bean
public DaoAuthenticationProvider authenticationProvider(){
    var authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(passwordEncoder());
    return authProvider;
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    http.csrf(AbstractHttpConfigurer::disable)  // Disable CSRF for stateless APIs
            .exceptionHandling(exception -> exception.authenticationEntryPoint(jwtAuthEntryPoint))
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth->
                            auth.requestMatchers("/api/auth/**").permitAll()
                            .requestMatchers("/api/roles/**").permitAll()
                            .requestMatchers("/api/users/**").permitAll()
                            .requestMatchers("/courses/**").permitAll()
                            .anyRequest().authenticated()
            );
    http.authenticationProvider(authenticationProvider());
    http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

}

Post Method

    @PostMapping("/add-course")
public ResponseEntity<?> addCourse(@RequestBody Course course){
    try{
        System.out.println("Course body: " + course);
        Course theCourse = courseService.addNewCourse(course);
        return ResponseEntity.ok(theCourse);
    }catch (Exception e){
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
    }
}

Postman API call Image

POSTMAN post request


Solution

  • I would like to go with chained configuration instead of separate configuration.

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        return http.csrf(AbstractHttpConfigurer::disable)  // Disable CSRF for stateless APIs
                .exceptionHandling(exception -> exception.authenticationEntryPoint(jwtAuthEntryPoint))
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authenticationProvider(authenticationProvider())
                .addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .authorizeHttpRequests(auth->
                        auth.requestMatchers("/api/auth/**").permitAll()
                                .requestMatchers("/api/roles/**").permitAll()
                                .requestMatchers("/api/users/**").permitAll()
                                .requestMatchers("/courses/**").permitAll()
                                .anyRequest().authenticated()
                ).build();
    }
    

    It ensures that the configuration is applied in a single fluent chain and avoids issues with multiple http.build() calls.