spring-projects/spring-security · error · BadCredentialsException

Bad credentials

Error message

Bad credentials

What it means

Spring Security LDAP's PasswordComparisonAuthenticator throws BadCredentialsException("Bad credentials") in authenticate() when the user was found via DN/search but the password comparison failed. Either the password attribute value did not match via the configured PasswordEncoder, or the LDAP bind-based comparison was rejected. It deliberately uses the generic message to avoid revealing whether the username exists.

Source

Thrown at ldap/src/main/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticator.java:125

		if (user == null) {
			throw UsernameNotFoundException.fromUsername(username);
		}
		if (logger.isTraceEnabled()) {
			logger.trace(LogMessage.format("Comparing password attribute '%s' for user '%s'",
					this.passwordAttributeName, user.getDn()));
		}
		if (this.usePasswordAttrCompare && isPasswordAttrCompare(user, password)) {
			logger.debug(LogMessage.format("Locally matched password attribute '%s' for user '%s'",
					this.passwordAttributeName, user.getDn()));
			return user;
		}
		Assert.notNull(password, "LDAP password cannot be null");
		if (isLdapPasswordCompare(user, ldapTemplate, password)) {
			logger.debug(LogMessage.format("LDAP-matched password attribute '%s' for user '%s'",
					this.passwordAttributeName, user.getDn()));
			return user;
		}
		throw new BadCredentialsException(
				this.messages.getMessage("PasswordComparisonAuthenticator.badCredentials", "Bad credentials"));
	}

	private boolean isPasswordAttrCompare(DirContextOperations user, @Nullable String password) {
		String passwordAttrValue = getPassword(user);
		return this.passwordEncoder.matches(password, passwordAttrValue);
	}

	private @Nullable String getPassword(DirContextOperations user) {
		Object passwordAttrValue = user.getObjectAttribute(this.passwordAttributeName);
		if (passwordAttrValue == null) {
			return null;
		}
		if (passwordAttrValue instanceof byte[]) {
			return new String((byte[]) passwordAttrValue);
		}
		return String.valueOf(passwordAttrValue);
	}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Verify the user is supplying the correct password (test an ldapwhoami/bind directly with the same DN and password).
  2. Check that the configured PasswordEncoder matches the hash format stored in the password attribute (e.g. LdapShaPasswordEncoder/SSHA vs BCrypt).
  3. Ensure the bind credentials used by the ContextSource have read access to the password attribute if using attribute comparison instead of bind comparison.
  4. Confirm searchFilter/base configuration returns the correct user entry and DN.

Example fix

// before: encoder does not match LDAP storage
this.encoder = new BCryptPasswordEncoder();
// after: use the encoder matching the LDAP password hash scheme, or compare via bind
authenticator.setPasswordCompare(new LdapShaPasswordEncoder());
// or configure the provider to authenticate by LDAP bind instead of attribute comparison
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: bind test before authentication
DirContext ctx = null;
try {
    ctx = contextSource.getContext(username, rawPassword); // will throw NamingException on bad bind
} finally {
    if (ctx != null) LdapUtils.closeContext(ctx);
}

Try / catch

try {
    authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (BadCredentialsException e) {
    // generic message on purpose: show 'Invalid username or password'
}

Prevention

When it happens

Trigger: Calling LdapAuthenticationProvider/PasswordComparisonAuthenticator.authenticate() when isLdapPasswordCompare() returns false — i.e. the supplied password does not match the user's password attribute (passwordAttributeName, default 'userPassword') per the configured PasswordEncoder, or the one-level bind comparison against the LDAP server fails.

Common situations: Wrong password entered by the user; PasswordEncoder mismatch (e.g. LDAP stores SSHA hashes but a plaintext or BCrypt encoder is configured); the LDAP bind comparison fails because the bind DN lacks permission to read the password attribute; typo in user DN/search base so the entry found is not the real user.

Related errors


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