Core API for multifactor authentication webflow configuration in Apereo CAS providing interfaces and base classes for MFA provider integration
—
Core authentication and provider selection logic including selectors, resolvers, and transaction management components. These components handle the orchestration of MFA providers, authentication context management, and provider selection strategies.
Components responsible for selecting appropriate MFA providers from available options based on various criteria.
Selects MFA providers based on ranking/priority order.
/**
* Selects MFA providers based on ranking/priority order
*/
public class RankedMultifactorAuthenticationProviderSelector
implements MultifactorAuthenticationProviderSelector {
/**
* Constructor
*/
public RankedMultifactorAuthenticationProviderSelector();
/**
* Resolve appropriate MFA provider from available providers
* @param providers Collection of available MFA providers
* @param service Service being accessed (can be null)
* @param principal Principal requesting authentication
* @return Selected MultifactorAuthenticationProvider or null if none suitable
*/
@Override
public MultifactorAuthenticationProvider resolve(
Collection<MultifactorAuthenticationProvider> providers,
RegisteredService service,
Principal principal);
/**
* Select multifactor authentication provider for service
* @param service Service being accessed
* @param providers List of available providers
* @return Selected provider or null
*/
protected MultifactorAuthenticationProvider selectMultifactorAuthenticationProvider(
RegisteredService service,
List<MultifactorAuthenticationProvider> providers);
}Creates chaining MFA providers when multiple providers are available.
/**
* Creates chaining MFA providers when multiple providers are available
*/
public class ChainingMultifactorAuthenticationProviderSelector
implements MultifactorAuthenticationProviderSelector {
/**
* Constructor
* @param applicationContext Spring application context
* @param failureModeEvaluator Evaluator for handling failure modes
*/
public ChainingMultifactorAuthenticationProviderSelector(
ConfigurableApplicationContext applicationContext,
MultifactorAuthenticationFailureModeEvaluator failureModeEvaluator);
/**
* Resolve chaining provider from available providers
* @param providers Collection of available MFA providers
* @param service Service being accessed
* @param principal Principal requesting authentication
* @return Chaining provider or selected single provider
*/
@Override
public MultifactorAuthenticationProvider resolve(
Collection<MultifactorAuthenticationProvider> providers,
RegisteredService service,
Principal principal);
/**
* Select multifactor authentication provider for service (protected method)
* @param service Service being accessed
* @param providers List of available providers
* @return Selected provider
*/
protected MultifactorAuthenticationProvider selectMultifactorAuthenticationProvider(
RegisteredService service,
List<MultifactorAuthenticationProvider> providers);
}MFA provider selector that uses Groovy scripts for custom selection logic.
/**
* MFA provider selector using Groovy scripts for custom selection logic
*/
public class GroovyScriptMultifactorAuthenticationProviderSelector
implements MultifactorAuthenticationProviderSelector {
/**
* Constructor
* @param groovyResource Resource containing the Groovy script
*/
public GroovyScriptMultifactorAuthenticationProviderSelector(Resource groovyResource);
/**
* Resolve provider using Groovy script logic
* @param providers Collection of available MFA providers
* @param service Service being accessed
* @param principal Principal requesting authentication
* @return Provider selected by Groovy script or null
*/
@Override
public MultifactorAuthenticationProvider resolve(
Collection<MultifactorAuthenticationProvider> providers,
RegisteredService service,
Principal principal);
}Groovy Script Example:
// Example Groovy script for custom provider selection
// Script receives: providers, service, principal, logger
import org.apereo.cas.authentication.MultifactorAuthenticationProvider
// Custom selection logic
def userAttributes = principal.getAttributes()
def userRole = userAttributes.get("role")
if (userRole?.contains("admin")) {
// Admins get highest security provider
return providers.find { it.getId() == "yubikey" } ?: providers.first()
} else if (service?.getServiceId()?.contains("banking")) {
// Banking services require specific provider
return providers.find { it.getId() == "sms" }
} else {
// Default to lowest friction provider
return providers.min { it.getOrder() }
}Final resolver that handles authentication transaction completion and ticket granting.
/**
* Final resolver handling authentication transaction completion and ticket granting
*/
public class FinalMultifactorAuthenticationTransactionWebflowEventResolver
extends BaseMultifactorAuthenticationProviderEventResolver {
/**
* Constructor
* @param webflowEventResolutionConfigurationContext Configuration context
*/
public FinalMultifactorAuthenticationTransactionWebflowEventResolver(
CasWebflowEventResolutionConfigurationContext webflowEventResolutionConfigurationContext);
/**
* Resolve events for transaction completion
* @param context The webflow request context
* @return Set of resolved events for transaction completion
*/
@Override
public Set<Event> resolveInternal(RequestContext context);
/**
* Resolve single event for transaction completion (audited method)
* @param context The webflow request context
* @return Single resolved event for transaction completion
*/
@Audit(action = AuditableActions.AUTHENTICATION_EVENT_RESOLVED,
actionResolverName = AuditActionResolvers.AUTHENTICATION_EVENT_RESOLVED_ACTION_RESOLVER,
resourceResolverName = AuditResourceResolvers.AUTHENTICATION_EVENT_RESOLVED_RESOURCE_RESOLVER)
public Event resolveSingle(RequestContext context);
}Configuration Examples:
@Configuration
public class MfaProviderSelectionConfiguration {
@Bean
@ConditionalOnMissingBean(name = "multifactorAuthenticationProviderSelector")
public MultifactorAuthenticationProviderSelector multifactorAuthenticationProviderSelector() {
return new RankedMultifactorAuthenticationProviderSelector();
}
@Bean
@ConditionalOnProperty(name = "cas.authn.mfa.provider-selector.groovy.location")
public MultifactorAuthenticationProviderSelector groovyProviderSelector(
@Value("${cas.authn.mfa.provider-selector.groovy.location}") Resource groovyScript) {
return new GroovyScriptMultifactorAuthenticationProviderSelector(groovyScript);
}
@Bean
@ConditionalOnProperty(name = "cas.authn.mfa.provider-selector.chaining.enabled", havingValue = "true")
public MultifactorAuthenticationProviderSelector chainingProviderSelector(
ConfigurableApplicationContext applicationContext,
MultifactorAuthenticationFailureModeEvaluator failureModeEvaluator) {
return new ChainingMultifactorAuthenticationProviderSelector(applicationContext, failureModeEvaluator);
}
}@Component
public class CustomProviderSelectionLogic {
@Bean
public MultifactorAuthenticationProviderSelector customProviderSelector() {
return new CustomProviderSelector();
}
private static class CustomProviderSelector implements MultifactorAuthenticationProviderSelector {
@Override
public MultifactorAuthenticationProvider resolve(
Collection<MultifactorAuthenticationProvider> providers,
RegisteredService service,
Principal principal) {
// Example: Select based on user attributes
val userAttributes = principal.getAttributes();
val department = userAttributes.get("department");
if (department != null && department.contains("finance")) {
// Finance users get hardware tokens
return providers.stream()
.filter(p -> "yubikey".equals(p.getId()))
.findFirst()
.orElse(null);
}
// Example: Select based on service properties
if (service != null) {
val serviceProperties = service.getProperties();
val requiredMfa = serviceProperties.get("requiredMfaProvider");
if (requiredMfa != null) {
return providers.stream()
.filter(p -> requiredMfa.getValue().equals(p.getId()))
.findFirst()
.orElse(null);
}
}
// Default: Select highest ranked available provider
return providers.stream()
.min(Comparator.comparing(MultifactorAuthenticationProvider::getOrder))
.orElse(null);
}
}
}@Configuration
public class MfaTransactionConfiguration {
@Bean
public CasWebflowEventResolver finalMultifactorAuthenticationTransactionEventResolver(
@Qualifier("casWebflowConfigurationContext")
CasWebflowEventResolutionConfigurationContext context) {
return new FinalMultifactorAuthenticationTransactionWebflowEventResolver(context);
}
@EventListener
public void handleMfaTransactionCompletion(MultifactorAuthenticationTransactionCompleteEvent event) {
val authentication = event.getAuthentication();
val service = event.getService();
// Custom logic after MFA transaction completion
logger.info("MFA transaction completed for user {} accessing service {}",
authentication.getPrincipal().getId(),
service != null ? service.getId() : "unknown");
// Additional processing like audit logging, metrics, etc.
}
}The authentication components integrate with the broader CAS authentication context:
// Example of using components in authentication flow
public class CustomAuthenticationHandler implements AuthenticationHandler {
private final MultifactorAuthenticationProviderSelector providerSelector;
public CustomAuthenticationHandler(MultifactorAuthenticationProviderSelector providerSelector) {
this.providerSelector = providerSelector;
}
@Override
public AuthenticationHandlerExecutionResult authenticate(Credential credential) throws GeneralSecurityException {
// During authentication, select appropriate MFA provider
val availableProviders = getAvailableMfaProviders();
val service = getServiceFromContext();
val principal = extractPrincipal(credential);
val selectedProvider = providerSelector.resolve(availableProviders, service, principal);
if (selectedProvider != null) {
// Store selected provider for webflow to use
storeSelectedProvider(selectedProvider);
}
return createSuccessfulAuthenticationResult(credential);
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-core-webflow-mfa-api