or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

adaptive-authentication.mdauthentication-handlers.mdauthentication-policies.mdcredential-handling.mdindex.mdpassword-policies.mdprincipal-resolution.md

password-policies.mddocs/

0

# Password Policies

1

2

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.

3

4

## Core Password Policy Interface

5

6

```java { .api }

7

package org.apereo.cas.authentication;

8

9

import org.apereo.cas.authentication.principal.Principal;

10

import java.util.List;

11

12

public interface AuthenticationPasswordPolicyHandlingStrategy<AuthnResponse, PasswordPolicyConfiguration> {

13

List<MessageDescriptor> handle(AuthnResponse response, PasswordPolicyConfiguration configuration)

14

throws Throwable;

15

}

16

```

17

18

## Password Policy Context

19

20

```java { .api }

21

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

22

23

import org.apereo.cas.authentication.AuthenticationAccountStateHandler;

24

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

25

26

public class PasswordPolicyContext {

27

private AuthenticationAccountStateHandler accountStateHandler;

28

private boolean alwaysDisplayPasswordExpirationWarning;

29

private int passwordWarningNumberOfDays = 30;

30

private int loginFailures = 5;

31

32

// Constructors

33

public PasswordPolicyContext() {}

34

35

public PasswordPolicyContext(int passwordWarningNumberOfDays) {

36

this.passwordWarningNumberOfDays = passwordWarningNumberOfDays;

37

}

38

39

public PasswordPolicyContext(AuthenticationAccountStateHandler accountStateHandler,

40

boolean alwaysDisplayPasswordExpirationWarning,

41

int passwordWarningNumberOfDays,

42

int loginFailures) {

43

this.accountStateHandler = accountStateHandler;

44

this.alwaysDisplayPasswordExpirationWarning = alwaysDisplayPasswordExpirationWarning;

45

this.passwordWarningNumberOfDays = passwordWarningNumberOfDays;

46

this.loginFailures = loginFailures;

47

}

48

49

public PasswordPolicyContext(PasswordPolicyProperties props) {

50

this(null, props.isWarnAll(), props.getWarningDays(), props.getLoginFailures());

51

}

52

53

// Getters and setters

54

public AuthenticationAccountStateHandler getAccountStateHandler() { return accountStateHandler; }

55

public void setAccountStateHandler(AuthenticationAccountStateHandler accountStateHandler) {

56

this.accountStateHandler = accountStateHandler;

57

}

58

59

public boolean isAlwaysDisplayPasswordExpirationWarning() {

60

return alwaysDisplayPasswordExpirationWarning;

61

}

62

public void setAlwaysDisplayPasswordExpirationWarning(boolean alwaysDisplayPasswordExpirationWarning) {

63

this.alwaysDisplayPasswordExpirationWarning = alwaysDisplayPasswordExpirationWarning;

64

}

65

66

public int getPasswordWarningNumberOfDays() { return passwordWarningNumberOfDays; }

67

public void setPasswordWarningNumberOfDays(int passwordWarningNumberOfDays) {

68

this.passwordWarningNumberOfDays = passwordWarningNumberOfDays;

69

}

70

71

public int getLoginFailures() { return loginFailures; }

72

public void setLoginFailures(int loginFailures) { this.loginFailures = loginFailures; }

73

}

74

```

75

76

## Message Descriptors

77

78

### Password Expiring Warning Message

79

80

