spring-projects/spring-security · error · BadCredentialsException
Bad credentials
Error message
Bad credentials
What it means
AbstractUserDetailsAuthenticationProvider.authenticate() catches UsernameNotFoundException from retrieveUser() and, when hideUserNotFoundExceptions is true (the default), rethrows it as BadCredentialsException 'Bad credentials'. This deliberately hides whether the username exists to prevent user-enumeration attacks.
Source
Thrown at core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java:154
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
String username = determineUsername(authentication);
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException ex) {
this.logger.debug(LogMessage.format("Failed to find user '%s'", username));
String message = this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials");
if (!this.hideUserNotFoundExceptions) {
throw ex;
}
throw new BadCredentialsException(message, ex);
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
performPreCheck(user, (UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException ex) {
if (!cacheWasUsed) {
throw ex;
}
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
performPreCheck(user, (UsernamePasswordAuthenticationToken) authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {View on GitHub (pinned to 96852e8860)
Solutions
- Verify the username exists and the password is correct — the message intentionally conflates both
- Call setHideUserNotFoundExceptions(false) on DaoAuthenticationProvider during debugging to surface the real UsernameNotFoundException
- Enable debug logging for org.springframework.security to see 'Failed to find user' vs password mismatch
- Check encoding/matching: PasswordEncoder must match how passwords were stored (e.g. BCryptPasswordEncoder for bcrypt hashes)
Example fix
// before (debugging) DaoAuthenticationProvider p = new DaoAuthenticationProvider(uds); // after DaoAuthenticationProvider p = new DaoAuthenticationProvider(uds); p.setHideUserNotFoundExceptions(false); // surfaces UsernameNotFoundException during troubleshooting only
Defensive patterns
Strategy: try-catch
Validate before calling
try { uds.loadUserByUsername(username); } catch (UsernameNotFoundException e) { log.debug("User not found"); } // combined with a PasswordEncoder check in tests
PasswordEncoder pe = PasswordEncoderFactories.createDelegatingPasswordEncoder();
assert pe.matches(rawPassword, storedPassword); Type guard
boolean credentialsPlausible(String username, String raw, UserDetails u, PasswordEncoder pe) { return username != null && !username.isBlank() && pe.matches(raw, u.getPassword()); } Try / catch
try { authMgr.authenticate(token); } catch (BadCredentialsException e) { return ResponseEntity.status(401).body("Invalid username or password"); } Prevention
- Ensure the PasswordEncoder matches how passwords are stored
- Keep hideUserNotFoundExceptions=true in production to avoid user enumeration
- Check username spelling/case and account existence before escalating to support
- Enable debug logging when troubleshooting, not in production
When it happens
Trigger: DaoAuthenticationProvider.authenticate() with a username not found by the UserDetailsService (mapped to Bad credentials), or BadCredentialsException thrown directly by an incorrect password comparison in additionalAuthenticationChecks.
Common situations: Typo in username OR wrong password; DaoAuthenticationProvider with default hideUserNotFoundExceptions=true; custom UserDetailsService throwing UsernameNotFoundException that gets masked; users expecting 'user not found' but seeing 'Bad credentials'.
Related errors
- RunAsImplAuthenticationProvider.incorrectKey
- CasAuthenticationProvider.incorrectKey
- CasAuthenticationProvider.noServiceTicket
- Authentication.getCredentials() cannot be null
- Bad credentials
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/827bef577ce4dfb8.
Report an issue: GitHub.