or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdauthorities.mdcontext-management.mdembedded-server.mdindex.mdjson-serialization.mdpassword-policy.mduser-details.md

password-policy.mddocs/

0

# Password Policy Support

1

2

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

3

4

## Capabilities

5

6

### PasswordPolicyControl

7

8

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

9

10

```java { .api }

11

/**

12

* LDAP password policy request control for requesting password policy information

13

* during bind operations and other LDAP operations

14

*/

15

public class PasswordPolicyControl extends BasicControl {

16

/**

17

* The object identifier for the password policy control

18

*/

19

public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1";

20

21

/**

22

* Creates a password policy control with default criticality (false)

23

*/

24

public PasswordPolicyControl();

25

26

/**

27

* Creates a password policy control with specified criticality

28

* @param criticality true if the control is critical to the operation

29

*/

30

public PasswordPolicyControl(boolean criticality);

31

32

/**

33

* Gets the control's object identifier

34

* @return the OID string

35

*/

36

public String getID();

37

38

/**

39

* Indicates whether this control is critical

40

* @return true if critical

41

*/

42

public boolean isCritical();

43

44

/**

45

* Gets the encoded value of the control (always null for request control)

46

* @return null as this is a request control

47

*/

48

public byte[] getEncodedValue();

49

}

50

```

51

52

**Usage Examples:**

53

54

```java

55

// Add password policy control to LDAP context

56

LdapContext ctx = (LdapContext) contextSource.getContext("uid=user,ou=people", "password");

57

Control[] requestControls = { new PasswordPolicyControl() };

58

ctx.setRequestControls(requestControls);

59

60

// Perform authentication with password policy information

61

NamingEnumeration<SearchResult> results = ctx.search("ou=people", "uid=user", null);

62

63

// Check response controls for password policy information

64

Control[] responseControls = ctx.getResponseControls();

65

for (Control control : responseControls) {

66

if (control instanceof PasswordPolicyResponseControl) {

67

PasswordPolicyResponseControl ppControl = (PasswordPolicyResponseControl) control;

68

// Process password policy response

69

}

70

}

71

```

72

73

### PasswordPolicyException

74

75

Exception thrown when password policy violations occur during LDAP authentication.

76

77

```java { .api }

78

/**

79

* Exception thrown when LDAP password policy violations are detected

80

* Extends BadCredentialsException to integrate with Spring Security exception handling

81

*/

82

public class PasswordPolicyException extends BadCredentialsException {

83

/**

84

* Creates a password policy exception with the specified error status

85

* @param status the password policy error status

86

*/

87

public PasswordPolicyException(PasswordPolicyErrorStatus status);

88

89

/**

90

* Creates a password policy exception with status and custom message

91

* @param status the password policy error status

92

* @param msg custom error message

93

*/

94

public PasswordPolicyException(PasswordPolicyErrorStatus status, String msg);

95

96

/**

97

* Creates a password policy exception with status, message, and cause

98

* @param status the password policy error status

99

* @param msg custom error message

100

* @param cause the underlying cause exception

101

*/

102

public PasswordPolicyException(PasswordPolicyErrorStatus status, String msg, Throwable cause);

103

104

/**

105

* Gets the password policy error status

106

* @return the error status that caused this exception

107

*/

108

public PasswordPolicyErrorStatus getStatus();

109

}

110

```

111

112

### PasswordPolicyErrorStatus

113

114

Enumeration of standard LDAP password policy error status codes.

115

116

```java { .api }

117

/**

118

* Enumeration of LDAP password policy error status codes as defined in

119

* the LDAP Password Policy specification

120

*/

121

public enum PasswordPolicyErrorStatus {

122

/**

123

* The user's password has expired and must be changed

124

*/

125

PASSWORD_EXPIRED(0),

126

127

/**

128

* The user's account is locked due to too many failed authentication attempts

129

*/

130

ACCOUNT_LOCKED(1),

131

132

/**

133

* The user must change their password after reset by administrator

134

*/

135

CHANGE_AFTER_RESET(2),

136

137

/**

138

* Password modifications are not allowed for this user

139

*/

140

PASSWORD_MOD_NOT_ALLOWED(3),

141

142

/**

143

* The user must supply their old password when changing password

144

*/

145

MUST_SUPPLY_OLD_PASSWORD(4),

146

147

/**

148

* The new password does not meet quality requirements

149

*/

150

INSUFFICIENT_PASSWORD_QUALITY(5),

151

152

/**

153

* The new password is too short according to policy

154

*/

155

PASSWORD_TOO_SHORT(6),

156

157

/**

158

* The password was changed too recently and cannot be changed again yet

159

*/

160

PASSWORD_TOO_YOUNG(7),

161

162

/**

163

* The new password matches a password in the user's password history

164

*/

165

PASSWORD_IN_HISTORY(8);

166

167

private final int value;

168

169

/**

170

* Creates an error status with the specified numeric value

171

* @param value the numeric error code

172

*/

173

PasswordPolicyErrorStatus(int value);

174

175

/**

176

* Gets the numeric value of this error status

177

* @return the numeric error code

178

*/

179

public int getValue();

180

181

/**

182

* Gets the error status for a numeric value

183

* @param value the numeric error code

184

* @return the corresponding error status, or null if not found

185

*/

186

public static PasswordPolicyErrorStatus valueOf(int value);

187

}

188

```