```java { .api }

81

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

82

83

import org.apereo.cas.authentication.MessageDescriptor;

84

import java.time.ZonedDateTime;

85

86

public class PasswordExpiringWarningMessageDescriptor implements MessageDescriptor {

87

88

public static final String DEFAULT_CODE = "password.expiration.warning";

89

90

private final String code;

91

private final String defaultMessage;

92

private final int daysToExpiration;

93

private final ZonedDateTime expirationDate;

94

95

public PasswordExpiringWarningMessageDescriptor(String defaultMessage,

96

int daysToExpiration) {

97

this(DEFAULT_CODE, defaultMessage, daysToExpiration, null);

98

}

99

100

public PasswordExpiringWarningMessageDescriptor(String code,

101

String defaultMessage,

102

int daysToExpiration,

103

ZonedDateTime expirationDate) {

104

this.code = code;

105

this.defaultMessage = defaultMessage;

106

this.daysToExpiration = daysToExpiration;

107

this.expirationDate = expirationDate;

108

}

109

110

public String getCode() { return code; }

111

112

public String getDefaultMessage() { return defaultMessage; }

113

114

public Object[] getParams() {

115

return new Object[]{daysToExpiration, expirationDate};

116

}

117

118

public int getDaysToExpiration() { return daysToExpiration; }

119

120

public ZonedDateTime getExpirationDate() { return expirationDate; }

121

}

122

```

123

124

## Password Policy Handling Strategies

125

126

### Default Password Policy Handling Strategy

127

128

```java { .api }

129

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

130

131

import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;

132

import org.apereo.cas.authentication.MessageDescriptor;

133

import java.util.ArrayList;

134

import java.util.List;

135

136

public class DefaultPasswordPolicyHandlingStrategy<AuthnResponse>

137

implements AuthenticationPasswordPolicyHandlingStrategy<AuthnResponse, PasswordPolicyContext> {

138

139

@Override

140

public List<MessageDescriptor> handle(AuthnResponse response,

141

PasswordPolicyContext configuration) throws Throwable {

142

143

if (configuration == null) {

144

return new ArrayList<>();

145

}

146

147

AuthenticationAccountStateHandler accountStateHandler = configuration.getAccountStateHandler();

148

if (accountStateHandler == null) {

149

return new ArrayList<>();

150

}

151

152

return accountStateHandler.handle(response, configuration);

153

}

154

}

155

```

156

157

### Reject Result Code Password Policy Strategy

158

159

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

160

161

```java { .api }

162

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

163

164

import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;

165

import org.apereo.cas.authentication.MessageDescriptor;

166

import org.apereo.cas.authentication.exceptions.AccountDisabledException;

167

import org.apereo.cas.authentication.exceptions.AccountPasswordMustChangeException;

168

import org.apereo.cas.authentication.exceptions.InvalidLoginLocationException;

169

import org.apereo.cas.authentication.exceptions.InvalidLoginTimeException;

170

import java.util.List;

171

import java.util.Map;

172

173

public class RejectResultCodePasswordPolicyHandlingStrategy

174

implements AuthenticationPasswordPolicyHandlingStrategy<Object, PasswordPolicyContext> {

175

176

private static final Map<String, Class<? extends Exception>> RESULT_CODE_EXCEPTIONS = Map.of(

177

"ACCOUNT_DISABLED", AccountDisabledException.class,

178

"PASSWORD_MUST_CHANGE", AccountPasswordMustChangeException.class,

179

"INVALID_LOGIN_LOCATION", InvalidLoginLocationException.class,

180

"INVALID_LOGIN_TIME", InvalidLoginTimeException.class

181

);

182

183

@Override

184

public List<MessageDescriptor> handle(Object response, PasswordPolicyContext configuration)

185

throws Throwable {

186

187

String resultCode = extractResultCode(response);

188

189

if (resultCode != null && RESULT_CODE_EXCEPTIONS.containsKey(resultCode)) {

190

Class<? extends Exception> exceptionClass = RESULT_CODE_EXCEPTIONS.get(resultCode);

191

Exception exception = exceptionClass.getDeclaredConstructor(String.class)

192

.newInstance("Password policy violation: " + resultCode);

193

throw exception;

194

}

195

196

return List.of();

197

}

198

199

private String extractResultCode(Object response) {

200

// Extract result code from authentication response

201

// Implementation depends on the specific authentication backend

202

return null;

203

}

204

}

205

```

206

207

