CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-apereo-cas--cas-server-core-authentication-api

Core authentication API module for Apereo CAS providing fundamental authentication interfaces, implementations, and components for the CAS server infrastructure

Pending
Overview
Eval results
Files

principal-resolution.mddocs/

Principal Resolution

Principal resolution is the process of identifying and constructing user principals with their associated attributes after successful authentication. The CAS authentication API provides a flexible framework for resolving principals from various sources and managing their attributes through configurable merging strategies.

Core Principal Interface

package org.apereo.cas.authentication.principal;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

public interface Principal extends Serializable {
    String getId();
    Map<String, List<Object>> getAttributes();
}

Principal Implementations

Simple Principal

The default implementation that provides immutable attribute storage:

package org.apereo.cas.authentication.principal;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class SimplePrincipal implements Principal {
    private final String id;
    private final Map<String, List<Object>> attributes;
    
    @JsonCreator
    public SimplePrincipal(@JsonProperty("id") String id,
                          @JsonProperty("attributes") Map<String, List<Object>> attributes) {
        this.id = id;
        this.attributes = attributes != null ? 
            new TreeMap<>(String.CASE_INSENSITIVE_ORDER) {{
                putAll(attributes);
            }} : new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    }
    
    public SimplePrincipal(String id) {
        this(id, null);
    }
    
    public String getId() { return id; }
    
    public Map<String, List<Object>> getAttributes() { return attributes; }
    
    @Override
    public boolean equals(Object obj) {
        if (obj == null || !this.getClass().equals(obj.getClass())) {
            return false;
        }
        return getId().equals(((SimplePrincipal) obj).getId());
    }
    
    @Override
    public int hashCode() {
        return getId().hashCode();
    }
}

Null Principal

Null object pattern implementation for cases where no principal is available:

package org.apereo.cas.authentication.principal;

import java.util.Collections;
import java.util.List;
import java.util.Map;

public final class NullPrincipal implements Principal {
    private static final NullPrincipal INSTANCE = new NullPrincipal();
    
    private NullPrincipal() {}
    
    public static NullPrincipal getInstance() {
        return INSTANCE;
    }
    
    public String getId() {
        return null;
    }
    
    public Map<String, List<Object>> getAttributes() {
        return Collections.emptyMap();
    }
    
    @Override
    public String toString() {
        return "[Principal id=null, attributes={}]";
    }
}

Principal Factory

Principal factories are responsible for creating principal instances:

package org.apereo.cas.authentication.principal;

import java.util.List;
import java.util.Map;

public interface PrincipalFactory {
    Principal createPrincipal(String id);
    Principal createPrincipal(String id, Map<String, List<Object>> attributes);
    Principal createPrincipal(String id, Map<String, List<Object>> attributes, 
                            Map<String, List<Object>> originalAttributes);
}

Default Principal Factory

package org.apereo.cas.authentication.principal;

import java.util.List;
import java.util.Map;

public class DefaultPrincipalFactory implements PrincipalFactory {
    
    public Principal createPrincipal(String id) {
        return new SimplePrincipal(id);
    }
    
    public Principal createPrincipal(String id, Map<String, List<Object>> attributes) {
        return new SimplePrincipal(id, attributes);
    }
    
    public Principal createPrincipal(String id, 
                                   Map<String, List<Object>> attributes,
                                   Map<String, List<Object>> originalAttributes) {
        return new SimplePrincipal(id, attributes);
    }
}

Groovy Principal Factory

Factory that uses Groovy scripts for dynamic principal creation:

package org.apereo.cas.authentication.principal;

import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;
import java.util.List;
import java.util.Map;

public class GroovyPrincipalFactory implements PrincipalFactory {
    private final ExecutableCompiledGroovyScript watchableScript;
    
    public GroovyPrincipalFactory(ExecutableCompiledGroovyScript watchableScript) {
        this.watchableScript = watchableScript;
    }
    
    public Principal createPrincipal(String id, Map<String, List<Object>> attributes) {
        Object result = watchableScript.execute(id, attributes, Principal.class);
        if (result instanceof Principal) {
            return (Principal) result;
        }
        return new SimplePrincipal(id, attributes);
    }
    
    public Principal createPrincipal(String id) {
        return createPrincipal(id, null);
    }
    
    public Principal createPrincipal(String id, 
                                   Map<String, List<Object>> attributes,
                                   Map<String, List<Object>> originalAttributes) {
        Object result = watchableScript.execute(id, attributes, originalAttributes, Principal.class);
        if (result instanceof Principal) {
            return (Principal) result;
        }
        return new SimplePrincipal(id, attributes);
    }
}

RESTful Principal Factory

Factory that creates principals by calling REST endpoints:

