CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-security--spring-security-cas

Spring Security support for Apereo's Central Authentication Service (CAS) enabling Single Sign-On authentication

Pending
Overview
Eval results
Files

user-details.mddocs/

User Details

Specialized user details services for extracting user information and authorities from CAS assertions. These services integrate CAS user attributes with Spring Security's user details framework to populate user authorities and profile information.

Capabilities

Abstract CAS Assertion User Details Service

Base class for implementing user details services that load user information from CAS assertions rather than traditional username lookup.

/**
 * Base class for user details services that construct UserDetails from CAS assertions.
 * Provides framework for loading user information from CAS assertion attributes.
 */
public abstract class AbstractCasAssertionUserDetailsService 
    implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
    
    /**
     * Loads user details from CAS assertion authentication token.
     * This method is final and delegates to the abstract loadUserDetails(Assertion) method.
     * @param token authentication token containing CAS assertion
     * @return UserDetails populated from CAS assertion
     */
    public final UserDetails loadUserDetails(CasAssertionAuthenticationToken token);
    
    /**
     * Loads user details from CAS assertion.
     * Implementations should extract user information and authorities from the assertion.
     * @param assertion CAS assertion containing user attributes
     * @return UserDetails object populated from assertion
     */
    protected abstract UserDetails loadUserDetails(Assertion assertion);
}

Granted Authority from Assertion Attributes User Details Service

Concrete implementation that creates user authorities from specific CAS assertion attributes.

/**
 * User details service that extracts granted authorities from specified CAS assertion attributes.
 * Creates GrantedAuthority objects from assertion attribute values.
 */
public final class GrantedAuthorityFromAssertionAttributesUserDetailsService 
    extends AbstractCasAssertionUserDetailsService {
    
    /**
     * Creates service that extracts authorities from specified assertion attributes.
     * @param attributes array of assertion attribute names to use for authorities
     * @throws IllegalArgumentException if attributes array is null or empty
     */
    public GrantedAuthorityFromAssertionAttributesUserDetailsService(String[] attributes);
    
    /**
     * Sets whether to convert attribute values to uppercase before creating authorities.
     * Useful for normalizing role names (e.g., "admin" becomes "ADMIN").
     * @param convertToUpperCase true to convert authorities to uppercase
     */
    public void setConvertToUpperCase(boolean convertToUpperCase);
    
    /**
     * Loads user details from CAS assertion by extracting authorities from configured attributes.
     * @param assertion CAS assertion containing user attributes
     * @return UserDetails with authorities populated from assertion attributes
     */
    protected UserDetails loadUserDetails(Assertion assertion);
}

Usage Example:

@Bean
public GrantedAuthorityFromAssertionAttributesUserDetailsService casUserDetailsService() {
    String[] attributes = {"role", "group", "department"};
    GrantedAuthorityFromAssertionAttributesUserDetailsService service = 
        new GrantedAuthorityFromAssertionAttributesUserDetailsService(attributes);
    service.setConvertToUpperCase(true);
    return service;
}

Custom User Details Service Implementation

Example of creating a custom user details service that extends the abstract base:

public class CustomCasUserDetailsService extends AbstractCasAssertionUserDetailsService {
    
    private UserRepository userRepository;
    
    public CustomCasUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    protected UserDetails loadUserDetails(Assertion assertion) {
        String username = assertion.getPrincipal().getName();
        Map<String, Object> attributes = assertion.getPrincipal().getAttributes();
        
        // Load user from database
        User user = userRepository.findByUsername(username);
        if (user == null) {
            // Handle user not found - could return a default user or throw a runtime exception
            throw new IllegalStateException("User not found: " + username);
        }
        
        // Extract roles from CAS attributes
        List<String> roles = (List<String>) attributes.get("roles");
        List<GrantedAuthority> authorities = roles.stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
            .collect(Collectors.toList());
        
        // Create UserDetails with additional attributes
        return User.builder()
            .username(username)
            .password("") // Password not needed for CAS
            .authorities(authorities)
            .accountNonExpired(true)
            .accountNonLocked(true)
            .credentialsNonExpired(true)
            .disabled(false)
            .build();
    }
}

