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

password-policies.mddocs/

Password Policies

Password policies in CAS provide comprehensive password management capabilities including expiration warnings, complexity validation, account state handling, and password encoding. The authentication API supports configurable password policy enforcement that integrates with various authentication sources and directory services.

Core Password Policy Interface

package org.apereo.cas.authentication;

import org.apereo.cas.authentication.principal.Principal;
import java.util.List;

public interface AuthenticationPasswordPolicyHandlingStrategy<AuthnResponse, PasswordPolicyConfiguration> {
    List<MessageDescriptor> handle(AuthnResponse response, PasswordPolicyConfiguration configuration) 
        throws Throwable;
}

Password Policy Context

package org.apereo.cas.authentication.support.password;

import org.apereo.cas.authentication.AuthenticationAccountStateHandler;
import org.apereo.cas.configuration.model.core.authentication.PasswordPolicyProperties;

public class PasswordPolicyContext {
    private AuthenticationAccountStateHandler accountStateHandler;
    private boolean alwaysDisplayPasswordExpirationWarning;
    private int passwordWarningNumberOfDays = 30;
    private int loginFailures = 5;
    
    // Constructors
    public PasswordPolicyContext() {}
    
    public PasswordPolicyContext(int passwordWarningNumberOfDays) {
        this.passwordWarningNumberOfDays = passwordWarningNumberOfDays;
    }
    
    public PasswordPolicyContext(AuthenticationAccountStateHandler accountStateHandler,
                               boolean alwaysDisplayPasswordExpirationWarning,
                               int passwordWarningNumberOfDays,
                               int loginFailures) {
        this.accountStateHandler = accountStateHandler;
        this.alwaysDisplayPasswordExpirationWarning = alwaysDisplayPasswordExpirationWarning;
        this.passwordWarningNumberOfDays = passwordWarningNumberOfDays;
        this.loginFailures = loginFailures;
    }
    
    public PasswordPolicyContext(PasswordPolicyProperties props) {
        this(null, props.isWarnAll(), props.getWarningDays(), props.getLoginFailures());
    }
    
    // Getters and setters
    public AuthenticationAccountStateHandler getAccountStateHandler() { return accountStateHandler; }
    public void setAccountStateHandler(AuthenticationAccountStateHandler accountStateHandler) { 
        this.accountStateHandler = accountStateHandler; 
    }
    
    public boolean isAlwaysDisplayPasswordExpirationWarning() { 
        return alwaysDisplayPasswordExpirationWarning; 
    }
    public void setAlwaysDisplayPasswordExpirationWarning(boolean alwaysDisplayPasswordExpirationWarning) { 
        this.alwaysDisplayPasswordExpirationWarning = alwaysDisplayPasswordExpirationWarning; 
    }
    
    public int getPasswordWarningNumberOfDays() { return passwordWarningNumberOfDays; }
    public void setPasswordWarningNumberOfDays(int passwordWarningNumberOfDays) { 
        this.passwordWarningNumberOfDays = passwordWarningNumberOfDays; 
    }
    
    public int getLoginFailures() { return loginFailures; }
    public void setLoginFailures(int loginFailures) { this.loginFailures = loginFailures; }
}

Message Descriptors

Password Expiring Warning Message

package org.apereo.cas.authentication.support.password;

import org.apereo.cas.authentication.MessageDescriptor;
import java.time.ZonedDateTime;

public class PasswordExpiringWarningMessageDescriptor implements MessageDescriptor {
    
    public static final String DEFAULT_CODE = "password.expiration.warning";
    
    private final String code;
    private final String defaultMessage;
    private final int daysToExpiration;
    private final ZonedDateTime expirationDate;
    
    public PasswordExpiringWarningMessageDescriptor(String defaultMessage, 
                                                  int daysToExpiration) {
        this(DEFAULT_CODE, defaultMessage, daysToExpiration, null);
    }
    
    public PasswordExpiringWarningMessageDescriptor(String code, 
                                                  String defaultMessage,
                                                  int daysToExpiration,
                                                  ZonedDateTime expirationDate) {
        this.code = code;
        this.defaultMessage = defaultMessage;
        this.daysToExpiration = daysToExpiration;
        this.expirationDate = expirationDate;
    }
    