189

190

### PasswordPolicyAwareContextSource

191

192

Enhanced context source that automatically handles password policy controls.

193

194

```java { .api }

195

/**

196

* Enhanced LDAP context source that automatically includes password policy controls

197

* in LDAP operations and processes policy responses

198

*/

199

public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityContextSource {

200

/**

201

* Creates a password policy aware context source

202

* @param providerUrl the LDAP provider URL

203

*/

204

public PasswordPolicyAwareContextSource(String providerUrl);

205

206

/**

207

* Gets an authenticated context with password policy control included

208

* @param principal the authentication principal

209

* @param credentials the authentication credentials

210

* @return DirContext with password policy awareness

211

* @throws NamingException if context creation fails

212

* @throws PasswordPolicyException if password policy violations occur

213

*/

214

@Override

215

public DirContext getContext(String principal, String credentials) throws NamingException;

216

217

/**

218

* Sets whether to throw exceptions on password policy warnings

219

* @param throwOnWarning true to throw exceptions on warnings

220

*/

221

public void setThrowExceptionOnPolicyWarning(boolean throwOnWarning);

222

}

223

```

224

225

### PasswordPolicyResponseControl

226

227

Response control containing password policy information from LDAP operations.

228

229

```java { .api }

230

/**

231

* LDAP password policy response control containing policy information returned by the server

232

*/

233

public class PasswordPolicyResponseControl extends PasswordPolicyControl {

234

/**

235

* Creates a password policy response control from encoded bytes

236

* @param encoded the encoded control value

237

* @throws IOException if decoding fails

238

*/

239

public PasswordPolicyResponseControl(byte[] encoded) throws IOException;

240

241

/**

242

* Gets the time in seconds before password expiration

243

* @return seconds until expiration, or -1 if not specified

244

*/

245

public int getTimeBeforeExpiration();

246

247

/**

248

* Gets the number of grace authentications remaining

249

* @return number of grace logins, or -1 if not specified

250

*/

251

public int getGraceLoginsRemaining();

252

253

/**

254

* Gets the password policy error status

255

* @return error status, or null if no error

256

*/

257

public PasswordPolicyErrorStatus getErrorStatus();

258

259

/**

260

* Indicates whether an error status is present

261

* @return true if error status exists

262

*/

263

public boolean hasError();

264

265

/**

266

* Indicates whether warning information is present

267

* @return true if warning information exists

268

*/

269

public boolean hasWarning();

270

}

271

```

272

273

### PasswordPolicyData

274

275

Interface for objects that can carry password policy information.

276

277

```java { .api }

278

/**

279

* Interface implemented by UserDetails objects that can carry password policy information

280

*/

281

public interface PasswordPolicyData {

282

/**

283

* Gets the time in seconds before password expiration

284

* @return seconds until expiration

285

*/

286

int getTimeBeforeExpiration();

287

288

/**

289

* Gets the number of grace authentications remaining

290

* @return number of grace logins

291

*/

292

int getGraceLoginsRemaining();

293

294

/**

295

* Sets the time before password expiration

296

* @param timeBeforeExpiration seconds until expiration

297

*/

298

void setTimeBeforeExpiration(int timeBeforeExpiration);

299

300

/**

301

* Sets the number of grace authentications remaining

302

* @param graceLoginsRemaining number of grace logins

303

*/

304

void setGraceLoginsRemaining(int graceLoginsRemaining);

305

}

306

```

307

308

### PasswordPolicyControlFactory

309

310

Factory for creating password policy controls from LDAP control responses.

311

312

