CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Spring Security LDAP module providing comprehensive LDAP authentication and authorization capabilities for enterprise applications

Pending
Overview
Eval results
Files

password-policy.mddocs/

Password Policy Support

LDAP password policy controls and exception handling for enterprise environments requiring advanced password management features.

Capabilities

PasswordPolicyControl

LDAP password policy request control for requesting password policy information during authentication.

/**
 * LDAP password policy request control for requesting password policy information
 * during bind operations and other LDAP operations
 */
public class PasswordPolicyControl extends BasicControl {
    /**
     * The object identifier for the password policy control
     */
    public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1";
    
    /**
     * Creates a password policy control with default criticality (false)
     */
    public PasswordPolicyControl();
    
    /**
     * Creates a password policy control with specified criticality
     * @param criticality true if the control is critical to the operation
     */
    public PasswordPolicyControl(boolean criticality);
    
    /**
     * Gets the control's object identifier
     * @return the OID string
     */
    public String getID();
    
    /**
     * Indicates whether this control is critical
     * @return true if critical
     */
    public boolean isCritical();
    
    /**
     * Gets the encoded value of the control (always null for request control)
     * @return null as this is a request control
     */
    public byte[] getEncodedValue();
}

Usage Examples:

// Add password policy control to LDAP context
LdapContext ctx = (LdapContext) contextSource.getContext("uid=user,ou=people", "password");
Control[] requestControls = { new PasswordPolicyControl() };
ctx.setRequestControls(requestControls);

// Perform authentication with password policy information
NamingEnumeration<SearchResult> results = ctx.search("ou=people", "uid=user", null);

// Check response controls for password policy information
Control[] responseControls = ctx.getResponseControls();
for (Control control : responseControls) {
    if (control instanceof PasswordPolicyResponseControl) {
        PasswordPolicyResponseControl ppControl = (PasswordPolicyResponseControl) control;
        // Process password policy response
    }
}

PasswordPolicyException

Exception thrown when password policy violations occur during LDAP authentication.

/**
 * Exception thrown when LDAP password policy violations are detected
 * Extends BadCredentialsException to integrate with Spring Security exception handling
 */
public class PasswordPolicyException extends BadCredentialsException {
    /**
     * Creates a password policy exception with the specified error status
     * @param status the password policy error status
     */
    public PasswordPolicyException(PasswordPolicyErrorStatus status);
    
    /**
     * Creates a password policy exception with status and custom message
     * @param status the password policy error status
     * @param msg custom error message
     */
    public PasswordPolicyException(PasswordPolicyErrorStatus status, String msg);
    
    /**
     * Creates a password policy exception with status, message, and cause
     * @param status the password policy error status
     * @param msg custom error message
     * @param cause the underlying cause exception
     */
    public PasswordPolicyException(PasswordPolicyErrorStatus status, String msg, Throwable cause);
    
    /**
     * Gets the password policy error status
     * @return the error status that caused this exception
     */
    public PasswordPolicyErrorStatus getStatus();
}

PasswordPolicyErrorStatus

Enumeration of standard LDAP password policy error status codes.

/**
 * Enumeration of LDAP password policy error status codes as defined in
 * the LDAP Password Policy specification
 */
public enum PasswordPolicyErrorStatus {
    /**
     * The user's password has expired and must be changed
     */
    PASSWORD_EXPIRED(0),
    
    /**
     * The user's account is locked due to too many failed authentication attempts
     */
    ACCOUNT_LOCKED(1),
    
    /**
     * The user must change their password after reset by administrator
     */
    CHANGE_AFTER_RESET(2),
    
    /**
     * Password modifications are not allowed for this user
     */
    PASSWORD_MOD_NOT_ALLOWED(3),
    
    /**
     * The user must supply their old password when changing password
     */
    MUST_SUPPLY_OLD_PASSWORD(4),
    
    /**
     * The new password does not meet quality requirements
     */
    INSUFFICIENT_PASSWORD_QUALITY(5),
    
