apereo/cas · error · GeneralSecurityException

<policy status exception>

Error message

<policy status exception>

What it means

RestfulAuthenticationPolicy delegates authentication-policy decisions to a remote REST endpoint. After executing the HTTP call, any status code other than 200 OK is converted via handleResponseStatusCode and rethrown as a GeneralSecurityException carrying the resulting AuthenticationHandlerExecutionResult, so the policy is reported as unsatisfied/failed rather than silently passing.

Solutions

  1. Check the REST endpoint is reachable and returns 200 for a valid principal: curl -i the configured URL with equivalent payload.
  2. Verify cas.authn.policy.rest.url points at the correct service and context path.
  3. Inspect remote service logs for why it returned 401/403/500 for this principal.
  4. Confirm network/proxy/TLS configuration between CAS and the endpoint (status 404/502/503 usually means infrastructure, not policy).

Example fix

// before
management: # wrong path
cas:
  authn:
    policy:
      rest:
        url: https://auth.example.com/api/wrong
// after
cas:
  authn:
    policy:
      rest:
        url: https://auth.example.com/api/policy
Defensive patterns

Strategy: try-catch

Validate before calling

val probe = HttpUtils.execute(HttpUtils.httpBuilder(properties.getUrl()).GET().build())
if (probe.getCode() != 200) throw new IllegalStateException("Policy REST endpoint not healthy: " + probe.getCode())

Try / catch

try { return policy.isSatisfiedBy(authentication, service, context); }
catch (GeneralSecurityException e) {
  LOGGER.warn("RESTful authentication policy failed", e);
  return AuthenticationPolicyExecutionResult.failure();
}

Prevention

When it happens

Trigger: Calling isSatisfiedBy on a policy backed by cas.authn.policy.rest[...] when the configured REST endpoint returns a non-200 status (401/403 for rejected credentials, 404 for a wrong URL, 500 for a backend crash) for the given principal.

Common situations: REST endpoint URL misconfigured or pointing at the wrong context path; the remote service rejects the principal (blocked/locked account); the remote service is down and a gateway returns 502/503; TLS or proxy issues producing error responses; API version change so the endpoint no longer returns 200 for success.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/3f98b3316500697f. Report an issue: GitHub.

Appendix: source

Thrown at core/cas-server-core-authentication-api/src/main/java/org/apereo/cas/authentication/policy/RestfulAuthenticationPolicy.java:96

        val principal = authentication.getPrincipal();
        try {
            val entity = MAPPER.writeValueAsString(principal);
            val headers = CollectionUtils.<String, String>wrap(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            headers.putAll(Objects.requireNonNull(properties).getHeaders());
            val exec = HttpExecutionRequest.builder()
                .url(properties.getUrl())
                .basicAuthUsername(properties.getBasicAuthUsername())
                .basicAuthPassword(properties.getBasicAuthPassword())
                .method(HttpMethod.POST)
                .entity(entity)
                .headers(headers)
                .maximumRetryAttempts(properties.getMaximumRetryAttempts())
                .build();
            response = HttpUtils.execute(exec);
            val statusCode = HttpStatus.valueOf(response.getCode());
            if (statusCode != HttpStatus.OK) {
                val ex = handleResponseStatusCode(statusCode, principal);
                throw new GeneralSecurityException(ex);
            }
            return AuthenticationPolicyExecutionResult.success();
        } finally {
            HttpUtils.close(response);
        }
    }

    @Override
    public Map<String, Object> toConfiguration() {
        val config = super.toConfiguration();
        config.put("url", properties.getUrl());
        config.put("basicAuthUsername", properties.getBasicAuthUsername());
        config.put("basicAuthPassword", properties.getBasicAuthPassword());
        config.put("maximumRetryAttempts", properties.getMaximumRetryAttempts());
        config.put("headers", properties.getHeaders());
        return config;
    }
}

View on GitHub (pinned to e7288fc434)