```java { .api }

313

/**

314

* Factory class for creating password policy controls from LDAP responses

315

*/

316

public class PasswordPolicyControlFactory extends ControlFactory {

317

/**

318

* Creates a control from the provided control information

319

* @param ctl the control to process

320

* @return PasswordPolicyResponseControl if applicable, otherwise the original control

321

* @throws NamingException if control creation fails

322

*/

323

@Override

324

public Control getControlInstance(Control ctl) throws NamingException;

325

}

326

```

327

328

### PasswordPolicyControlExtractor

329

330

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

331

332

```java { .api }

333

/**

334

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

335

*/

336

public final class PasswordPolicyControlExtractor {

337

/**

338

* Extracts password policy response information from an LDAP response control

339

* @param control the password policy response control

340

* @return PasswordPolicyResponse containing policy information

341

* @throws PasswordPolicyException if policy violations are detected

342

*/

343

public static PasswordPolicyResponse extractPasswordPolicyResponse(PasswordPolicyResponseControl control)

344

throws PasswordPolicyException;

345

346

/**

347

* Checks LDAP response controls for password policy information

348

* @param responseControls array of LDAP response controls

349

* @return PasswordPolicyResponse if policy control found, null otherwise

350

* @throws PasswordPolicyException if policy violations are detected

351

*/

352

public static PasswordPolicyResponse checkForPasswordPolicyControl(Control[] responseControls)

353

throws PasswordPolicyException;

354

}

355

```

356

357

## Password Policy Integration

358

359

### Enhanced Authentication with Policy Support

360

361

```java { .api }

362

/**

363

* Enhanced LDAP authenticator that processes password policy controls

364

*/

365

public class PolicyAwareBindAuthenticator extends BindAuthenticator {

366

367

/**

368

* Creates a policy-aware bind authenticator

369

* @param contextSource the LDAP context source

370

*/

371

public PolicyAwareBindAuthenticator(ContextSource contextSource);

372

373

/**

374

* Authenticates with password policy control processing

375

* @param authentication the authentication request

376

* @return DirContextOperations with policy information

377

* @throws PasswordPolicyException if policy violations occur

378

*/

379

@Override

380

public DirContextOperations authenticate(Authentication authentication) {

381

String username = authentication.getName();

382

String password = (String) authentication.getCredentials();

383

384

try {

385

// Get user DN

386

String userDn = getUserDn(username);

387

388

// Create context with password policy control

389

LdapContext ctx = (LdapContext) getContextSource().getContext(userDn, password);

390

Control[] requestControls = { new PasswordPolicyControl() };

391

ctx.setRequestControls(requestControls);

392

393

// Perform a simple operation to trigger policy evaluation

394

ctx.getAttributes("", new String[]{"1.1"});

395

396

// Check for password policy response

397

Control[] responseControls = ctx.getResponseControls();

398

if (responseControls != null) {

399

PasswordPolicyResponse response =

400

PasswordPolicyControlExtractor.checkForPasswordPolicyControl(responseControls);

401

402

if (response != null) {

403

processPasswordPolicyResponse(response, username);

404

}

405

}

406

407

// Return user context information

408

return createUserContext(ctx, username);

409

410

} catch (NamingException e) {

411

throw new BadCredentialsException("Authentication failed", e);

412

}

413

}

414

415

private void processPasswordPolicyResponse(PasswordPolicyResponse response, String username) {

416

// Handle password policy warnings

417

if (response.getTimeBeforeExpiration() > 0) {

418

logger.info("Password expires in {} seconds for user: {}",

419

response.getTimeBeforeExpiration(), username);

420

}

421

422

if (response.getGraceAuthNsRemaining() > 0) {

423

logger.warn("User {} has {} grace logins remaining",

424

username, response.getGraceAuthNsRemaining());

425

}

426

427

// Handle password policy errors

428

if (response.hasError()) {

429

PasswordPolicyErrorStatus error = response.getErrorStatus();

430

throw new PasswordPolicyException(error,

431

"Password policy violation: " + error.name());

432

}

433

}

434

}

435

```

436

437

### Password Policy Response Processing

438

439