    /**
     * The new password is too short according to policy
     */
    PASSWORD_TOO_SHORT(6),
    
    /**
     * The password was changed too recently and cannot be changed again yet
     */
    PASSWORD_TOO_YOUNG(7),
    
    /**
     * The new password matches a password in the user's password history
     */
    PASSWORD_IN_HISTORY(8);
    
    private final int value;
    
    /**
     * Creates an error status with the specified numeric value
     * @param value the numeric error code
     */
    PasswordPolicyErrorStatus(int value);
    
    /**
     * Gets the numeric value of this error status
     * @return the numeric error code
     */
    public int getValue();
    
    /**
     * Gets the error status for a numeric value
     * @param value the numeric error code
     * @return the corresponding error status, or null if not found
     */
    public static PasswordPolicyErrorStatus valueOf(int value);
}

PasswordPolicyAwareContextSource

Enhanced context source that automatically handles password policy controls.

/**
 * Enhanced LDAP context source that automatically includes password policy controls
 * in LDAP operations and processes policy responses
 */
public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityContextSource {
    /**
     * Creates a password policy aware context source
     * @param providerUrl the LDAP provider URL
     */
    public PasswordPolicyAwareContextSource(String providerUrl);
    
    /**
     * Gets an authenticated context with password policy control included
     * @param principal the authentication principal
     * @param credentials the authentication credentials
     * @return DirContext with password policy awareness
     * @throws NamingException if context creation fails
     * @throws PasswordPolicyException if password policy violations occur
     */
    @Override
    public DirContext getContext(String principal, String credentials) throws NamingException;
    
    /**
     * Sets whether to throw exceptions on password policy warnings
     * @param throwOnWarning true to throw exceptions on warnings
     */
    public void setThrowExceptionOnPolicyWarning(boolean throwOnWarning);
}

PasswordPolicyResponseControl

Response control containing password policy information from LDAP operations.

/**
 * LDAP password policy response control containing policy information returned by the server
 */
public class PasswordPolicyResponseControl extends PasswordPolicyControl {
    /**
     * Creates a password policy response control from encoded bytes
     * @param encoded the encoded control value
     * @throws IOException if decoding fails
     */
    public PasswordPolicyResponseControl(byte[] encoded) throws IOException;
    
    /**
     * Gets the time in seconds before password expiration
     * @return seconds until expiration, or -1 if not specified
     */
    public int getTimeBeforeExpiration();
    
    /**
     * Gets the number of grace authentications remaining
     * @return number of grace logins, or -1 if not specified
     */
    public int getGraceLoginsRemaining();
    
    /**
     * Gets the password policy error status
     * @return error status, or null if no error
     */
    public PasswordPolicyErrorStatus getErrorStatus();
    
    /**
     * Indicates whether an error status is present
     * @return true if error status exists
     */
    public boolean hasError();
    
    /**
     * Indicates whether warning information is present
     * @return true if warning information exists
     */
    public boolean hasWarning();
}

PasswordPolicyData

Interface for objects that can carry password policy information.

/**
 * Interface implemented by UserDetails objects that can carry password policy information
 */
public interface PasswordPolicyData {
    /**
     * Gets the time in seconds before password expiration
     * @return seconds until expiration
     */
    int getTimeBeforeExpiration();
    
    /**
     * Gets the number of grace authentications remaining
     * @return number of grace logins
     */
    int getGraceLoginsRemaining();
    
    /**
     * Sets the time before password expiration
     * @param timeBeforeExpiration seconds until expiration
     */
    void setTimeBeforeExpiration(int timeBeforeExpiration);
    
    /**
     * Sets the number of grace authentications remaining
     * @param graceLoginsRemaining number of grace logins
     */
    void setGraceLoginsRemaining(int graceLoginsRemaining);
}

PasswordPolicyControlFactory

Factory for creating password policy controls from LDAP control responses.

/**
 * Factory class for creating password policy controls from LDAP responses
 */
