Core authentication API module for Apereo CAS providing fundamental authentication interfaces, implementations, and components for the CAS server infrastructure
—
Credentials represent the authentication information provided by users, such as usernames/passwords, certificates, tokens, or other authentication artifacts. The CAS authentication API provides a comprehensive framework for handling various credential types with validation, security, and extensibility features.
package org.apereo.cas.authentication;
import java.io.Serializable;
public interface Credential extends Serializable {
String getId();
CredentialMetadata getCredentialMetadata();
}package org.apereo.cas.authentication;
public interface MutableCredential extends Credential {
void setId(String id);
}package org.apereo.cas.authentication;
import java.time.ZonedDateTime;
import java.util.Map;
public interface CredentialMetadata {
String getId();
Map<String, Object> getProperties();
void addProperty(String name, Object value);
ZonedDateTime getAuthenticationDate();
void setAuthenticationDate(ZonedDateTime authenticationDate);
Class<? extends Credential> getCredentialClass();
}package org.apereo.cas.authentication.credential;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.CredentialMetadata;
import org.apereo.cas.authentication.metadata.BasicCredentialMetadata;
import org.springframework.binding.validation.ValidationContext;
public abstract class AbstractCredential implements Credential {
private CredentialMetadata credentialMetadata;
public CredentialMetadata getCredentialMetadata() {
if (credentialMetadata == null) {
this.credentialMetadata = new BasicCredentialMetadata(this);
}
return this.credentialMetadata;
}
public void setCredentialMetadata(CredentialMetadata credentialMetadata) {
this.credentialMetadata = credentialMetadata;
}
public boolean isValid() {
return StringUtils.isNotBlank(getId());
}
public void validate(ValidationContext context) {
// Default validation - subclasses can override
if (!isValid()) {
context.getMessageContext().addMessage(
new MessageBuilder()
.error()
.source(getClass().getSimpleName())
.defaultText("Invalid credential")
.code("credential.invalid")
.build());
}
}
}The most common credential type supporting username/password authentication:
package org.apereo.cas.authentication.credential;
import org.apereo.cas.authentication.MutableCredential;
import org.springframework.binding.validation.ValidationContext;
import jakarta.validation.constraints.Size;
import java.util.Map;
public class UsernamePasswordCredential extends AbstractCredential implements MutableCredential {
public static final String AUTHENTICATION_ATTRIBUTE_PASSWORD = "credential";
@Size(min = 1, message = "username.required")
private String username;
@Size(min = 1, message = "password.required")
private char[] password;
private String source;
private Map<String, Object> customFields = new LinkedHashMap<>();
// Constructors
public UsernamePasswordCredential() {}
public UsernamePasswordCredential(String username, String password) {
this.username = username;
assignPassword(StringUtils.defaultString(password));
}
public UsernamePasswordCredential(String username, char[] password,
String source, Map<String, Object> customFields) {
this.username = username;
this.password = password.clone();
this.source = source;
this.customFields = new HashMap<>(customFields);
}
// MutableCredential implementation
public String getId() {
return this.username;
}
public void setId(String id) {
this.username = id;
}
// Password handling
public char[] getPassword() {
return password != null ? password.clone() : null;
}
public void setPassword(char[] password) {
this.password = password != null ? password.clone() : null;
}
public String toPassword() {
return password != null ? new String(password) : null;
}
public void assignPassword(String password) {
if (password != null) {
this.password = password.toCharArray();
} else {
this.password = null;
}
}
// Properties
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public Map<String, Object> getCustomFields() { return customFields; }
public void setCustomFields(Map<String, Object> customFields) {
this.customFields = customFields;
}
// Validation
@Override
public void validate(ValidationContext context) {
super.validate(context);
MessageContext messageContext = context.getMessageContext();
if (!"submit".equalsIgnoreCase(context.getUserEvent()) ||
messageContext.hasErrorMessages()) {
return;
}
// Validate username
if (StringUtils.isBlank(username)) {
messageContext.addMessage(new MessageBuilder()
.error()
.source("username")
.defaultText("Username is required")
.code("username.required")
.build());
}
// Validate password
if (password == null || password.length == 0) {
messageContext.addMessage(new MessageBuilder()
.error()
.source("password")
.defaultText("Password is required")
.code("password.required")
.build());
}
}
@Override
public boolean equals(Object obj) {
if (obj == null || !this.getClass().equals(obj.getClass())) {
return false;
}
UsernamePasswordCredential other = (UsernamePasswordCredential) obj;
return Objects.equals(this.username, other.username);
}
@Override
public int hashCode() {
return Objects.hashCode(this.username);
}
@Override
public String toString() {
return String.format("UsernamePasswordCredential[username=%s, source=%s]",
username, source);
}
}Extended credential that supports "Remember Me" functionality:
package org.apereo.cas.authentication.credential;
import org.apereo.cas.authentication.RememberMeCredential;
public class RememberMeUsernamePasswordCredential extends UsernamePasswordCredential
implements RememberMeCredential {
public static final String AUTHENTICATION_ATTRIBUTE_REMEMBER_ME = "rememberMe";
private boolean rememberMe;
// Constructors
public RememberMeUsernamePasswordCredential() {
super();
}
public RememberMeUsernamePasswordCredential(String username, String password,
boolean rememberMe) {
super(username, password);
this.rememberMe = rememberMe;
}
// RememberMeCredential implementation
public boolean isRememberMe() {
return this.rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
@Override
public String toString() {
return String.format("RememberMeUsernamePasswordCredential[username=%s, rememberMe=%s]",
getUsername(), rememberMe);
}
}For OTP-based authentication systems:
package org.apereo.cas.authentication.credential;
import jakarta.validation.constraints.Size;
public class OneTimePasswordCredential extends AbstractCredential {
@Size(min = 1, message = "oneTimePassword.required")
private String password;
private String id;
// Constructors
public OneTimePasswordCredential() {}
public OneTimePasswordCredential(String id, String password) {
this.id = id;
this.password = password;
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
@Override
public void validate(ValidationContext context) {
super.validate(context);
MessageContext messageContext = context.getMessageContext();
if (StringUtils.isBlank(id)) {
messageContext.addMessage(new MessageBuilder()
.error()
.source("id")
.defaultText("ID is required")
.code("id.required")
.build());
}
if (StringUtils.isBlank(password)) {
messageContext.addMessage(new MessageBuilder()
.error()
.source("password")
.defaultText("One-time password is required")
.code("oneTimePassword.required")
.build());
}
}
}For single-use token authentication:
package org.apereo.cas.authentication.credential;
import jakarta.validation.constraints.Size;
public class OneTimeTokenCredential extends AbstractCredential {
@Size(min = 1, message = "token.required")
private String token;
private String id;
// Constructors
public OneTimeTokenCredential() {}
public OneTimeTokenCredential(String id, String token) {
this.id = id;
this.token = token;
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
@Override
public void validate(ValidationContext context) {
super.validate(context);
MessageContext messageContext = context.getMessageContext();
if (StringUtils.isBlank(token)) {
messageContext.addMessage(new MessageBuilder()
.error()
.source("token")
.defaultText("Token is required")
.code("token.required")
.build());
}
}
}Simple credential with just an identifier:
package org.apereo.cas.authentication.credential;
public class BasicIdentifiableCredential extends AbstractCredential {
private String id;
// Constructors
public BasicIdentifiableCredential() {}
public BasicIdentifiableCredential(String id) {
this.id = id;
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
@Override
public String toString() {
return String.format("BasicIdentifiableCredential[id=%s]", id);
}
}Credential for HTTP service callback authentication:
package org.apereo.cas.authentication.credential;
import org.apereo.cas.services.RegisteredService;
import java.net.URL;
public class HttpBasedServiceCredential extends AbstractCredential {
private final URL callbackUrl;
private final RegisteredService registeredService;
public HttpBasedServiceCredential(URL callbackUrl, RegisteredService registeredService) {
this.callbackUrl = callbackUrl;
this.registeredService = registeredService;
}
public String getId() {
return this.callbackUrl != null ? this.callbackUrl.toExternalForm() : null;
}
public URL getCallbackUrl() { return callbackUrl; }
public RegisteredService getService() { return registeredService; }
@Override
public String toString() {
return String.format("HttpBasedServiceCredential[callbackUrl=%s, service=%s]",
callbackUrl, registeredService != null ? registeredService.getName() : "null");
}
}// Validate credential in Spring WebFlow context
ValidationContext context = new DefaultValidationContext(requestContext, "credentialValidation");
UsernamePasswordCredential credential = new UsernamePasswordCredential("user", "pass");
credential.validate(context);
if (context.getMessageContext().hasErrorMessages()) {
// Handle validation errors
for (Message message : context.getMessageContext().getAllMessages()) {
System.out.println("Validation error: " + message.getText());
}
}public class CustomUsernamePasswordCredential extends UsernamePasswordCredential {
@Override
public void validate(ValidationContext context) {
super.validate(context);
MessageContext messageContext = context.getMessageContext();
// Custom username validation
String username = getUsername();
if (username != null && username.contains("@")) {
if (!isValidEmail(username)) {
messageContext.addMessage(new MessageBuilder()
.error()
.source("username")
.defaultText("Invalid email format")
.code("username.invalid.email")
.build());
}
}
// Password complexity validation
String password = toPassword();
if (password != null && !isPasswordComplex(password)) {
messageContext.addMessage(new MessageBuilder()
.error()
.source("password")
.defaultText("Password does not meet complexity requirements")
.code("password.complexity.insufficient")
.build());
}
}
private boolean isValidEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
private boolean isPasswordComplex(String password) {
return password.length() >= 8 &&
password.matches(".*[A-Z].*") &&
password.matches(".*[a-z].*") &&
password.matches(".*[0-9].*") &&
password.matches(".*[!@#$%^&*()].*");
}
}public class SecureUsernamePasswordCredential extends UsernamePasswordCredential {
@Override
public void setPassword(char[] password) {
super.setPassword(password);
// Clear the input parameter for security
if (password != null) {
Arrays.fill(password, '\0');
}
}
@Override
public void assignPassword(String password) {
super.assignPassword(password);
// Note: String parameter cannot be cleared,
// prefer char[] methods for security
}
public void clearPassword() {
char[] password = getPassword();
if (password != null) {
Arrays.fill(password, '\0');
}
setPassword(null);
}
}public class EncryptedUsernamePasswordCredential extends UsernamePasswordCredential {
private final Cipher cipher;
public EncryptedUsernamePasswordCredential(Cipher cipher) {
this.cipher = cipher;
}
@Override
public void setPassword(char[] password) {
if (password != null && cipher != null) {
try {
byte[] encrypted = cipher.doFinal(new String(password).getBytes());
String encodedPassword = Base64.getEncoder().encodeToString(encrypted);
super.assignPassword(encodedPassword);
// Clear original password
Arrays.fill(password, '\0');
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt password", e);
}
} else {
super.setPassword(password);
}
}
public char[] getDecryptedPassword() throws Exception {
String encryptedPassword = toPassword();
if (encryptedPassword != null && cipher != null) {
byte[] encrypted = Base64.getDecoder().decode(encryptedPassword);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted).toCharArray();
}
return getPassword();
}
}package org.apereo.cas.authentication.metadata;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.CredentialMetadata;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class BasicCredentialMetadata implements CredentialMetadata {
private final String id;
private final Class<? extends Credential> credentialClass;
private final Map<String, Object> properties = new ConcurrentHashMap<>();
private ZonedDateTime authenticationDate;
public BasicCredentialMetadata(Credential credential) {
this.id = credential.getId();
this.credentialClass = credential.getClass();
this.authenticationDate = ZonedDateTime.now();
}
public String getId() { return id; }
public Class<? extends Credential> getCredentialClass() { return credentialClass; }
public Map<String, Object> getProperties() { return properties; }
public void addProperty(String name, Object value) {
properties.put(name, value);
}
public ZonedDateTime getAuthenticationDate() { return authenticationDate; }
public void setAuthenticationDate(ZonedDateTime authenticationDate) {
this.authenticationDate = authenticationDate;
}
}package org.apereo.cas.authentication;
public interface RememberMeCredential {
String AUTHENTICATION_ATTRIBUTE_REMEMBER_ME = "rememberMe";
boolean isRememberMe();
void setRememberMe(boolean rememberMe);
}// Create credential selection predicates
Predicate<Credential> usernamePasswordPredicate =
credential -> credential instanceof UsernamePasswordCredential;
Predicate<Credential> tokenCredentialPredicate =
credential -> credential instanceof OneTimeTokenCredential;
Predicate<Credential> sourcePredicate = credential -> {
if (credential instanceof UsernamePasswordCredential) {
UsernamePasswordCredential upc = (UsernamePasswordCredential) credential;
return "LDAP".equals(upc.getSource());
}
return false;
};
// Using predicates for handler selection
public class ConditionalAuthenticationHandler extends AbstractAuthenticationHandler {
private final Predicate<Credential> credentialSelector;
public ConditionalAuthenticationHandler(Predicate<Credential> credentialSelector) {
this.credentialSelector = credentialSelector;
}
@Override
public boolean supports(Credential credential) {
return credentialSelector.test(credential);
}
}@Configuration
public class CredentialConfiguration {
@Bean
public Predicate<Credential> usernamePasswordCredentialSelector() {
return credential -> credential instanceof UsernamePasswordCredential;
}
@Bean
public Predicate<Credential> rememberMeCredentialSelector() {
return credential -> credential instanceof RememberMeCredential &&
((RememberMeCredential) credential).isRememberMe();
}
@Bean
public MessageSource credentialValidationMessageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("credential-validation-messages");
return messageSource;
}
}// Create and validate credentials
UsernamePasswordCredential credential = new UsernamePasswordCredential("user", "password");
credential.setSource("LDAP");
credential.getCustomFields().put("department", "IT");
// Validate credential
ValidationContext validationContext = new DefaultValidationContext();
credential.validate(validationContext);
if (!validationContext.getMessageContext().hasErrorMessages()) {
// Credential is valid, proceed with authentication
AuthenticationHandler handler = getAppropriateHandler(credential);
AuthenticationHandlerExecutionResult result = handler.authenticate(credential);
}
// Working with remember-me credentials
RememberMeUsernamePasswordCredential rememberMeCredential =
new RememberMeUsernamePasswordCredential("user", "password", true);
// Check remember-me status
if (rememberMeCredential.isRememberMe()) {
// Apply remember-me logic
}
// Secure password handling
char[] password = "securePassword".toCharArray();
try {
UsernamePasswordCredential secureCredential =
new UsernamePasswordCredential("user", password);
// Use credential
} finally {
// Always clear sensitive data
Arrays.fill(password, '\0');
}Credential handling provides the foundation for secure authentication data management, supporting various credential types, validation mechanisms, and security practices essential for enterprise authentication systems.
Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-core-authentication-api