Spring Security support for Apereo's Central Authentication Service (CAS) enabling Single Sign-On authentication
—
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.
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);
}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;
}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
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;
}
}@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;
}@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 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();// CAS attribute: role = ["admin", "user"]
// Result: [ROLE_ADMIN, ROLE_USER] (if convertToUpperCase = true)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);
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-security--spring-security-cas