public class PasswordPolicyControlFactory extends ControlFactory {
    /**
     * Creates a control from the provided control information
     * @param ctl the control to process
     * @return PasswordPolicyResponseControl if applicable, otherwise the original control
     * @throws NamingException if control creation fails
     */
    @Override
    public Control getControlInstance(Control ctl) throws NamingException;
}

PasswordPolicyControlExtractor

Utility class for extracting password policy information from LDAP response controls.

/**
 * Utility class for extracting password policy response information from LDAP controls
 */
public final class PasswordPolicyControlExtractor {
    /**
     * Extracts password policy response information from an LDAP response control
     * @param control the password policy response control
     * @return PasswordPolicyResponse containing policy information
     * @throws PasswordPolicyException if policy violations are detected
     */
    public static PasswordPolicyResponse extractPasswordPolicyResponse(PasswordPolicyResponseControl control)
            throws PasswordPolicyException;
    
    /**
     * Checks LDAP response controls for password policy information
     * @param responseControls array of LDAP response controls
     * @return PasswordPolicyResponse if policy control found, null otherwise
     * @throws PasswordPolicyException if policy violations are detected
     */
    public static PasswordPolicyResponse checkForPasswordPolicyControl(Control[] responseControls)
            throws PasswordPolicyException;
}

Password Policy Integration

Enhanced Authentication with Policy Support

/**
 * Enhanced LDAP authenticator that processes password policy controls
 */
public class PolicyAwareBindAuthenticator extends BindAuthenticator {
    
    /**
     * Creates a policy-aware bind authenticator
     * @param contextSource the LDAP context source
     */
    public PolicyAwareBindAuthenticator(ContextSource contextSource);
    
    /**
     * Authenticates with password policy control processing
     * @param authentication the authentication request
     * @return DirContextOperations with policy information
     * @throws PasswordPolicyException if policy violations occur
     */
    @Override
    public DirContextOperations authenticate(Authentication authentication) {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
        
        try {
            // Get user DN
            String userDn = getUserDn(username);
            
            // Create context with password policy control
            LdapContext ctx = (LdapContext) getContextSource().getContext(userDn, password);
            Control[] requestControls = { new PasswordPolicyControl() };
            ctx.setRequestControls(requestControls);
            
            // Perform a simple operation to trigger policy evaluation
            ctx.getAttributes("", new String[]{"1.1"});
            
            // Check for password policy response
            Control[] responseControls = ctx.getResponseControls();
            if (responseControls != null) {
                PasswordPolicyResponse response = 
                    PasswordPolicyControlExtractor.checkForPasswordPolicyControl(responseControls);
                
                if (response != null) {
                    processPasswordPolicyResponse(response, username);
                }
            }
            
            // Return user context information
            return createUserContext(ctx, username);
            
        } catch (NamingException e) {
            throw new BadCredentialsException("Authentication failed", e);
        }
    }
    
    private void processPasswordPolicyResponse(PasswordPolicyResponse response, String username) {
        // Handle password policy warnings
        if (response.getTimeBeforeExpiration() > 0) {
            logger.info("Password expires in {} seconds for user: {}", 
                response.getTimeBeforeExpiration(), username);
        }
        
        if (response.getGraceAuthNsRemaining() > 0) {
            logger.warn("User {} has {} grace logins remaining", 
                username, response.getGraceAuthNsRemaining());
        }
        
        // Handle password policy errors
        if (response.hasError()) {
            PasswordPolicyErrorStatus error = response.getErrorStatus();
            throw new PasswordPolicyException(error, 
                "Password policy violation: " + error.name());
        }
    }
}

Password Policy Response Processing

/**
 * Container for password policy response information
 */
public class PasswordPolicyResponse {
    private final int timeBeforeExpiration;
    private final int graceAuthNsRemaining;
    private final PasswordPolicyErrorStatus errorStatus;
    
