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
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.