package org.apereo.cas.authentication.principal;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Map;

public class RestfulPrincipalFactory implements PrincipalFactory {
    private final RestTemplate restTemplate;
    private final String endpoint;
    
    public RestfulPrincipalFactory(RestTemplate restTemplate, String endpoint) {
        this.restTemplate = restTemplate;
        this.endpoint = endpoint;
    }
    
    public Principal createPrincipal(String id, Map<String, List<Object>> attributes) {
        try {
            Map<String, Object> request = Map.of("id", id, "attributes", attributes);
            HttpEntity<Map<String, Object>> entity = new HttpEntity<>(request);
            
            Map<String, Object> response = restTemplate.exchange(
                endpoint, HttpMethod.POST, entity, Map.class).getBody();
                
            if (response != null && response.containsKey("id")) {
                String principalId = (String) response.get("id");
                Map<String, List<Object>> principalAttributes = 
                    (Map<String, List<Object>>) response.get("attributes");
                return new SimplePrincipal(principalId, principalAttributes);
            }
        } catch (Exception e) {
            // Log error and fall back to default behavior
        }
        
        return new SimplePrincipal(id, attributes);
    }
    
    public Principal createPrincipal(String id) {
        return createPrincipal(id, null);
    }
    
    public Principal createPrincipal(String id, 
                                   Map<String, List<Object>> attributes,
                                   Map<String, List<Object>> originalAttributes) {
        return createPrincipal(id, attributes);
    }
}

Principal Factory Utilities

package org.apereo.cas.authentication.principal;

public final class PrincipalFactoryUtils {
    private PrincipalFactoryUtils() {}
    
    public static PrincipalFactory newPrincipalFactory() {
        return new DefaultPrincipalFactory();
    }
}

Principal Election Strategy

When multiple authentication handlers produce different principals, an election strategy determines which principal to use:

package org.apereo.cas.authentication.principal;

import org.apereo.cas.authentication.Authentication;
import java.util.Collection;

public interface PrincipalElectionStrategy {
    Principal electPrincipal(Collection<Authentication> authentications);
}

Default Election Strategy

package org.apereo.cas.authentication.principal;

import org.apereo.cas.authentication.Authentication;
import java.util.Collection;

public class DefaultPrincipalElectionStrategy implements PrincipalElectionStrategy {
    
    public Principal electPrincipal(Collection<Authentication> authentications) {
        return authentications.stream()
            .findFirst()
            .map(Authentication::getPrincipal)
            .orElse(NullPrincipal.getInstance());
    }
}

Chaining Election Strategy

package org.apereo.cas.authentication.principal;

import org.apereo.cas.authentication.Authentication;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class ChainingPrincipalElectionStrategy implements PrincipalElectionStrategy {
    private final List<PrincipalElectionStrategy> chain = new ArrayList<>();
    
    public void addStrategy(PrincipalElectionStrategy strategy) {
        chain.add(strategy);
    }
    
    public Principal electPrincipal(Collection<Authentication> authentications) {
        return chain.stream()
            .map(strategy -> strategy.electPrincipal(authentications))
            .filter(principal -> principal != null && !NullPrincipal.getInstance().equals(principal))
            .findFirst()
            .orElse(NullPrincipal.getInstance());
    }
}

Principal Resolvers

Principal resolvers are responsible for enriching principals with attributes from external sources:

package org.apereo.cas.authentication.principal;

import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.Credential;
import java.util.Optional;

public interface PrincipalResolver {
    Optional<Principal> resolve(Credential credential, 
                               Optional<Principal> currentPrincipal,
                               Optional<AuthenticationHandler> handler);
    boolean supports(Credential credential);
    IPersonAttributeDao getAttributeRepository();
}

Echoing Principal Resolver

Simple resolver that returns the principal as-is:

package org.apereo.cas.authentication.principal.resolvers;

import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import java.util.Optional;

public class EchoingPrincipalResolver implements PrincipalResolver {
    
    public Optional<Principal> resolve(Credential credential,
                                     Optional<Principal> currentPrincipal,
                                     Optional<AuthenticationHandler> handler) {
        return currentPrincipal;
    }
    
    public boolean supports(Credential credential) {
        return true;
    }
    
    public IPersonAttributeDao getAttributeRepository() {
        return null;
    }
}

Proxying Principal Resolver

Resolver that creates proxy principals for service authentication:

package org.apereo.cas.authentication.principal.resolvers;

import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import java.util.Optional;

public class ProxyingPrincipalResolver implements PrincipalResolver {
    private final PrincipalFactory principalFactory;
    
    public ProxyingPrincipalResolver(PrincipalFactory principalFactory) {
        this.principalFactory = principalFactory;
    }
    