```java { .api }

440

/**

441

* Container for password policy response information

442

*/

443

public class PasswordPolicyResponse {

444

private final int timeBeforeExpiration;

445

private final int graceAuthNsRemaining;

446

private final PasswordPolicyErrorStatus errorStatus;

447

448

/**

449

* Creates a password policy response

450

* @param timeBeforeExpiration seconds until password expires (0 if not applicable)

451

* @param graceAuthNsRemaining number of grace authentications remaining

452

* @param errorStatus error status, or null if no error

453

*/

454

public PasswordPolicyResponse(int timeBeforeExpiration, int graceAuthNsRemaining,

455

PasswordPolicyErrorStatus errorStatus);

456

457

/**

458

* Gets the time in seconds before password expiration

459

* @return seconds until expiration, or 0 if not applicable

460

*/

461

public int getTimeBeforeExpiration();

462

463

/**

464

* Gets the number of grace authentications remaining

465

* @return number of grace logins, or 0 if not applicable

466

*/

467

public int getGraceAuthNsRemaining();

468

469

/**

470

* Gets the password policy error status

471

* @return error status, or null if no error

472

*/

473

public PasswordPolicyErrorStatus getErrorStatus();

474

475

/**

476

* Indicates whether this response contains an error

477

* @return true if error status is present

478

*/

479

public boolean hasError();

480

481

/**

482

* Indicates whether this response contains warnings

483

* @return true if expiration warning or grace login information present

484

*/

485

public boolean hasWarning();

486

}

487

```

488

489

## Configuration Examples

490

491

### Password Policy-Aware Authentication Provider

492

493

```java

494

@Configuration

495

public class PasswordPolicyConfig {

496

497

@Bean

498

public PolicyAwareBindAuthenticator policyAwareAuthenticator() {

499

PolicyAwareBindAuthenticator authenticator =

500

new PolicyAwareBindAuthenticator(contextSource());

501

authenticator.setUserSearch(userSearch());

502

return authenticator;

503

}

504

505

@Bean

506

public LdapAuthenticationProvider policyAwareAuthProvider() {

507

LdapAuthenticationProvider provider =

508

new LdapAuthenticationProvider(policyAwareAuthenticator());

509

510

// Set custom authentication exception handler

511

provider.setAuthenticationExceptionHandler(passwordPolicyExceptionHandler());

512

513

return provider;

514

}

515

516

@Bean

517

public AuthenticationExceptionHandler passwordPolicyExceptionHandler() {

518

return new PasswordPolicyAuthenticationExceptionHandler();

519

}

520

}

521

```

522

523

### Custom Password Policy Exception Handler

524

525

```java

526

@Component

527

public class PasswordPolicyAuthenticationExceptionHandler {

528

529

private static final Logger logger = LoggerFactory.getLogger(

530

PasswordPolicyAuthenticationExceptionHandler.class);

531

532

public void handlePasswordPolicyException(PasswordPolicyException ex, String username) {

533

PasswordPolicyErrorStatus status = ex.getStatus();

534

535

switch (status) {

536

case PASSWORD_EXPIRED:

537

logger.warn("Password expired for user: {}", username);

538

// Redirect to password change page

539

break;

540

541

case ACCOUNT_LOCKED:

542

logger.warn("Account locked for user: {}", username);

543

// Send account locked notification

544

break;

545

546

case CHANGE_AFTER_RESET:

547

logger.info("User {} must change password after reset", username);

548

// Force password change workflow

549

break;

550

551

case INSUFFICIENT_PASSWORD_QUALITY:

552

logger.info("Password quality insufficient for user: {}", username);

553

// Show password requirements

554

break;

555

556

case PASSWORD_TOO_SHORT:

557

logger.info("Password too short for user: {}", username);

558

// Show minimum length requirement

559

break;

560

561

default:

562

logger.error("Password policy violation for user {}: {}", username, status);

563

break;

564

}

565

}

566

}

567

```

568

569

### Web Security Integration

570

571

```java

572

@Configuration

573

@EnableWebSecurity

574

public class WebSecurityConfig {

575

576

@Bean

577

public AuthenticationFailureHandler ldapAuthenticationFailureHandler() {

578

return new LdapPasswordPolicyAuthenticationFailureHandler();

579

}

580

581

@Bean

582

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

583

http

584

.authorizeHttpRequests(authz -> authz

585

.requestMatchers("/login", "/password-change").permitAll()

586

.anyRequest().authenticated()

587

)

588

.formLogin(form -> form

589

.loginPage("/login")

590

.failureHandler(ldapAuthenticationFailureHandler())

591

.permitAll()

592

)

593

.logout(logout -> logout.permitAll());

594

595

return http.build();

596

}

597

}

598

599

@Component

600

public class LdapPasswordPolicyAuthenticationFailureHandler

601

implements AuthenticationFailureHandler {

602

603

@Override

604

public void onAuthenticationFailure(HttpServletRequest request,

605

HttpServletResponse response, AuthenticationException exception)

606

throws IOException, ServletException {

607

608

if (exception instanceof PasswordPolicyException) {

609

PasswordPolicyException ppEx = (PasswordPolicyException) exception;

610

PasswordPolicyErrorStatus status = ppEx.getStatus();

611

612

switch (status) {

613

case PASSWORD_EXPIRED:

614

case CHANGE_AFTER_RESET:

615

response.sendRedirect("/password-change?expired=true");

616

return;

617

618

case ACCOUNT_LOCKED:

619

response.sendRedirect("/login?locked=true");

620

return;

621

622

default:

623

break;

624

}

625

}

626

627

// Default failure handling

628

response.sendRedirect("/login?error=true");

629

}

630

}

631

```

