spring-projects/spring-security · error · DisabledException

User is disabled

Error message

User is disabled

What it means

ActiveDirectoryLdapAuthenticationProvider maps AD bind-failure sub-codes to Spring exceptions. When Active Directory reports the account is disabled (bind error 49 with data 533, ACCOUNT_DISABLED), raiseExceptionForErrorCode throws DisabledException('User is disabled') with the ActiveDirectoryAuthenticationException as cause.

Source

Thrown at ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java:267

	private int parseSubErrorCode(@Nullable String message) {
		if (message == null) {
			return -1;
		}
		Matcher matcher = SUB_ERROR_CODE.matcher(message);
		if (matcher.matches()) {
			return Integer.parseInt(matcher.group(1), 16);
		}
		return -1;
	}

	private void raiseExceptionForErrorCode(int code, NamingException exception) {
		String hexString = Integer.toHexString(code);
		Throwable cause = new ActiveDirectoryAuthenticationException(hexString, exception.getMessage(), exception);
		switch (code) {
			case PASSWORD_EXPIRED -> throw new CredentialsExpiredException(this.messages
				.getMessage("LdapAuthenticationProvider.credentialsExpired", "User credentials have expired"), cause);
			case ACCOUNT_DISABLED -> throw new DisabledException(
					this.messages.getMessage("LdapAuthenticationProvider.disabled", "User is disabled"), cause);
			case ACCOUNT_EXPIRED -> throw new AccountExpiredException(
					this.messages.getMessage("LdapAuthenticationProvider.expired", "User account has expired"), cause);
			case ACCOUNT_LOCKED -> throw new LockedException(
					this.messages.getMessage("LdapAuthenticationProvider.locked", "User account is locked"), cause);
			default -> throw badCredentials(cause);
		}
	}

	private String subCodeToLogMessage(int code) {
		return switch (code) {
			case USERNAME_NOT_FOUND -> "User was not found in directory";
			case INVALID_PASSWORD -> "Supplied password was invalid";
			case NOT_PERMITTED -> "User not permitted to logon at this time";
			case PASSWORD_EXPIRED -> "Password has expired";
			case ACCOUNT_DISABLED -> "Account is disabled";
			case ACCOUNT_EXPIRED -> "Account expired";
			case PASSWORD_NEEDS_RESET -> "User must reset password";

View on GitHub (pinned to 96852e8860)

Solutions

  1. Have an AD administrator enable the account (Set-ADUser -Enabled $true) if access is legitimate.
  2. Confirm you are authenticating with the intended, active account rather than a deprecated one.
  3. Catch DisabledException separately to show an account-specific message instead of a generic login failure.
  4. Verify user search base doesn't resolve to a different, disabled account with the same name.

Example fix

// before
catch (BadCredentialsException e) { return "login?error"; }
// after
catch (DisabledException e) { model.addAttribute("msg", "Account disabled; contact IT"); return "login"; }
Defensive patterns

Strategy: try-catch

Try / catch

try {
    return authenticationManager.authenticate(token);
} catch (DisabledException e) {
    model.addAttribute("msg", "Account disabled; contact IT");
    return "login";
}

Prevention

When it happens

Trigger: authenticate() -> handleBindException() -> raiseExceptionForErrorCode(ACCOUNT_DISABLED), raised when an LDAP bind against AD fails with sub-error code 533 indicating the user account is disabled in Active Directory.

Common situations: User left the company and the account was disabled; account administratively disabled pending onboarding/HR action; authenticating with a shared or legacy account that IT disabled.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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