    public Optional<Principal> resolve(Credential credential,
                                     Optional<Principal> currentPrincipal,
                                     Optional<AuthenticationHandler> handler) {
        String principalId = credential.getId();
        if (principalId != null) {
            Principal principal = principalFactory.createPrincipal(principalId);
            return Optional.of(principal);
        }
        return currentPrincipal;
    }
    
    public boolean supports(Credential credential) {
        return credential != null && credential.getId() != null;
    }
    
    public IPersonAttributeDao getAttributeRepository() {
        return null;
    }
}

Chaining Principal Resolver

Resolver that chains multiple resolvers together:

package org.apereo.cas.authentication.principal.resolvers;

import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class ChainingPrincipalResolver implements PrincipalResolver {
    private final List<PrincipalResolver> chain = new ArrayList<>();
    
    public void addResolver(PrincipalResolver resolver) {
        chain.add(resolver);
    }
    
    public Optional<Principal> resolve(Credential credential,
                                     Optional<Principal> currentPrincipal,
                                     Optional<AuthenticationHandler> handler) {
        Optional<Principal> result = currentPrincipal;
        
        for (PrincipalResolver resolver : chain) {
            if (resolver.supports(credential)) {
                result = resolver.resolve(credential, result, handler);
                if (result.isPresent()) {
                    break;
                }
            }
        }
        
        return result;
    }
    
    public boolean supports(Credential credential) {
        return chain.stream().anyMatch(resolver -> resolver.supports(credential));
    }
    
    public IPersonAttributeDao getAttributeRepository() {
        return chain.stream()
            .map(PrincipalResolver::getAttributeRepository)
            .filter(Objects::nonNull)
            .findFirst()
            .orElse(null);
    }
}

Attribute Merging Strategies

Attribute mergers determine how to combine attributes from multiple sources:

package org.apereo.cas.authentication.principal.merger;

import java.util.List;
import java.util.Map;

public interface AttributeMerger {
    Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,
                                            Map<String, List<Object>> toMerge);
}

Base Additive Attribute Merger

package org.apereo.cas.authentication.principal.merger;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public abstract class BaseAdditiveAttributeMerger implements AttributeMerger {
    
    public final Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,
                                                         Map<String, List<Object>> toMerge) {
        Map<String, List<Object>> results = new LinkedHashMap<>(toModify);
        
        for (Map.Entry<String, List<Object>> entry : toMerge.entrySet()) {
            String key = entry.getKey();
            List<Object> values = entry.getValue();
            
            if (results.containsKey(key)) {
                List<Object> existingValues = new ArrayList<>(results.get(key));
                List<Object> mergedValues = mergeValues(existingValues, values);
                results.put(key, mergedValues);
            } else {
                results.put(key, new ArrayList<>(values));
            }
        }
        
        return results;
    }
    
    protected abstract List<Object> mergeValues(List<Object> existing, List<Object> additional);
}

Multivalued Attribute Merger

Adds all values from both sources:

package org.apereo.cas.authentication.principal.merger;

import java.util.ArrayList;
import java.util.List;

public class MultivaluedAttributeMerger extends BaseAdditiveAttributeMerger {
    
    protected List<Object> mergeValues(List<Object> existing, List<Object> additional) {
        List<Object> merged = new ArrayList<>(existing);
        merged.addAll(additional);
        return merged;
    }
}

Non-colliding Attribute Adder

Only adds attributes that don't already exist:

package org.apereo.cas.authentication.principal.merger;

import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;

public class NoncollidingAttributeAdder implements AttributeMerger {
    
    public Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,
                                                   Map<String, List<Object>> toMerge) {
        Map<String, List<Object>> results = new LinkedHashMap<>(toModify);
        
        for (Map.Entry<String, List<Object>> entry : toMerge.entrySet()) {
            String key = entry.getKey();
            if (!results.containsKey(key)) {
                results.put(key, entry.getValue());
            }
        }
        
        return results;
    }
}

Replacing Attribute Adder

Replaces existing attributes with new values:

package org.apereo.cas.authentication.principal.merger;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ReplacingAttributeAdder implements AttributeMerger {
    
    public Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,
                                                   Map<String, List<Object>> toMerge) {
        Map<String, List<Object>> results = new LinkedHashMap<>(toModify);
        results.putAll(toMerge);
        return results;
    }
}

Return Changes Attribute Merger

Only returns the new/changed attributes:

package org.apereo.cas.authentication.principal.merger;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ReturnChangesAttributeMerger implements AttributeMerger {
    
    public Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,
                                                   Map<String, List<Object>> toMerge) {
        return new LinkedHashMap<>(toMerge);
    }
}

Return Original Attribute Merger