    public String getCode() { return code; }
    
    public String getDefaultMessage() { return defaultMessage; }
    
    public Object[] getParams() {
        return new Object[]{daysToExpiration, expirationDate};
    }
    
    public int getDaysToExpiration() { return daysToExpiration; }
    
    public ZonedDateTime getExpirationDate() { return expirationDate; }
}

Password Policy Handling Strategies

Default Password Policy Handling Strategy

package org.apereo.cas.authentication.support.password;

import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.MessageDescriptor;
import java.util.ArrayList;
import java.util.List;

public class DefaultPasswordPolicyHandlingStrategy<AuthnResponse> 
    implements AuthenticationPasswordPolicyHandlingStrategy<AuthnResponse, PasswordPolicyContext> {
    
    @Override
    public List<MessageDescriptor> handle(AuthnResponse response, 
                                        PasswordPolicyContext configuration) throws Throwable {
        
        if (configuration == null) {
            return new ArrayList<>();
        }
        
        AuthenticationAccountStateHandler accountStateHandler = configuration.getAccountStateHandler();
        if (accountStateHandler == null) {
            return new ArrayList<>();
        }
        
        return accountStateHandler.handle(response, configuration);
    }
}

Reject Result Code Password Policy Strategy

Strategy that handles specific LDAP result codes for password policy enforcement:

package org.apereo.cas.authentication.support.password;

import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.MessageDescriptor;
import org.apereo.cas.authentication.exceptions.AccountDisabledException;
import org.apereo.cas.authentication.exceptions.AccountPasswordMustChangeException;
import org.apereo.cas.authentication.exceptions.InvalidLoginLocationException;
import org.apereo.cas.authentication.exceptions.InvalidLoginTimeException;
import java.util.List;
import java.util.Map;

public class RejectResultCodePasswordPolicyHandlingStrategy 
    implements AuthenticationPasswordPolicyHandlingStrategy<Object, PasswordPolicyContext> {
    
    private static final Map<String, Class<? extends Exception>> RESULT_CODE_EXCEPTIONS = Map.of(
        "ACCOUNT_DISABLED", AccountDisabledException.class,
        "PASSWORD_MUST_CHANGE", AccountPasswordMustChangeException.class,
        "INVALID_LOGIN_LOCATION", InvalidLoginLocationException.class,
        "INVALID_LOGIN_TIME", InvalidLoginTimeException.class
    );
    
    @Override
    public List<MessageDescriptor> handle(Object response, PasswordPolicyContext configuration) 
        throws Throwable {
        
        String resultCode = extractResultCode(response);
        
        if (resultCode != null && RESULT_CODE_EXCEPTIONS.containsKey(resultCode)) {
            Class<? extends Exception> exceptionClass = RESULT_CODE_EXCEPTIONS.get(resultCode);
            Exception exception = exceptionClass.getDeclaredConstructor(String.class)
                .newInstance("Password policy violation: " + resultCode);
            throw exception;
        }
        
        return List.of();
    }
    
    private String extractResultCode(Object response) {
        // Extract result code from authentication response
        // Implementation depends on the specific authentication backend
        return null;
    }
}

Groovy Password Policy Handling Strategy

Scriptable password policy handler using Groovy:

package org.apereo.cas.authentication.support.password;

import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.MessageDescriptor;
import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;
import java.util.ArrayList;
import java.util.List;

public class GroovyPasswordPolicyHandlingStrategy 
    implements AuthenticationPasswordPolicyHandlingStrategy<Object, PasswordPolicyContext> {
    
    private final ExecutableCompiledGroovyScript watchableScript;
    
    public GroovyPasswordPolicyHandlingStrategy(ExecutableCompiledGroovyScript watchableScript) {
        this.watchableScript = watchableScript;
    }
    
    @Override
    public List<MessageDescriptor> handle(Object response, PasswordPolicyContext configuration) 
        throws Throwable {
        
        Object result = watchableScript.execute(response, configuration, List.class);
        
        if (result instanceof List) {
            return (List<MessageDescriptor>) result;
        }
        
        return new ArrayList<>();
    }
}

Account State Handlers

package org.apereo.cas.authentication;