Configuration Examples

Basic Attribute-Based Authorities

@Configuration
public class CasUserDetailsConfig {
    
    @Bean
    public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> casUserDetailsService() {
        // Extract authorities from 'role' attribute
        String[] attributes = {"role"};
        GrantedAuthorityFromAssertionAttributesUserDetailsService service = 
            new GrantedAuthorityFromAssertionAttributesUserDetailsService(attributes);
        service.setConvertToUpperCase(true);
        return service;
    }
    
    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        provider.setAuthenticationUserDetailsService(casUserDetailsService());
        provider.setKey("cas-authentication-provider");
        return provider;
    }
}

Multiple Attribute Sources

@Bean
public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> multiAttributeUserDetailsService() {
    // Extract authorities from multiple attributes
    String[] attributes = {"role", "group", "department", "permission"};
    GrantedAuthorityFromAssertionAttributesUserDetailsService service = 
        new GrantedAuthorityFromAssertionAttributesUserDetailsService(attributes);
    service.setConvertToUpperCase(true);
    return service;
}

Hybrid Approach with UserDetailsService

@Configuration
public class HybridUserDetailsConfig {
    
    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        
        // Use traditional UserDetailsService for basic user loading
        provider.setUserDetailsService(userDetailsService());
        
        // Also set assertion-based service for additional attributes
        provider.setAuthenticationUserDetailsService(casAssertionUserDetailsService());
        
        provider.setKey("cas-authentication-provider");
        return provider;
    }
    
    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserDetailsService(); // Your existing implementation
    }
    
    @Bean
    public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> casAssertionUserDetailsService() {
        String[] attributes = {"additionalRole"};
        return new GrantedAuthorityFromAssertionAttributesUserDetailsService(attributes);
    }
}

CAS Assertion Structure

CAS assertions typically contain:

// Example CAS assertion structure
Assertion assertion = ...;

// Principal information
AttributePrincipal principal = assertion.getPrincipal();
String username = principal.getName(); // Primary identifier

// User attributes from CAS server
Map<String, Object> attributes = principal.getAttributes();
List<String> roles = (List<String>) attributes.get("role");
String email = (String) attributes.get("email");
String fullName = (String) attributes.get("displayName");
String department = (String) attributes.get("department");

// Authentication metadata
Date validFromDate = assertion.getValidFromDate();
Date validUntilDate = assertion.getValidUntilDate();
Map<String, Object> authenticationAttributes = assertion.getAuthenticationAttributes();

Authority Mapping Strategies

Simple Role Mapping

// CAS attribute: role = ["admin", "user"]
// Result: [ROLE_ADMIN, ROLE_USER] (if convertToUpperCase = true)

Prefix-Based Mapping

public class PrefixedAuthorityUserDetailsService extends AbstractCasAssertionUserDetailsService {
    
    @Override
    protected UserDetails loadUserDetails(Assertion assertion) {
        String username = assertion.getPrincipal().getName();
        Map<String, Object> attributes = assertion.getPrincipal().getAttributes();
        
        List<GrantedAuthority> authorities = new ArrayList<>();
        
        // Add role-based authorities
        List<String> roles = (List<String>) attributes.get("role");
        if (roles != null) {
            roles.forEach(role -> authorities.add(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())));
        }
        
        // Add permission-based authorities
        List<String> permissions = (List<String>) attributes.get("permission");
        if (permissions != null) {
            permissions.forEach(perm -> authorities.add(new SimpleGrantedAuthority("PERM_" + perm.toUpperCase())));
        }
        
        return new User(username, "", authorities);
    }
}

Integration Notes

  • Attribute Configuration: CAS server must be configured to release required attributes
  • Authority Mapping: Plan authority structure to align with Spring Security's role-based access control
  • Performance: Consider caching user details if assertion processing is expensive
  • Fallback Strategy: Implement fallback when required attributes are missing
  • Testing: Test with various assertion structures and attribute combinations

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-security--spring-security-cas

docs

authentication.md

configuration.md

index.md

json-serialization.md

ticket-caching.md

user-details.md

web-integration.md

tile.json