632

633

### Password Change Controller

634

635

```java

636

@Controller

637

public class PasswordChangeController {

638

639

private final LdapTemplate ldapTemplate;

640

private final PasswordEncoder passwordEncoder;

641

642

public PasswordChangeController(LdapTemplate ldapTemplate, PasswordEncoder passwordEncoder) {

643

this.ldapTemplate = ldapTemplate;

644

this.passwordEncoder = passwordEncoder;

645

}

646

647

@GetMapping("/password-change")

648

public String showPasswordChangeForm(Model model,

649

@RequestParam(required = false) String expired) {

650

if ("true".equals(expired)) {

651

model.addAttribute("message", "Your password has expired and must be changed.");

652

}

653

return "password-change";

654

}

655

656

@PostMapping("/password-change")

657

public String changePassword(@RequestParam String currentPassword,

658

@RequestParam String newPassword,

659

@RequestParam String confirmPassword,

660

Authentication authentication,

661

RedirectAttributes redirectAttributes) {

662

663

try {

664

if (!newPassword.equals(confirmPassword)) {

665

redirectAttributes.addFlashAttribute("error", "Passwords do not match");

666

return "redirect:/password-change";

667

}

668

669

String username = authentication.getName();

670

String userDn = findUserDn(username);

671

672

// Validate current password

673

validateCurrentPassword(userDn, currentPassword);

674

675

// Change password with policy compliance check

676

changeUserPassword(userDn, newPassword);

677

678

redirectAttributes.addFlashAttribute("success", "Password changed successfully");

679

return "redirect:/dashboard";

680

681

} catch (PasswordPolicyException ex) {

682

String errorMessage = getPasswordPolicyErrorMessage(ex.getStatus());

683

redirectAttributes.addFlashAttribute("error", errorMessage);

684

return "redirect:/password-change";

685

686

} catch (Exception ex) {

687

redirectAttributes.addFlashAttribute("error", "Password change failed");

688

return "redirect:/password-change";

689

}

690

}

691

692

private void changeUserPassword(String userDn, String newPassword) {

693

// Use LDAP modify operation with password policy control

694

LdapContext ctx = (LdapContext) ldapTemplate.getContextSource().getContext(

695

"cn=admin,dc=example,dc=com", "adminPassword");

696

697

try {

698

Control[] requestControls = { new PasswordPolicyControl() };

699

ctx.setRequestControls(requestControls);

700

701

ModificationItem[] mods = new ModificationItem[] {

702

new ModificationItem(DirContext.REPLACE_ATTRIBUTE,

703

new BasicAttribute("userPassword", passwordEncoder.encode(newPassword)))

704

};

705

706

ctx.modifyAttributes(userDn, mods);

707

708

// Check response for policy violations

709

Control[] responseControls = ctx.getResponseControls();

710

PasswordPolicyControlExtractor.checkForPasswordPolicyControl(responseControls);

711

712

} catch (NamingException ex) {

713

throw new RuntimeException("Failed to change password", ex);

714

} finally {

715

LdapUtils.closeContext(ctx);

716

}

717

}

718

719

private String getPasswordPolicyErrorMessage(PasswordPolicyErrorStatus status) {

720

switch (status) {

721

case INSUFFICIENT_PASSWORD_QUALITY:

722

return "Password does not meet quality requirements";

723

case PASSWORD_TOO_SHORT:

724

return "Password is too short";

725

case PASSWORD_IN_HISTORY:

726

return "Password was used recently and cannot be reused";

727

case PASSWORD_TOO_YOUNG:

728

return "Password was changed too recently";

729

default:

730

return "Password policy violation: " + status.name();

731

}

732

}

733

}

734

```