import org.apereo.cas.authentication.support.password.PasswordPolicyContext;
import java.util.List;

public interface AuthenticationAccountStateHandler<Response, Configuration> {
    List<MessageDescriptor> handle(Response response, Configuration configuration) throws Exception;
    
    boolean supports(Response response);
}

LDAP Account State Handler

package org.apereo.cas.authentication.support.password;

import org.apereo.cas.authentication.AuthenticationAccountStateHandler;
import org.apereo.cas.authentication.MessageDescriptor;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;

public class LdapPasswordPolicyAccountStateHandler 
    implements AuthenticationAccountStateHandler<LdapAuthenticationResult, PasswordPolicyContext> {
    
    @Override
    public List<MessageDescriptor> handle(LdapAuthenticationResult response, 
                                        PasswordPolicyContext configuration) throws Exception {
        
        List<MessageDescriptor> messages = new ArrayList<>();
        
        // Check password expiration
        LocalDateTime expirationDate = response.getPasswordExpirationDate();
        if (expirationDate != null) {
            long daysToExpiration = ChronoUnit.DAYS.between(LocalDateTime.now(), expirationDate);
            
            if (daysToExpiration <= configuration.getPasswordWarningNumberOfDays()) {
                messages.add(new PasswordExpiringWarningMessageDescriptor(
                    "Your password expires in " + daysToExpiration + " days",
                    (int) daysToExpiration));
            }
        }
        
        // Check account locked status
        if (response.isAccountLocked()) {
            throw new AccountLockedException("Account is locked");
        }
        
        // Check if password must be changed
        if (response.isPasswordMustChange()) {
            throw new AccountPasswordMustChangeException("Password must be changed");
        }
        
        // Check login failures
        int failureCount = response.getLoginFailureCount();
        if (failureCount >= configuration.getLoginFailures()) {
            throw new AccountLockedException("Too many login failures: " + failureCount);
        }
        
        return messages;
    }
    
    @Override
    public boolean supports(LdapAuthenticationResult response) {
        return response != null;
    }
}

Password Encoding

Password Encoder Interface

package org.springframework.security.crypto.password;

public interface PasswordEncoder {
    String encode(CharSequence rawPassword);
    boolean matches(CharSequence rawPassword, String encodedPassword);
    default boolean upgradeEncoding(String encodedPassword) { return false; }
}

Password Encoder Utilities

package org.apereo.cas.authentication.support.password;

import org.apereo.cas.configuration.model.core.authentication.PasswordEncoderProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;

public final class PasswordEncoderUtils {
    
    private PasswordEncoderUtils() {}
    
    public static PasswordEncoder newPasswordEncoder(PasswordEncoderProperties properties,
                                                   ApplicationContext applicationContext) {
        
        String type = properties.getType();
        
        switch (type.toLowerCase()) {
            case "bcrypt":
                return new BCryptPasswordEncoder(properties.getStrength());
                
            case "scrypt":
                return new SCryptPasswordEncoder(
                    properties.getCpuCost(),
                    properties.getMemoryCost(), 
                    properties.getParallelization(),
                    properties.getKeyLength(),
                    properties.getSaltLength());
                    
            case "argon2":
                return new Argon2PasswordEncoder(
                    properties.getSaltLength(),
                    properties.getHashLength(),
                    properties.getParallelism(),
                    properties.getMemory(),
                    properties.getIterations());
                    
            case "pbkdf2":
                return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
                
            case "noop":
                return NoOpPasswordEncoder.getInstance();
                
            case "groovy":
                return new GroovyPasswordEncoder(properties, applicationContext);
                
            default:
                throw new IllegalArgumentException("Unsupported password encoder type: " + type);
        }
    }
}

Groovy Password Encoder

Custom password encoder using Groovy scripts:

package org.apereo.cas.authentication.support.password;

import org.apereo.cas.configuration.model.core.authentication.PasswordEncoderProperties;
import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;
import org.springframework.context.ApplicationContext;
import org.springframework.security.crypto.password.PasswordEncoder;

public class GroovyPasswordEncoder implements PasswordEncoder {
    
    private final ExecutableCompiledGroovyScript encodeScript;
    private final ExecutableCompiledGroovyScript matchesScript;
    