### Groovy Password Policy Handling Strategy

208

209

Scriptable password policy handler using Groovy:

210

211

```java { .api }

212

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

213

214

import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;

215

import org.apereo.cas.authentication.MessageDescriptor;

216

import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;

217

import java.util.ArrayList;

218

import java.util.List;

219

220

public class GroovyPasswordPolicyHandlingStrategy

221

implements AuthenticationPasswordPolicyHandlingStrategy<Object, PasswordPolicyContext> {

222

223

private final ExecutableCompiledGroovyScript watchableScript;

224

225

public GroovyPasswordPolicyHandlingStrategy(ExecutableCompiledGroovyScript watchableScript) {

226

this.watchableScript = watchableScript;

227

}

228

229

@Override

230

public List<MessageDescriptor> handle(Object response, PasswordPolicyContext configuration)

231

throws Throwable {

232

233

Object result = watchableScript.execute(response, configuration, List.class);

234

235

if (result instanceof List) {

236

return (List<MessageDescriptor>) result;

237

}

238

239

return new ArrayList<>();

240

}

241

}

242

```

243

244

## Account State Handlers

245

246

```java { .api }

247

package org.apereo.cas.authentication;

248

249

import org.apereo.cas.authentication.support.password.PasswordPolicyContext;

250

import java.util.List;

251

252

public interface AuthenticationAccountStateHandler<Response, Configuration> {

253

List<MessageDescriptor> handle(Response response, Configuration configuration) throws Exception;

254

255

boolean supports(Response response);

256

}

257

```

258

259

### LDAP Account State Handler

260

261

```java { .api }

262

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

263

264

import org.apereo.cas.authentication.AuthenticationAccountStateHandler;

265

import org.apereo.cas.authentication.MessageDescriptor;

266

import java.time.LocalDateTime;

267

import java.time.temporal.ChronoUnit;

268

import java.util.ArrayList;

269

import java.util.List;

270

271

public class LdapPasswordPolicyAccountStateHandler

272

implements AuthenticationAccountStateHandler<LdapAuthenticationResult, PasswordPolicyContext> {

273

274

@Override

275

public List<MessageDescriptor> handle(LdapAuthenticationResult response,

276

PasswordPolicyContext configuration) throws Exception {

277

278

List<MessageDescriptor> messages = new ArrayList<>();

279

280

// Check password expiration

281

LocalDateTime expirationDate = response.getPasswordExpirationDate();

282

if (expirationDate != null) {

283

long daysToExpiration = ChronoUnit.DAYS.between(LocalDateTime.now(), expirationDate);

284

285

if (daysToExpiration <= configuration.getPasswordWarningNumberOfDays()) {

286

messages.add(new PasswordExpiringWarningMessageDescriptor(

287

"Your password expires in " + daysToExpiration + " days",

288

(int) daysToExpiration));

289

}

290

}

291

292

// Check account locked status

293

if (response.isAccountLocked()) {

294

throw new AccountLockedException("Account is locked");

295

}

296

297

// Check if password must be changed

298

if (response.isPasswordMustChange()) {

299

throw new AccountPasswordMustChangeException("Password must be changed");

300

}

301

302

// Check login failures

303

int failureCount = response.getLoginFailureCount();

304

if (failureCount >= configuration.getLoginFailures()) {

305

throw new AccountLockedException("Too many login failures: " + failureCount);

306

}

307

308

return messages;

309

}

310

311

@Override

312

public boolean supports(LdapAuthenticationResult response) {

313

return response != null;

314

}

315

}

316

```

317

318

## Password Encoding

319

320

### Password Encoder Interface

321

322

```java { .api }

323

package org.springframework.security.crypto.password;

324

325

public interface PasswordEncoder {

326

String encode(CharSequence rawPassword);

327

boolean matches(CharSequence rawPassword, String encodedPassword);

328

default boolean upgradeEncoding(String encodedPassword) { return false; }

329

}

330

```

