spring-projects/spring-security · error · PasswordPolicyException
${ctrl.getErrorStatus()}
Error message
${ctrl.getErrorStatus()} What it means
PasswordPolicyAwareContextSource.getContext() consults the LDAP password-policy response control after a failed bind. If the control indicates the account is locked and carries an error status string, it throws PasswordPolicyException with the server's error status as the message; otherwise the original NamingException is converted normally.
Source
Thrown at ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyAwareContextSource.java:69
return super.getContext(principal, credentials);
}
this.logger.trace(LogMessage.format("Binding as %s, prior to reconnect as user %s", getUserDn(), principal));
// First bind as manager user before rebinding as the specific principal.
LdapContext ctx = (LdapContext) super.getContext(getUserDn(), getPassword());
Control[] rctls = { new PasswordPolicyControl(false) };
try {
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
ctx.reconnect(rctls);
}
catch (javax.naming.NamingException ex) {
PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor.extractControl(ctx);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Failed to bind with %s", ctrl), ex);
}
LdapUtils.closeContext(ctx);
if (ctrl != null && ctrl.isLocked() && ctrl.getErrorStatus() != null) {
throw new PasswordPolicyException(ctrl.getErrorStatus());
}
throw LdapUtils.convertLdapException(ex);
}
this.logger.debug(LogMessage.of(() -> "Bound with " + PasswordPolicyControlExtractor.extractControl(ctx)));
return ctx;
}
@Override
@SuppressWarnings("unchecked")
protected Hashtable getAuthenticatedEnv(String principal, String credentials) {
Hashtable<String, Object> env = super.getAuthenticatedEnv(principal, credentials);
env.put(LdapContext.CONTROL_FACTORIES, PasswordPolicyControlFactory.class.getName());
return env;
}
}
View on GitHub (pinned to 96852e8860)
Solutions
- Have the user's account unlocked / password changed according to the directory's password policy.
- Read PasswordPolicyException.getMessage() — it contains the server's error status describing the exact policy violation.
- Catch PasswordPolicyException to route users to unlock/reset flows instead of showing 'wrong password'.
- Check ppolicy overlay configuration (lockout duration, pwdMaxAge) if lockouts occur unexpectedly.
Example fix
// before
catch (BadCredentialsException e) { return "login?error"; }
// after
catch (PasswordPolicyException e) { model.addAttribute("msg", e.getMessage()); return "redirect:/account/unlock"; } Defensive patterns
Strategy: try-catch
Try / catch
try {
DirContext ctx = passwordPolicyAwareContextSource.getContext(userDn, password);
} catch (PasswordPolicyException e) {
// e.getMessage() contains the server error status (locked/expired)
model.addAttribute("policyMessage", e.getMessage());
} Prevention
- Read PasswordPolicyException.getMessage() for the exact policy violation.
- Configure ppolicy lockout duration so lockouts self-clear.
- Distinguish PasswordPolicyException from generic AuthenticationException in handlers.
- Verify the directory returns the ppolicy response control (ppolicy overlay enabled).
When it happens
Trigger: Calling getContext(username, password) (directly or via LDAP authentication using this ContextSource) where the bind fails, the server returns a PasswordPolicyResponseControl with isLocked()==true and non-null error status, e.g. 'account is locked' or 'password expired'.
Common situations: OpenLDAP/Oracle directory with ppolicy overlay locking accounts after failed attempts; authenticating a user whose password is expired per password policy; password must-change state preventing bind.
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
- ${ex.getStatus().getDefaultMessage()}
- User account is locked
- <namingException.getMessage()>
- 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/9995188f3d2876cd.
Report an issue: GitHub.