Core authentication API module for Apereo CAS providing fundamental authentication interfaces, implementations, and components for the CAS server infrastructure
—
Authentication policies define the rules and requirements that must be satisfied for an authentication attempt to be considered successful. The CAS authentication API provides a comprehensive set of configurable policies that can be combined to implement complex authentication requirements.
package org.apereo.cas.authentication;
public interface AuthenticationPolicy {
AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction) throws Exception;
boolean shouldResumeOnFailure(Throwable failure);
String getName();
void setName(String name);
int getOrder();
void setOrder(int order);
}package org.apereo.cas.authentication;
import java.util.Optional;
public interface AuthenticationPolicyExecutionResult {
boolean isSuccess();
Optional<Throwable> getFailure();
static AuthenticationPolicyExecutionResult success() {
return new DefaultAuthenticationPolicyExecutionResult(true, null);
}
static AuthenticationPolicyExecutionResult failure(Throwable failure) {
return new DefaultAuthenticationPolicyExecutionResult(false, failure);
}
}
class DefaultAuthenticationPolicyExecutionResult implements AuthenticationPolicyExecutionResult {
private final boolean success;
private final Throwable failure;
public DefaultAuthenticationPolicyExecutionResult(boolean success, Throwable failure) {
this.success = success;
this.failure = failure;
}
public boolean isSuccess() { return success; }
public Optional<Throwable> getFailure() { return Optional.ofNullable(failure); }
}package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicy;
import org.springframework.core.Ordered;
public abstract class BaseAuthenticationPolicy implements AuthenticationPolicy {
private int order = Ordered.LOWEST_PRECEDENCE;
private String name = getClass().getSimpleName();
public int getOrder() { return order; }
public void setOrder(int order) { this.order = order; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public boolean shouldResumeOnFailure(Throwable failure) {
return false;
}
}Requires all configured authentication handlers to succeed:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
public class AllAuthenticationHandlersSucceededAuthenticationPolicy
extends BaseAuthenticationPolicy {
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Set<AuthenticationHandler> handlers = transaction.getAuthenticationHandlers();
Map<String, Throwable> failures = transaction.getFailures();
if (handlers.isEmpty()) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("No authentication handlers configured"));
}
for (AuthenticationHandler handler : handlers) {
if (failures.containsKey(handler.getName())) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("Handler " + handler.getName() + " failed"));
}
}
return AuthenticationPolicyExecutionResult.success();
}
}Requires specific authentication handlers to succeed:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
import java.util.Set;
public class RequiredAuthenticationHandlerAuthenticationPolicy
extends BaseAuthenticationHandlerAuthenticationPolicy {
private final Set<String> requiredHandlerNames;
private final boolean tryAll;
public RequiredAuthenticationHandlerAuthenticationPolicy(Set<String> requiredHandlerNames) {
this(requiredHandlerNames, false);
}
public RequiredAuthenticationHandlerAuthenticationPolicy(Set<String> requiredHandlerNames,
boolean tryAll) {
this.requiredHandlerNames = requiredHandlerNames;
this.tryAll = tryAll;
}
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
for (String requiredHandler : requiredHandlerNames) {
if (!successes.containsKey(requiredHandler)) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("Required handler " + requiredHandler + " did not succeed"));
}
}
return AuthenticationPolicyExecutionResult.success();
}
public boolean shouldResumeOnFailure(Throwable failure) {
return tryAll;
}
}Excludes specific authentication handlers from being required:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
import java.util.Set;
public class ExcludedAuthenticationHandlerAuthenticationPolicy
extends BaseAuthenticationHandlerAuthenticationPolicy {
private final Set<String> excludedHandlerNames;
public ExcludedAuthenticationHandlerAuthenticationPolicy(Set<String> excludedHandlerNames) {
this.excludedHandlerNames = excludedHandlerNames;
}
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
Map<String, Throwable> failures = transaction.getFailures();
// Check if any non-excluded handlers succeeded
boolean hasNonExcludedSuccess = successes.keySet().stream()
.anyMatch(handlerName -> !excludedHandlerNames.contains(handlerName));
if (hasNonExcludedSuccess) {
return AuthenticationPolicyExecutionResult.success();
}
// Check if only excluded handlers failed
boolean onlyExcludedFailures = failures.keySet().stream()
.allMatch(excludedHandlerNames::contains);
if (onlyExcludedFailures && !successes.isEmpty()) {
return AuthenticationPolicyExecutionResult.success();
}
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("No non-excluded authentication handlers succeeded"));
}
}Requires all provided credentials to be validated successfully:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
import org.apereo.cas.authentication.Credential;
public class AllCredentialsValidatedAuthenticationPolicy extends BaseAuthenticationPolicy {
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Collection<Credential> credentials = transaction.getCredentials();
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
if (credentials.isEmpty()) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("No credentials provided"));
}
for (Credential credential : credentials) {
boolean credentialValidated = successes.values().stream()
.anyMatch(result -> result.getCredential().equals(credential));
if (!credentialValidated) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("Credential not validated: " + credential.getId()));
}
}
return AuthenticationPolicyExecutionResult.success();
}
}Requires at least one credential to be validated successfully:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
public class AtLeastOneCredentialValidatedAuthenticationPolicy extends BaseAuthenticationPolicy {
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Collection<Credential> credentials = transaction.getCredentials();
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
if (credentials.isEmpty()) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("No credentials provided"));
}
if (successes.isEmpty()) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("No authentication handlers succeeded"));
}
// At least one credential was validated if we have successes
return AuthenticationPolicyExecutionResult.success();
}
}Requires specific attributes to be present in the authentication:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
import org.apereo.cas.authentication.principal.Principal;
import java.util.Map;
import java.util.Set;
public class RequiredAttributesAuthenticationPolicy extends BaseAuthenticationPolicy {
private final Map<String, Set<Object>> requiredAttributes;
private final boolean tryAll;
public RequiredAttributesAuthenticationPolicy(Map<String, Set<Object>> requiredAttributes) {
this(requiredAttributes, false);
}
public RequiredAttributesAuthenticationPolicy(Map<String, Set<Object>> requiredAttributes,
boolean tryAll) {
this.requiredAttributes = requiredAttributes;
this.tryAll = tryAll;
}
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
for (AuthenticationHandlerExecutionResult result : successes.values()) {
Principal principal = result.getPrincipal();
Map<String, List<Object>> principalAttributes = principal.getAttributes();
for (Map.Entry<String, Set<Object>> requiredEntry : requiredAttributes.entrySet()) {
String attributeName = requiredEntry.getKey();
Set<Object> requiredValues = requiredEntry.getValue();
List<Object> actualValues = principalAttributes.get(attributeName);
if (actualValues == null || actualValues.isEmpty()) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("Required attribute missing: " + attributeName));
}
if (!requiredValues.isEmpty()) {
boolean hasRequiredValue = actualValues.stream()
.anyMatch(requiredValues::contains);
if (!hasRequiredValue) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("Required attribute value not found: " + attributeName));
}
}
}
}
return AuthenticationPolicyExecutionResult.success();
}
public boolean shouldResumeOnFailure(Throwable failure) {
return tryAll;
}
}Ensures that no authentication attempts are marked as prevented:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
import org.apereo.cas.authentication.PreventedException;
public class NotPreventedAuthenticationPolicy extends BaseAuthenticationPolicy {
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Map<String, Throwable> failures = transaction.getFailures();
for (Throwable failure : failures.values()) {
if (failure instanceof PreventedException) {
return AuthenticationPolicyExecutionResult.failure(failure);
}
}
return AuthenticationPolicyExecutionResult.success();
}
}Ensures that all successful authentications resolve to the same principal:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
import org.apereo.cas.authentication.principal.Principal;
import java.util.Set;
import java.util.stream.Collectors;
public class UniquePrincipalAuthenticationPolicy extends BaseAuthenticationPolicy {
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
if (successes.size() <= 1) {
return AuthenticationPolicyExecutionResult.success();
}
Set<String> principalIds = successes.values().stream()
.map(AuthenticationHandlerExecutionResult::getPrincipal)
.map(Principal::getId)
.collect(Collectors.toSet());
if (principalIds.size() > 1) {
return AuthenticationPolicyExecutionResult.failure(
new MixedPrincipalException("Multiple principals found: " + principalIds));
}
return AuthenticationPolicyExecutionResult.success();
}
}Allows defining authentication policies using Groovy scripts:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;
public class GroovyScriptAuthenticationPolicy extends BaseAuthenticationPolicy {
private final ExecutableCompiledGroovyScript watchableScript;
public GroovyScriptAuthenticationPolicy(ExecutableCompiledGroovyScript watchableScript) {
this.watchableScript = watchableScript;
}
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
Object result = watchableScript.execute(transaction,
AuthenticationPolicyExecutionResult.class);
if (result instanceof AuthenticationPolicyExecutionResult) {
return (AuthenticationPolicyExecutionResult) result;
} else if (result instanceof Boolean) {
return (Boolean) result
? AuthenticationPolicyExecutionResult.success()
: AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("Groovy script returned false"));
}
return AuthenticationPolicyExecutionResult.success();
}
}Delegates policy decisions to external REST services:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
import org.apereo.cas.authentication.AuthenticationTransaction;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
public class RestfulAuthenticationPolicy extends BaseAuthenticationPolicy {
private final RestTemplate restTemplate;
private final String endpoint;
public RestfulAuthenticationPolicy(RestTemplate restTemplate, String endpoint) {
this.restTemplate = restTemplate;
this.endpoint = endpoint;
}
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
throws Exception {
try {
Map<String, Object> request = Map.of(
"transaction", transaction,
"credentials", transaction.getCredentials(),
"successes", transaction.getSuccesses(),
"failures", transaction.getFailures()
);
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(request);
ResponseEntity<Map> response = restTemplate.exchange(
endpoint, HttpMethod.POST, entity, Map.class);
Map<String, Object> responseBody = response.getBody();
if (responseBody != null) {
Boolean success = (Boolean) responseBody.get("success");
if (success != null && success) {
return AuthenticationPolicyExecutionResult.success();
}
String message = (String) responseBody.get("message");
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException(message != null ? message : "REST policy failed"));
}
} catch (Exception e) {
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("REST policy endpoint error", e));
}
return AuthenticationPolicyExecutionResult.failure(
new AuthenticationException("REST policy evaluation failed"));
}
}Policy resolvers determine which policies should be applied to authentication transactions:
package org.apereo.cas.authentication;
import java.util.Set;
public interface AuthenticationPolicyResolver {
Set<AuthenticationPolicy> resolve(AuthenticationTransaction transaction);
boolean supports(AuthenticationTransaction transaction);
}Resolves policies based on the registered service configuration:
package org.apereo.cas.authentication.policy;
import org.apereo.cas.authentication.AuthenticationPolicy;
import org.apereo.cas.authentication.AuthenticationPolicyResolver;
import org.apereo.cas.authentication.AuthenticationTransaction;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ServicesManager;
import java.util.Set;
import java.util.LinkedHashSet;
public class RegisteredServiceAuthenticationPolicyResolver
implements AuthenticationPolicyResolver {
private final ServicesManager servicesManager;
public RegisteredServiceAuthenticationPolicyResolver(ServicesManager servicesManager) {
this.servicesManager = servicesManager;
}
public Set<AuthenticationPolicy> resolve(AuthenticationTransaction transaction) {
Set<AuthenticationPolicy> policies = new LinkedHashSet<>();
Service service = transaction.getService();
if (service != null) {
RegisteredService registeredService = servicesManager.findServiceBy(service);
if (registeredService != null && registeredService.getAuthenticationPolicy() != null) {
// Add required handler policy if specified
Set<String> requiredHandlers = registeredService.getAuthenticationPolicy()
.getRequiredAuthenticationHandlers();
if (!requiredHandlers.isEmpty()) {
policies.add(new RequiredAuthenticationHandlerAuthenticationPolicy(requiredHandlers));
}
// Add required attribute policy if specified
Map<String, Set<Object>> requiredAttributes = registeredService.getAuthenticationPolicy()
.getRequiredAttributes();
if (!requiredAttributes.isEmpty()) {
policies.add(new RequiredAttributesAuthenticationPolicy(requiredAttributes));
}
}
}
return policies;
}
public boolean supports(AuthenticationTransaction transaction) {
return transaction.getService() != null;
}
}@Configuration
public class AuthenticationPolicyConfiguration {
@Bean
public AuthenticationPolicy allCredentialsValidatedAuthenticationPolicy() {
return new AllCredentialsValidatedAuthenticationPolicy();
}
@Bean
public AuthenticationPolicy requiredHandlersAuthenticationPolicy() {
Set<String> requiredHandlers = Set.of("LdapAuthenticationHandler", "DatabaseAuthenticationHandler");
return new RequiredAuthenticationHandlerAuthenticationPolicy(requiredHandlers);
}
@Bean
public AuthenticationPolicy requiredAttributesAuthenticationPolicy() {
Map<String, Set<Object>> requiredAttributes = Map.of(
"memberOf", Set.of("CN=Users,DC=example,DC=com"),
"accountEnabled", Set.of("true")
);
return new RequiredAttributesAuthenticationPolicy(requiredAttributes);
}
@Bean
public AuthenticationPolicyResolver registeredServiceAuthenticationPolicyResolver(
ServicesManager servicesManager) {
return new RegisteredServiceAuthenticationPolicyResolver(servicesManager);
}
}// Create policy chain
List<AuthenticationPolicy> policies = List.of(
new NotPreventedAuthenticationPolicy(),
new AtLeastOneCredentialValidatedAuthenticationPolicy(),
new UniquePrincipalAuthenticationPolicy()
);
// Register policies in execution plan
for (AuthenticationPolicy policy : policies) {
authenticationEventExecutionPlan.registerAuthenticationPolicy(policy);
}
// Create Groovy-based policy
ExecutableCompiledGroovyScript script = // ... create script
GroovyScriptAuthenticationPolicy groovyPolicy = new GroovyScriptAuthenticationPolicy(script);
authenticationEventExecutionPlan.registerAuthenticationPolicy(groovyPolicy);
// Create REST-based policy
RestTemplate restTemplate = new RestTemplate();
RestfulAuthenticationPolicy restPolicy = new RestfulAuthenticationPolicy(
restTemplate, "https://policy.example.com/validate");
authenticationEventExecutionPlan.registerAuthenticationPolicy(restPolicy);Authentication policies provide fine-grained control over authentication requirements and enable complex security scenarios by composing multiple policy types to meet specific organizational needs.
Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-core-authentication-api