    /**
     * Creates a password policy response
     * @param timeBeforeExpiration seconds until password expires (0 if not applicable)
     * @param graceAuthNsRemaining number of grace authentications remaining
     * @param errorStatus error status, or null if no error
     */
    public PasswordPolicyResponse(int timeBeforeExpiration, int graceAuthNsRemaining, 
            PasswordPolicyErrorStatus errorStatus);
    
    /**
     * Gets the time in seconds before password expiration
     * @return seconds until expiration, or 0 if not applicable
     */
    public int getTimeBeforeExpiration();
    
    /**
     * Gets the number of grace authentications remaining
     * @return number of grace logins, or 0 if not applicable
     */
    public int getGraceAuthNsRemaining();
    
    /**
     * Gets the password policy error status
     * @return error status, or null if no error
     */
    public PasswordPolicyErrorStatus getErrorStatus();
    
    /**
     * Indicates whether this response contains an error
     * @return true if error status is present
     */
    public boolean hasError();
    
    /**
     * Indicates whether this response contains warnings
     * @return true if expiration warning or grace login information present
     */
    public boolean hasWarning();
}

Configuration Examples

Password Policy-Aware Authentication Provider

@Configuration
public class PasswordPolicyConfig {
    
    @Bean
    public PolicyAwareBindAuthenticator policyAwareAuthenticator() {
        PolicyAwareBindAuthenticator authenticator = 
            new PolicyAwareBindAuthenticator(contextSource());
        authenticator.setUserSearch(userSearch());
        return authenticator;
    }
    
    @Bean
    public LdapAuthenticationProvider policyAwareAuthProvider() {
        LdapAuthenticationProvider provider = 
            new LdapAuthenticationProvider(policyAwareAuthenticator());
        
        // Set custom authentication exception handler
        provider.setAuthenticationExceptionHandler(passwordPolicyExceptionHandler());
        
        return provider;
    }
    
    @Bean
    public AuthenticationExceptionHandler passwordPolicyExceptionHandler() {
        return new PasswordPolicyAuthenticationExceptionHandler();
    }
}

Custom Password Policy Exception Handler

@Component
public class PasswordPolicyAuthenticationExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(
        PasswordPolicyAuthenticationExceptionHandler.class);
    
    public void handlePasswordPolicyException(PasswordPolicyException ex, String username) {
        PasswordPolicyErrorStatus status = ex.getStatus();
        
        switch (status) {
            case PASSWORD_EXPIRED:
                logger.warn("Password expired for user: {}", username);
                // Redirect to password change page
                break;
                
            case ACCOUNT_LOCKED:
                logger.warn("Account locked for user: {}", username);
                // Send account locked notification
                break;
                
            case CHANGE_AFTER_RESET:
                logger.info("User {} must change password after reset", username);
                // Force password change workflow
                break;
                
            case INSUFFICIENT_PASSWORD_QUALITY:
                logger.info("Password quality insufficient for user: {}", username);
                // Show password requirements
                break;
                
            case PASSWORD_TOO_SHORT:
                logger.info("Password too short for user: {}", username);
                // Show minimum length requirement
                break;
                
            default:
                logger.error("Password policy violation for user {}: {}", username, status);
                break;
        }
    }
}

Web Security Integration

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    
    @Bean
    public AuthenticationFailureHandler ldapAuthenticationFailureHandler() {
        return new LdapPasswordPolicyAuthenticationFailureHandler();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/login", "/password-change").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .failureHandler(ldapAuthenticationFailureHandler())
                .permitAll()
            )
            .logout(logout -> logout.permitAll());
        
        return http.build();
    }
}

