spring-projects/spring-security · error · BadCredentialsException

Bad credentials

Error message

Bad credentials

What it means

LdapAuthenticationProvider.doAuthentication() catches UsernameNotFoundException from the authenticator and, when hideUserNotFoundExceptions is true (the default), masks it as BadCredentialsException('Bad credentials') to avoid revealing which usernames exist. When hideUserNotFoundExceptions is false the original UsernameNotFoundException is rethrown.

Source

Thrown at ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticationProvider.java:184

	public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
		this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
	}

	@Override
	protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken authentication) {
		try {
			return getAuthenticator().authenticate(authentication);
		}
		catch (PasswordPolicyException ex) {
			// The only reason a ppolicy exception can occur during a bind is that the
			// account is locked.
			throw new LockedException(
					this.messages.getMessage(ex.getStatus().getErrorCode(), ex.getStatus().getDefaultMessage()));
		}
		catch (UsernameNotFoundException ex) {
			if (this.hideUserNotFoundExceptions) {
				throw new BadCredentialsException(
						this.messages.getMessage("LdapAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			throw ex;
		}
		catch (NamingException ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

	@Override
	protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username,
			String password) {
		return getAuthoritiesPopulator().getGrantedAuthorities(userData, username);
	}

}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Verify userSearchBase and the search filter (e.g. (uid={0}) vs (sAMAccountName={0})) match the directory schema and actually locate the user (test with ldapsearch).
  2. Check the search uses the correct base DN and that the context source's manager credentials allow reading the user subtree.
  3. Remember 'Bad credentials' here can mean 'user not found' — don't debug only the password path.
  4. For debugging only, set hideUserNotFoundExceptions(false) temporarily to surface the real UsernameNotFoundException.
  5. Confirm users are synced/present in the directory the app points to (not a staging vs prod mismatch).

Example fix

// before
provider.setHideUserNotFoundExceptions(false); // leaks user existence in prod

// after
// prod: keep masking (default true)
provider.setHideUserNotFoundExceptions(true);
// fix lookup instead:
LdapUserSearch search = new FilterBasedLdapUserSearch("ou=people", "(sAMAccountName={0})", contextSource);
provider.setUserSearch(search);
Defensive patterns

Strategy: try-catch

Validate before calling

// verify user resolves before authenticating
LdapTemplate ldap = new LdapTemplate(contextSource);
boolean exists = !ldap.search("ou=people", "(sAMAccountName={0})",
        new String[] { username }, new AbstractContextMapper<Object>() {
            protected Object doMapFromContext(DirContextOperations c) { return new Object(); }
        }).isEmpty();
if (!exists) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid username or password");

Try / catch

try {
    return authManager.authenticate(token);
} catch (BadCredentialsException e) {
    // can mean wrong password OR unknown user (hidden) — do not distinguish to clients
    logger.debug("ldap auth failed for {}", token.getName());
    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid username or password");
} catch (UsernameNotFoundException e) {
    // only reachable when hideUserNotFoundExceptions=false
    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid username or password");
}

Prevention

When it happens

Trigger: The configured UserSearch/LdapUserSearch finds no directory entry matching the submitted username (or the bind DN lookup fails) and doAuthentication translates the resulting UsernameNotFoundException into this BadCredentialsException unless user-not-found exceptions are not hidden.

Common situations: Typo'd username, wrong userSearchBase or filter so existing users are never found, users in an OU not covered by the search, casing/base DN mismatch after a directory migration, or testing account-enumeration protection.

Understand the failure class

Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.

Related errors


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