    public GroovyPasswordEncoder(PasswordEncoderProperties properties,
                               ApplicationContext applicationContext) {
        
        this.encodeScript = ExecutableCompiledGroovyScript.getExecutableCompiledGroovyScript(
            properties.getEncodeScript(), applicationContext);
            
        this.matchesScript = ExecutableCompiledGroovyScript.getExecutableCompiledGroovyScript(
            properties.getMatchesScript(), applicationContext);
    }
    
    @Override
    public String encode(CharSequence rawPassword) {
        Object result = encodeScript.execute(rawPassword.toString(), String.class);
        return result != null ? result.toString() : null;
    }
    
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        Object result = matchesScript.execute(rawPassword.toString(), encodedPassword, Boolean.class);
        return result instanceof Boolean ? (Boolean) result : false;
    }
    
    @Override
    public boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

Password Policy Configuration

Configuration Properties

package org.apereo.cas.configuration.model.core.authentication;

import java.io.Serializable;

public class PasswordPolicyProperties implements Serializable {
    
    private boolean enabled = false;
    private boolean warnAll = false;
    private boolean displayWarningOnFailedLogin = true;
    private boolean displayWarningOnSuccessfulLogin = true;
    private int warningDays = 30;
    private int loginFailures = 5;
    private String type = "DEFAULT";
    private String groovyScript;
    private String endpoint;
    
    // Account state handling
    private boolean accountStateHandlingEnabled = true;
    private String accountStateHandler = "DEFAULT";
    
    // Password encoding
    private PasswordEncoderProperties passwordEncoder = new PasswordEncoderProperties();
    
    // Getters and setters
    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
    
    public boolean isWarnAll() { return warnAll; }
    public void setWarnAll(boolean warnAll) { this.warnAll = warnAll; }
    
    public boolean isDisplayWarningOnFailedLogin() { return displayWarningOnFailedLogin; }
    public void setDisplayWarningOnFailedLogin(boolean displayWarningOnFailedLogin) { 
        this.displayWarningOnFailedLogin = displayWarningOnFailedLogin; 
    }
    
    public boolean isDisplayWarningOnSuccessfulLogin() { return displayWarningOnSuccessfulLogin; }
    public void setDisplayWarningOnSuccessfulLogin(boolean displayWarningOnSuccessfulLogin) { 
        this.displayWarningOnSuccessfulLogin = displayWarningOnSuccessfulLogin; 
    }
    
    public int getWarningDays() { return warningDays; }
    public void setWarningDays(int warningDays) { this.warningDays = warningDays; }
    
    public int getLoginFailures() { return loginFailures; }
    public void setLoginFailures(int loginFailures) { this.loginFailures = loginFailures; }
    
    // Additional getters and setters...
}

Password Encoder Properties

package org.apereo.cas.configuration.model.core.authentication;

public class PasswordEncoderProperties implements Serializable {
    
    private String type = "NONE";
    private String characterEncoding = "UTF-8";
    private String encodingAlgorithm = "SHA-256";
    private int strength = 10;
    private boolean secret = false;
    
    // BCrypt specific
    private int cost = 10;
    
    // SCrypt specific
    private int cpuCost = 16384;
    private int memoryCost = 8;
    private int parallelization = 1;
    private int keyLength = 32;
    private int saltLength = 16;
    
    // Argon2 specific
    private int hashLength = 32;
    private int parallelism = 1;
    private int memory = 4096;
    private int iterations = 3;
    
    // Groovy specific
    private String encodeScript;
    private String matchesScript;
    
    // Getters and setters
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
    
    public int getStrength() { return strength; }
    public void setStrength(int strength) { this.strength = strength; }
    
    // Additional getters and setters...
}

Integration Examples

Spring Configuration

