spring-projects/spring-security · error · LockedException
User account is locked
Error message
User account is locked
What it means
ActiveDirectoryLdapAuthenticationProvider maps AD bind-failure sub-codes to Spring Security exceptions. When AD bind fails with sub-error 775 (ACCOUNT_LOCKED, too many bad password attempts), raiseExceptionForErrorCode throws LockedException('User account is locked') wrapping the ActiveDirectoryAuthenticationException as cause.
Source
Thrown at ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java:271
}
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";
case ACCOUNT_LOCKED -> "Account locked";
default -> "Unknown (error code " + Integer.toHexString(code) + ")";
};
}View on GitHub (pinned to 96852e8860)
Solutions
- Wait for the AD lockout duration to pass or have an admin unlock the account (Unlock-ADAccount).
- Fix the stale/incorrect credentials in any application or scheduled job using this account to stop re-locking.
- Catch LockedException separately to inform the user their account is locked rather than 'wrong password'.
- Review AD lockout policy and the security event log to find the source of bad password attempts.
Example fix
// before
catch (BadCredentialsException e) { return "login?error"; }
// after
catch (LockedException e) { model.addAttribute("msg", "Account locked; contact helpdesk"); return "login"; } Defensive patterns
Strategy: try-catch
Try / catch
try {
return authenticationManager.authenticate(token);
} catch (LockedException e) {
model.addAttribute("msg", "Account locked; contact helpdesk or retry later");
return "login";
} Prevention
- Fix stale credentials in automated jobs to prevent re-locking.
- Catch LockedException separately to distinguish lockout from wrong password.
- Monitor AD lockout events (4740) to find offending sources.
- Implement client-side throttling to avoid contributing to lockouts.
When it happens
Trigger: authenticate() -> handleBindException() -> raiseExceptionForErrorCode(ACCOUNT_LOCKED), raised when the LDAP bind against AD fails with sub-error code 775 indicating the account is locked out by the domain lockout policy.
Common situations: Repeated wrong-password logins triggering AD lockout policy; a service with stale credentials retrying continuously and locking the account; user locked out across the domain by attacker lockout or misconfigured app.
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
- User credentials have expired
- User is disabled
- User account has expired
- ${ctrl.getErrorStatus()}
- managerPassword is required if managerDn is supplied
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/d0eec55c2b57f223.
Report an issue: GitHub.