spring-projects/spring-security · error · CredentialsExpiredException
User credentials have expired
Error message
User credentials have expired
What it means
ActiveDirectoryLdapAuthenticationProvider maps AD bind-failure sub-error codes to Spring Security exceptions. When AD returns error 49 (data 532) meaning the user's password has expired, raiseExceptionForErrorCode throws CredentialsExpiredException with message 'User credentials have expired', wrapping the ActiveDirectoryAuthenticationException as the cause.
Source
Thrown at ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java:265
}
}
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";View on GitHub (pinned to 96852e8860)
Solutions
- The user must change their AD password (via Ctrl+Alt+Del on a domain machine, self-service portal, or admin reset).
- For service accounts, rotate the configured password and update the application's credentials.
- Admin can set the password to not expire for the account if appropriate (Set-AdUser -PasswordNeverExpires).
- Catch CredentialsExpiredException in your failure handler and redirect users to a password-change flow.
Example fix
// before: generic failure handler
catch (BadCredentialsException e) { return "login?error"; }
// after: handle expired credentials distinctly
catch (CredentialsExpiredException e) { return "redirect:/password/change-expired"; } Defensive patterns
Strategy: try-catch
Try / catch
try {
return authenticationManager.authenticate(token);
} catch (CredentialsExpiredException e) {
return "redirect:/password/expired"; // send user to change-password flow
} Prevention
- Monitor AD password expiry for service accounts and rotate proactively.
- Surface 'finestGrain' error codes from ActiveDirectoryAuthenticationException in logs.
- Provide a self-service password change flow for expired credentials.
- Consider 'warn days' notifications before max password age.
When it happens
Trigger: authenticate() -> handleBindException() -> raiseExceptionForErrorCode(PASSWORD_EXPIRED), triggered when an LDAP bind against Active Directory fails with error code 532 (ERROR_PASSWORD_EXPIRED) during bindAuthentication.
Common situations: AD domain password policy's max password age elapsed for the user; service accounts whose passwords were not rotated; users authenticating who must change their password at next logon after expiration.
Related errors
- User is disabled
- User account has expired
- User account is locked
- managerPassword is required if managerDn is supplied
- No BaseLdapPathContextSource instances found. Have you added
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/aa3faf6dad583dc4.
Report an issue: GitHub.