Always returns the original attributes unchanged:

package org.apereo.cas.authentication.principal.merger;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ReturnOriginalAttributeMerger implements AttributeMerger {
    
    public Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,
                                                   Map<String, List<Object>> toMerge) {
        return new LinkedHashMap<>(toModify);
    }
}

Principal Name Transformation

package org.apereo.cas.authentication.principal;

import org.apereo.cas.util.transforms.PrefixSuffixPrincipalNameTransformer;
import org.apereo.cas.util.transforms.RegexPrincipalNameTransformer;
import org.apereo.cas.util.transforms.UpperCasePrincipalNameTransformer;
import org.apereo.cas.util.transforms.LowerCasePrincipalNameTransformer;

public final class PrincipalNameTransformerUtils {
    
    public static PrincipalNameTransformer newPrefixSuffixTransformer(String prefix, String suffix) {
        return new PrefixSuffixPrincipalNameTransformer(prefix, suffix);
    }
    
    public static PrincipalNameTransformer newRegexTransformer(String pattern, String replacement) {
        return new RegexPrincipalNameTransformer(pattern, replacement);
    }
    
    public static PrincipalNameTransformer newUpperCaseTransformer() {
        return new UpperCasePrincipalNameTransformer();
    }
    
    public static PrincipalNameTransformer newLowerCaseTransformer() {
        return new LowerCasePrincipalNameTransformer();
    }
}

Resolution Execution Plan

package org.apereo.cas.authentication.principal;

import org.apereo.cas.authentication.AuthenticationHandler;
import java.util.Collection;

public interface PrincipalResolutionExecutionPlan {
    void registerPrincipalResolver(PrincipalResolver resolver);
    void registerPrincipalResolver(PrincipalResolver resolver, AuthenticationHandler handler);
    Collection<PrincipalResolver> getRegisteredPrincipalResolvers();
}

public class DefaultPrincipalResolutionExecutionPlan implements PrincipalResolutionExecutionPlan {
    private final Map<AuthenticationHandler, PrincipalResolver> resolverMap = new LinkedHashMap<>();
    private PrincipalResolver globalResolver;
    
    public void registerPrincipalResolver(PrincipalResolver resolver) {
        this.globalResolver = resolver;
    }
    
    public void registerPrincipalResolver(PrincipalResolver resolver, AuthenticationHandler handler) {
        resolverMap.put(handler, resolver);
    }
    
    public Collection<PrincipalResolver> getRegisteredPrincipalResolvers() {
        Collection<PrincipalResolver> resolvers = new ArrayList<>(resolverMap.values());
        if (globalResolver != null) {
            resolvers.add(globalResolver);
        }
        return resolvers;
    }
}

Configuration Examples

Spring Configuration

@Configuration
public class PrincipalResolutionConfiguration {
    
    @Bean
    public PrincipalFactory principalFactory() {
        return new DefaultPrincipalFactory();
    }
    
    @Bean
    public PrincipalElectionStrategy principalElectionStrategy() {
        return new DefaultPrincipalElectionStrategy();
    }
    
    @Bean
    public AttributeMerger attributeMerger() {
        return new MultivaluedAttributeMerger();
    }
    
    @Bean
    public PrincipalResolver echoingPrincipalResolver() {
        return new EchoingPrincipalResolver();
    }
}

Programmatic Usage

// Create principal with attributes
Map<String, List<Object>> attributes = Map.of(
    "email", List.of("user@example.com"),
    "roles", List.of("admin", "user"),
    "department", List.of("IT")
);

PrincipalFactory factory = new DefaultPrincipalFactory();
Principal principal = factory.createPrincipal("username", attributes);

// Merge attributes from multiple sources
AttributeMerger merger = new MultivaluedAttributeMerger();
Map<String, List<Object>> additionalAttrs = Map.of(
    "groups", List.of("developers", "admins"),
    "roles", List.of("manager")  // This will be merged with existing roles
);

Map<String, List<Object>> mergedAttrs = merger.mergeAttributes(
    principal.getAttributes(), additionalAttrs);

// Create election strategy chain
ChainingPrincipalElectionStrategy electionStrategy = new ChainingPrincipalElectionStrategy();
electionStrategy.addStrategy(new DefaultPrincipalElectionStrategy());

Principal resolution provides the foundation for user identity management in CAS, allowing flexible attribute resolution, merging strategies, and principal election to support complex enterprise requirements.

Install with Tessl CLI

npx tessl i tessl/maven-org-apereo-cas--cas-server-core-authentication-api

docs

adaptive-authentication.md

authentication-handlers.md

authentication-policies.md

credential-handling.md

index.md

password-policies.md

principal-resolution.md

tile.json