331

332

### Password Encoder Utilities

333

334

```java { .api }

335

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

336

337

import org.apereo.cas.configuration.model.core.authentication.PasswordEncoderProperties;

338

import org.springframework.context.ApplicationContext;

339

import org.springframework.security.crypto.password.PasswordEncoder;

340

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

341

import org.springframework.security.crypto.password.NoOpPasswordEncoder;

342

import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

343

import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;

344

345

public final class PasswordEncoderUtils {

346

347

private PasswordEncoderUtils() {}

348

349

public static PasswordEncoder newPasswordEncoder(PasswordEncoderProperties properties,

350

ApplicationContext applicationContext) {

351

352

String type = properties.getType();

353

354

switch (type.toLowerCase()) {

355

case "bcrypt":

356

return new BCryptPasswordEncoder(properties.getStrength());

357

358

case "scrypt":

359

return new SCryptPasswordEncoder(

360

properties.getCpuCost(),

361

properties.getMemoryCost(),

362

properties.getParallelization(),

363

properties.getKeyLength(),

364

properties.getSaltLength());

365

366

case "argon2":

367

return new Argon2PasswordEncoder(

368

properties.getSaltLength(),

369

properties.getHashLength(),

370

properties.getParallelism(),

371

properties.getMemory(),

372

properties.getIterations());

373

374

case "pbkdf2":

375

return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();

376

377

case "noop":

378

return NoOpPasswordEncoder.getInstance();

379

380

case "groovy":

381

return new GroovyPasswordEncoder(properties, applicationContext);

382

383

default:

384

throw new IllegalArgumentException("Unsupported password encoder type: " + type);

385

}

386

}

387

}

388

```

389

390

### Groovy Password Encoder

391

392

Custom password encoder using Groovy scripts:

393

394

```java { .api }

395

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

396

397

import org.apereo.cas.configuration.model.core.authentication.PasswordEncoderProperties;

398

import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;

399

import org.springframework.context.ApplicationContext;

400

import org.springframework.security.crypto.password.PasswordEncoder;

401

402

public class GroovyPasswordEncoder implements PasswordEncoder {

403

404

private final ExecutableCompiledGroovyScript encodeScript;

405

private final ExecutableCompiledGroovyScript matchesScript;

406

407

public GroovyPasswordEncoder(PasswordEncoderProperties properties,

408

ApplicationContext applicationContext) {

409

410

this.encodeScript = ExecutableCompiledGroovyScript.getExecutableCompiledGroovyScript(

411

properties.getEncodeScript(), applicationContext);

412

413

this.matchesScript = ExecutableCompiledGroovyScript.getExecutableCompiledGroovyScript(

414

properties.getMatchesScript(), applicationContext);

415

}

416

417

@Override

418

public String encode(CharSequence rawPassword) {

419

Object result = encodeScript.execute(rawPassword.toString(), String.class);

420

return result != null ? result.toString() : null;

421

}

422

423

@Override

424

public boolean matches(CharSequence rawPassword, String encodedPassword) {

425

Object result = matchesScript.execute(rawPassword.toString(), encodedPassword, Boolean.class);

426

return result instanceof Boolean ? (Boolean) result : false;

427

}

428

429

@Override

430

public boolean upgradeEncoding(String encodedPassword) {

431

return false;

432

}

433

}

434

```

435

436

## Password Policy Configuration

437

438

### Configuration Properties

439

440

