spring-projects/spring-security · error · BadCredentialsException

Bad credentials

Error message

Bad credentials

What it means

DaoAuthenticationProvider throws BadCredentialsException in additionalAuthenticationChecks when the authentication token carries no credentials at all (authentication.getCredentials() == null). Spring Security deliberately reuses a generic 'Bad credentials' message so attackers cannot distinguish a missing password from a wrong one. This is thrown after the user has been successfully looked up by username.

Source

Thrown at core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java:85

	private volatile @Nullable String userNotFoundEncodedPassword;

	private final UserDetailsService userDetailsService;

	private UserDetailsPasswordService userDetailsPasswordService = UserDetailsPasswordService.NOOP;

	private @Nullable CompromisedPasswordChecker compromisedPasswordChecker;

	public DaoAuthenticationProvider(UserDetailsService userDetailsService) {
		Assert.notNull(userDetailsService, "userDetailsService cannot be null");
		this.userDetailsService = userDetailsService;
	}

	@Override
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			this.logger.debug("Failed to authenticate since no credentials provided");
			throw new BadCredentialsException(this.messages
				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
		String presentedPassword = authentication.getCredentials().toString();
		if (!this.passwordEncoder.get().matches(presentedPassword, userDetails.getPassword())) {
			this.logger.debug("Failed to authenticate since password does not match stored value");
			throw new BadCredentialsException(this.messages
				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

	@Override
	protected void doAfterPropertiesSet() {
		Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
	}

	@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {

View on GitHub (pinned to 96852e8860)

Solutions

  1. Ensure the password is non-null when constructing UsernamePasswordAuthenticationToken(username, password) before calling AuthenticationManager.authenticate()
  2. If credentials are supplied via HTTP, verify the request actually carries the password parameter and that the filter reading it (e.g. UsernamePasswordAuthenticationFilter) has the correct parameter names configured
  3. When building tokens in tests or code, assert credentials are present: Assert.hasText(password, ...) before authenticate()

Example fix

// before
Authentication auth = new UsernamePasswordAuthenticationToken(username, null);
authManager.authenticate(auth);
// after
Assert.hasText(password, "password is required");
Authentication auth = new UsernamePasswordAuthenticationToken(username, password);
authManager.authenticate(auth);
Defensive patterns

Strategy: validation

Validate before calling

if (password == null || password.isEmpty()) { throw new IllegalArgumentException("password is required"); }
Authentication token = new UsernamePasswordAuthenticationToken(username, password);

Type guard

boolean hasCredentials(Authentication a) { return a != null && a.getCredentials() instanceof String s && !s.isEmpty(); }

Try / catch

try { return authManager.authenticate(token); } catch (BadCredentialsException e) { throw new LoginFailureException("Invalid username or password"); }

Prevention

When it happens

Trigger: Calling UsernamePasswordAuthenticationToken(username, null) and passing it to AuthenticationManager.authenticate(); an AuthenticationProvider or filter upstream that strips or never sets credentials; custom filters building the token before the servlet request parameters are read (e.g. missing Content-Type so parameters are not populated).

Common situations: Custom REST login endpoints where the JSON body password field is absent or null; misconfigured form login where the password parameter name was changed without updating the filter; clients sending only a username in the auth request; tests constructing the token with a null credential.

Related errors


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