apereo/cas · error · AccountLockedException
AccountLockedException
Error message
AccountLockedException
What it means
RedisAuthenticationHandler throws AccountLockedException when the account record stored in Redis reports status LOCKED during username/password authentication. CAS maps each account state to a dedicated GeneralSecurityException so the authentication chain and lockout machinery can react appropriately. A locked account is rejected even if the supplied password is correct.
Solutions
- Unlock the account in the Redis user store (set its status to OK/ACTIVE) and retry authentication
- Check why the account got locked: review CAS lockout/throttling configuration and failed-login logs
- Verify the correct Redis database/index is configured so the handler reads current account data
- If the status mapping is wrong in your custom Redis user service, fix the persisted status value
Example fix
// before (stored in Redis)
{"username":"jdoe","status":"LOCKED"}
// after
{"username":"jdoe","status":"OK"} Defensive patterns
Strategy: try-catch
Validate before calling
// Check account status in Redis before authentication
val acct = redisAccountService.findAccount(username);
if (acct != null && acct.getStatus() == AccountStatus.LOCKED) {
throw new AccountLockedException("Account is locked");
} Type guard
boolean isUnlockable(AccountState s) {
return s != null && s.getStatus() != AccountStatus.LOCKED;
} Try / catch
try {
return authenticationHandler.authenticate(credential);
} catch (AccountLockedException e) {
LOGGER.warn("Account locked: {}", credential.getId());
// route to unlock/contact-admin flow
throw e;
} Prevention
- Monitor and alert on LOCKED account counts in Redis
- Keep lockout policies consistent between CAS throttling and the user store
- Audit automated systems that set LOCKED status
- Clear stale lock flags when syncing accounts from upstream sources
When it happens
Trigger: authenticateUsernamePasswordInternal successfully loads the account from Redis and verifies the password, but account.getStatus() returns LOCKED in the switch statement.
Common situations: An administrator locked the user in the Redis-backed user store; an automated lockout policy flagged the account after repeated failures; stale Redis data left an old LOCKED status after the lock was lifted elsewhere.
Related errors
- AccountNotFoundException
- FailedLoginException
- AccountDisabledException
- AccountExpiredException
- AccountPasswordMustChangeException
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/d3e9670c4e259018.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-redis-authentication/src/main/java/org/apereo/cas/redis/RedisAuthenticationHandler.java:46
this.redisTemplate = redisTemplate;
}
@Override
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(
final UsernamePasswordCredential credential,
final String originalPassword) throws Throwable {
val account = (RedisUserAccount) redisTemplate.opsForValue().get(credential.getUsername());
if (account == null) {
throw new AccountNotFoundException();
}
if (!getPasswordEncoder().matches(originalPassword, account.getPassword())) {
LOGGER.warn("Account password on record for [{}] does not match the given/encoded password", credential.getId());
throw new FailedLoginException();
}
switch (account.getStatus()) {
case DISABLED -> throw new AccountDisabledException();
case EXPIRED -> throw new AccountExpiredException();
case LOCKED -> throw new AccountLockedException();
case MUST_CHANGE_PASSWORD -> throw new AccountPasswordMustChangeException();
case OK -> LOGGER.debug("Account status is OK");
}
val principal = principalFactory.createPrincipal(account.getUsername(), account.getAttributes());
return createHandlerResult(credential, principal, new ArrayList<>());
}
}
View on GitHub (pinned to e7288fc434)