```java { .api }

441

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

442

443

import java.io.Serializable;

444

445

public class PasswordPolicyProperties implements Serializable {

446

447

private boolean enabled = false;

448

private boolean warnAll = false;

449

private boolean displayWarningOnFailedLogin = true;

450

private boolean displayWarningOnSuccessfulLogin = true;

451

private int warningDays = 30;

452

private int loginFailures = 5;

453

private String type = "DEFAULT";

454

private String groovyScript;

455

private String endpoint;

456

457

// Account state handling

458

private boolean accountStateHandlingEnabled = true;

459

private String accountStateHandler = "DEFAULT";

460

461

// Password encoding

462

private PasswordEncoderProperties passwordEncoder = new PasswordEncoderProperties();

463

464

// Getters and setters

465

public boolean isEnabled() { return enabled; }

466

public void setEnabled(boolean enabled) { this.enabled = enabled; }

467

468

public boolean isWarnAll() { return warnAll; }

469

public void setWarnAll(boolean warnAll) { this.warnAll = warnAll; }

470

471

public boolean isDisplayWarningOnFailedLogin() { return displayWarningOnFailedLogin; }

472

public void setDisplayWarningOnFailedLogin(boolean displayWarningOnFailedLogin) {

473

this.displayWarningOnFailedLogin = displayWarningOnFailedLogin;

474

}

475

476

public boolean isDisplayWarningOnSuccessfulLogin() { return displayWarningOnSuccessfulLogin; }

477

public void setDisplayWarningOnSuccessfulLogin(boolean displayWarningOnSuccessfulLogin) {

478

this.displayWarningOnSuccessfulLogin = displayWarningOnSuccessfulLogin;

479

}

480

481

public int getWarningDays() { return warningDays; }

482

public void setWarningDays(int warningDays) { this.warningDays = warningDays; }

483

484

public int getLoginFailures() { return loginFailures; }

485

public void setLoginFailures(int loginFailures) { this.loginFailures = loginFailures; }

486

487

// Additional getters and setters...

488

}

489

```

490

491

### Password Encoder Properties

492

493

```java { .api }

494

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

495

496

public class PasswordEncoderProperties implements Serializable {

497

498

private String type = "NONE";

499

private String characterEncoding = "UTF-8";

500

private String encodingAlgorithm = "SHA-256";

501

private int strength = 10;

502

private boolean secret = false;

503

504

// BCrypt specific

505

private int cost = 10;

506

507

// SCrypt specific

508

private int cpuCost = 16384;

509

private int memoryCost = 8;

510

private int parallelization = 1;

511

private int keyLength = 32;

512

private int saltLength = 16;

513

514

// Argon2 specific

515

private int hashLength = 32;

516

private int parallelism = 1;

517

private int memory = 4096;

518

private int iterations = 3;

519

520

// Groovy specific

521

private String encodeScript;

522

private String matchesScript;

523

524

// Getters and setters

525

public String getType() { return type; }

526

public void setType(String type) { this.type = type; }

527

528

public int getStrength() { return strength; }

529

public void setStrength(int strength) { this.strength = strength; }

530

531

// Additional getters and setters...

532

}

533

```

534

535

## Integration Examples

536

537

### Spring Configuration

538

539

```java { .api }

540

@Configuration

541

@EnableConfigurationProperties(CasConfigurationProperties.class)

542

public class PasswordPolicyConfiguration {

543

544

@Bean

545

public PasswordPolicyContext passwordPolicyContext(CasConfigurationProperties casProperties) {

546

PasswordPolicyProperties props = casProperties.getAuthn().getPasswordPolicy();

547

return new PasswordPolicyContext(props);

548

}

549

550

@Bean

551

public AuthenticationPasswordPolicyHandlingStrategy passwordPolicyHandlingStrategy() {

552

return new DefaultPasswordPolicyHandlingStrategy<>();

553

}

554

555

@Bean

556

public PasswordEncoder passwordEncoder(CasConfigurationProperties casProperties,

557

ApplicationContext applicationContext) {

558

PasswordEncoderProperties props = casProperties.getAuthn().getPasswordEncoder();

559

return PasswordEncoderUtils.newPasswordEncoder(props, applicationContext);

560

}

561

562

@Bean

563

public AuthenticationAccountStateHandler accountStateHandler() {

564

return new LdapPasswordPolicyAccountStateHandler();

565

}

566

}

567

```

568

569