@Configuration
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class PasswordPolicyConfiguration {
    
    @Bean
    public PasswordPolicyContext passwordPolicyContext(CasConfigurationProperties casProperties) {
        PasswordPolicyProperties props = casProperties.getAuthn().getPasswordPolicy();
        return new PasswordPolicyContext(props);
    }
    
    @Bean
    public AuthenticationPasswordPolicyHandlingStrategy passwordPolicyHandlingStrategy() {
        return new DefaultPasswordPolicyHandlingStrategy<>();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder(CasConfigurationProperties casProperties,
                                         ApplicationContext applicationContext) {
        PasswordEncoderProperties props = casProperties.getAuthn().getPasswordEncoder();
        return PasswordEncoderUtils.newPasswordEncoder(props, applicationContext);
    }
    
    @Bean
    public AuthenticationAccountStateHandler accountStateHandler() {
        return new LdapPasswordPolicyAccountStateHandler();
    }
}

Programmatic Usage

// Create password policy context
PasswordPolicyContext policyContext = new PasswordPolicyContext();
policyContext.setPasswordWarningNumberOfDays(30);
policyContext.setLoginFailures(5);
policyContext.setAlwaysDisplayPasswordExpirationWarning(false);

// Set up account state handler
AuthenticationAccountStateHandler accountStateHandler = new LdapPasswordPolicyAccountStateHandler();
policyContext.setAccountStateHandler(accountStateHandler);

// Create password policy handling strategy
AuthenticationPasswordPolicyHandlingStrategy strategy = new DefaultPasswordPolicyHandlingStrategy<>();

// Handle password policy during authentication
try {
    List<MessageDescriptor> messages = strategy.handle(authenticationResponse, policyContext);
    
    for (MessageDescriptor message : messages) {
        if (message instanceof PasswordExpiringWarningMessageDescriptor) {
            PasswordExpiringWarningMessageDescriptor warning = 
                (PasswordExpiringWarningMessageDescriptor) message;
            System.out.println("Password expires in " + warning.getDaysToExpiration() + " days");
        }
    }
} catch (AccountPasswordMustChangeException e) {
    // Redirect user to password change form
    System.out.println("Password must be changed");
} catch (AccountLockedException e) {
    // Display account locked message
    System.out.println("Account is locked");
}

// Password encoding example
PasswordEncoder encoder = new BCryptPasswordEncoder(12);
String encodedPassword = encoder.encode("plainTextPassword");
boolean matches = encoder.matches("plainTextPassword", encodedPassword);

// Groovy password policy example
String groovyScript = """
    import org.apereo.cas.authentication.MessageDescriptor
    import org.apereo.cas.authentication.support.password.PasswordExpiringWarningMessageDescriptor
    
    def handle(response, configuration) {
        def messages = []
        
        if (response.passwordExpirationDays <= 7) {
            messages.add(new PasswordExpiringWarningMessageDescriptor(
                "Password expires soon!",
                response.passwordExpirationDays))
        }
        
        return messages
    }
    
    handle(binding.variables.response, binding.variables.configuration)
""";

ExecutableCompiledGroovyScript script = new ExecutableCompiledGroovyScript(groovyScript);
GroovyPasswordPolicyHandlingStrategy groovyStrategy = new GroovyPasswordPolicyHandlingStrategy(script);

Custom Password Policy Implementation

public class CustomPasswordPolicyHandlingStrategy 
    implements AuthenticationPasswordPolicyHandlingStrategy<CustomAuthResponse, PasswordPolicyContext> {
    
    @Override
    public List<MessageDescriptor> handle(CustomAuthResponse response, 
                                        PasswordPolicyContext configuration) throws Throwable {
        
        List<MessageDescriptor> messages = new ArrayList<>();
        
        // Check custom password requirements
        if (response.isPasswordWeak()) {
            messages.add(new MessageDescriptor() {
                public String getCode() { return "password.weak"; }
                public String getDefaultMessage() { return "Password is too weak"; }
                public Object[] getParams() { return new Object[0]; }
            });
        }
        
        // Check password age
        int passwordAge = response.getPasswordAgeDays();
        if (passwordAge > 90) {
            messages.add(new PasswordExpiringWarningMessageDescriptor(
                "Password is old and should be changed",
                90 - passwordAge));
        }
        
        // Check account inactivity
        if (response.getDaysSinceLastLogin() > 180) {
            throw new AccountDisabledException("Account inactive for too long");
        }
        
        return messages;
    }
}

Password policies provide comprehensive password management and security enforcement, enabling organizations to implement sophisticated password requirements, expiration warnings, and account state management integrated with their authentication infrastructure.

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