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

  1. Verify the username exists and the password is correct — the message intentionally conflates both
  2. Call setHideUserNotFoundExceptions(false) on DaoAuthenticationProvider during debugging to surface the real UsernameNotFoundException
  3. Enable debug logging for org.springframework.security to see 'Failed to find user' vs password mismatch
  4. 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

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


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/827bef577ce4dfb8. Report an issue: GitHub.