### Programmatic Usage

570

571

```java { .api }

572

// Create password policy context

573

PasswordPolicyContext policyContext = new PasswordPolicyContext();

574

policyContext.setPasswordWarningNumberOfDays(30);

575

policyContext.setLoginFailures(5);

576

policyContext.setAlwaysDisplayPasswordExpirationWarning(false);

577

578

// Set up account state handler

579

AuthenticationAccountStateHandler accountStateHandler = new LdapPasswordPolicyAccountStateHandler();

580

policyContext.setAccountStateHandler(accountStateHandler);

581

582

// Create password policy handling strategy

583

AuthenticationPasswordPolicyHandlingStrategy strategy = new DefaultPasswordPolicyHandlingStrategy<>();

584

585

// Handle password policy during authentication

586

try {

587

List<MessageDescriptor> messages = strategy.handle(authenticationResponse, policyContext);

588

589

for (MessageDescriptor message : messages) {

590

if (message instanceof PasswordExpiringWarningMessageDescriptor) {

591

PasswordExpiringWarningMessageDescriptor warning =

592

(PasswordExpiringWarningMessageDescriptor) message;

593

System.out.println("Password expires in " + warning.getDaysToExpiration() + " days");

594

}

595

}

596

} catch (AccountPasswordMustChangeException e) {

597

// Redirect user to password change form

598

System.out.println("Password must be changed");

599

} catch (AccountLockedException e) {

600

// Display account locked message

601

System.out.println("Account is locked");

602

}

603

604

// Password encoding example

605

PasswordEncoder encoder = new BCryptPasswordEncoder(12);

606

String encodedPassword = encoder.encode("plainTextPassword");

607

boolean matches = encoder.matches("plainTextPassword", encodedPassword);

608

609

// Groovy password policy example

610

String groovyScript = """

611

import org.apereo.cas.authentication.MessageDescriptor

612

import org.apereo.cas.authentication.support.password.PasswordExpiringWarningMessageDescriptor

613

614

def handle(response, configuration) {

615

def messages = []

616

617

if (response.passwordExpirationDays <= 7) {

618

messages.add(new PasswordExpiringWarningMessageDescriptor(

619

"Password expires soon!",

620

response.passwordExpirationDays))

621

}

622

623

return messages

624

}

625

626

handle(binding.variables.response, binding.variables.configuration)

627

""";

628

629

ExecutableCompiledGroovyScript script = new ExecutableCompiledGroovyScript(groovyScript);

630

GroovyPasswordPolicyHandlingStrategy groovyStrategy = new GroovyPasswordPolicyHandlingStrategy(script);

631

```

632

633

### Custom Password Policy Implementation

634

635

```java { .api }

636

public class CustomPasswordPolicyHandlingStrategy

637

implements AuthenticationPasswordPolicyHandlingStrategy<CustomAuthResponse, PasswordPolicyContext> {

638

639

@Override

640

public List<MessageDescriptor> handle(CustomAuthResponse response,

641

PasswordPolicyContext configuration) throws Throwable {

642

643

List<MessageDescriptor> messages = new ArrayList<>();

644

645

// Check custom password requirements

646

if (response.isPasswordWeak()) {

647

messages.add(new MessageDescriptor() {

648

public String getCode() { return "password.weak"; }

649

public String getDefaultMessage() { return "Password is too weak"; }

650

public Object[] getParams() { return new Object[0]; }

651

});

652

}

653

654

// Check password age

655

int passwordAge = response.getPasswordAgeDays();

656

if (passwordAge > 90) {

657

messages.add(new PasswordExpiringWarningMessageDescriptor(

658

"Password is old and should be changed",

659

90 - passwordAge));

660

}

661

662

// Check account inactivity

663

if (response.getDaysSinceLastLogin() > 180) {

664

throw new AccountDisabledException("Account inactive for too long");

665

}

666

667

return messages;

668

}

669

}

670

```

671

672

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.