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

  1. Unlock the account in the Redis user store (set its status to OK/ACTIVE) and retry authentication
  2. Check why the account got locked: review CAS lockout/throttling configuration and failed-login logs
  3. Verify the correct Redis database/index is configured so the handler reads current account data
  4. 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

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


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)