@Component
public class LdapPasswordPolicyAuthenticationFailureHandler 
        implements AuthenticationFailureHandler {
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, 
            HttpServletResponse response, AuthenticationException exception) 
            throws IOException, ServletException {
        
        if (exception instanceof PasswordPolicyException) {
            PasswordPolicyException ppEx = (PasswordPolicyException) exception;
            PasswordPolicyErrorStatus status = ppEx.getStatus();
            
            switch (status) {
                case PASSWORD_EXPIRED:
                case CHANGE_AFTER_RESET:
                    response.sendRedirect("/password-change?expired=true");
                    return;
                    
                case ACCOUNT_LOCKED:
                    response.sendRedirect("/login?locked=true");
                    return;
                    
                default:
                    break;
            }
        }
        
        // Default failure handling
        response.sendRedirect("/login?error=true");
    }
}

Password Change Controller

@Controller
public class PasswordChangeController {
    
    private final LdapTemplate ldapTemplate;
    private final PasswordEncoder passwordEncoder;
    
    public PasswordChangeController(LdapTemplate ldapTemplate, PasswordEncoder passwordEncoder) {
        this.ldapTemplate = ldapTemplate;
        this.passwordEncoder = passwordEncoder;
    }
    
    @GetMapping("/password-change")
    public String showPasswordChangeForm(Model model, 
            @RequestParam(required = false) String expired) {
        if ("true".equals(expired)) {
            model.addAttribute("message", "Your password has expired and must be changed.");
        }
        return "password-change";
    }
    
    @PostMapping("/password-change")
    public String changePassword(@RequestParam String currentPassword,
            @RequestParam String newPassword,
            @RequestParam String confirmPassword,
            Authentication authentication,
            RedirectAttributes redirectAttributes) {
        
        try {
            if (!newPassword.equals(confirmPassword)) {
                redirectAttributes.addFlashAttribute("error", "Passwords do not match");
                return "redirect:/password-change";
            }
            
            String username = authentication.getName();
            String userDn = findUserDn(username);
            
            // Validate current password
            validateCurrentPassword(userDn, currentPassword);
            
            // Change password with policy compliance check
            changeUserPassword(userDn, newPassword);
            
            redirectAttributes.addFlashAttribute("success", "Password changed successfully");
            return "redirect:/dashboard";
            
        } catch (PasswordPolicyException ex) {
            String errorMessage = getPasswordPolicyErrorMessage(ex.getStatus());
            redirectAttributes.addFlashAttribute("error", errorMessage);
            return "redirect:/password-change";
            
        } catch (Exception ex) {
            redirectAttributes.addFlashAttribute("error", "Password change failed");
            return "redirect:/password-change";
        }
    }
    
    private void changeUserPassword(String userDn, String newPassword) {
        // Use LDAP modify operation with password policy control
        LdapContext ctx = (LdapContext) ldapTemplate.getContextSource().getContext(
            "cn=admin,dc=example,dc=com", "adminPassword");
        
        try {
            Control[] requestControls = { new PasswordPolicyControl() };
            ctx.setRequestControls(requestControls);
            
            ModificationItem[] mods = new ModificationItem[] {
                new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("userPassword", passwordEncoder.encode(newPassword)))
            };
            
            ctx.modifyAttributes(userDn, mods);
            
            // Check response for policy violations
            Control[] responseControls = ctx.getResponseControls();
            PasswordPolicyControlExtractor.checkForPasswordPolicyControl(responseControls);
            
        } catch (NamingException ex) {
            throw new RuntimeException("Failed to change password", ex);
        } finally {
            LdapUtils.closeContext(ctx);
        }
    }
    
    private String getPasswordPolicyErrorMessage(PasswordPolicyErrorStatus status) {
        switch (status) {
            case INSUFFICIENT_PASSWORD_QUALITY:
                return "Password does not meet quality requirements";
            case PASSWORD_TOO_SHORT:
                return "Password is too short";
            case PASSWORD_IN_HISTORY:
                return "Password was used recently and cannot be reused";
            case PASSWORD_TOO_YOUNG:
                return "Password was changed too recently";
            default:
                return "Password policy violation: " + status.name();
        }
    }
}

Install with Tessl CLI

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

docs

authentication.md

authorities.md

context-management.md

embedded-server.md

index.md

json-serialization.md

password-policy.md

user-details.md

tile.json