I'm learning Spring Boot with Spring Security, and I'm having trouble with a @PostMapping endpoint in my AuthController. The @GetMapping endpoint works fine, but the @PostMapping endpoint is never hit. I've verified the request using Postman, and everything seems correct. Here are the details:
Security Configuration (SecurityConfig):
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
// Define your custom UserDetailsService implementation
return new CustomUserDetailsService();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(req ->
req.requestMatchers(
"/auth/**",
"/css/**.css",
"/js/**"
).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
@RestController
@RequestMapping("/auth")
public class AuthController {
@GetMapping("/test")
public String test() {
return "Testing GET in Auth Ctrl.";
}
@PostMapping("/login")
public String login(@RequestBody AuthRequest authRequest) {
try {
if(authRequest.getUsername() == null || authRequest.getPassword() == null)
throw new AuthenticationException("AuthRequest is empty");
// Assuming successful authentication, you would normally return a JWT token here
return "Login successful!";
} catch (AuthenticationException e) {
return "Login failed!";
}
}
}
@Data
@NoArgsConstructor
class AuthRequest {
private String username;
private String password;
}
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final Map<String, String> usersMap = new HashMap<>();
@Autowired
public CustomUserDetailsService() {
// Initialize the usersMap with encoded passwords
usersMap.put("ricky", "$2a$10$Dow1SE9N1XzFxXh3YDJIoO/b0Zi4DlCg8Up7X5DpIS9b/T/xkaHOO"); // password: 123
usersMap.put("martin", "$2a$10$Dow1SE9N1XzFxXh3YDJIoO/b0Zi4DlCg8Up7X5DpIS9b/T/xkaHOO"); // password: 456
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (usersMap.containsKey(username)) {
return new User(username, usersMap.get(username), new ArrayList<>()); // Empty authorities list
}
// If this is thrown, then we won't generate a JWT token.
throw new UsernameNotFoundException(username + " not found.");
}
}
Testing with Postman:
URL: http://localhost:8080/auth/login Method: POST Headers: Content-Type: application/json Body: Raw JSON Example body:
{
"username": "ricky",
"password": "123"
}
What I’ve Tried:
Problem: Despite the above steps, the @PostMapping endpoint is never hit, and no error messages are logged. The @GetMapping endpoint works fine.
Additional Information: pom.xml
Any insights or suggestions on why the @PostMapping endpoint is not being hit would be greatly appreciated!
well of course it won't reach your method, if a Spring Security Filter returns forbidden :)
you should also check WHY you are getting 403
, set logging level to debug with logging.level.org.springframework.security=DEBUG
so that you see the error.
I am willing to bet it is CSRF which correctly doesn't allow you to send "dangerous" requests (basically any other request, except GET) without a CSRF Token.
Configure you backend to store CSRF tokens in cookies
http.csrf(csrf -> csrf.csrfTokenRepository
(CookieCsrfTokenRepository.withHttpOnlyFalse()));
You should first send some kind of request (any request) in order to get a CSRF cookie (gets automatically added to the response of a call that doesn't have it yet) with the name (XSRF-TOKEN
) and then add the value of that cookie as a X-XSRF-TOKEN
header to your POST call. See here
If you don't rely on cookies for your authentication and don't employ session you can also consider turning off CSRF since you don't need CSRF protection without session / cookies
http
.csrf(AbstractHttpConfigurer::disable)
// ...