0
# Credential Handling
1
2
Credentials represent the authentication information provided by users, such as usernames/passwords, certificates, tokens, or other authentication artifacts. The CAS authentication API provides a comprehensive framework for handling various credential types with validation, security, and extensibility features.
3
4
## Core Credential Interface
5
6
```java { .api }
7
package org.apereo.cas.authentication;
8
9
import java.io.Serializable;
10
11
public interface Credential extends Serializable {
12
String getId();
13
CredentialMetadata getCredentialMetadata();
14
}
15
```
16
17
## Mutable Credential Interface
18
19
```java { .api }
20
package org.apereo.cas.authentication;
21
22
public interface MutableCredential extends Credential {
23
void setId(String id);
24
}
25
```
26
27
## Credential Metadata
28
29
```java { .api }
30
package org.apereo.cas.authentication;
31
32
import java.time.ZonedDateTime;
33
import java.util.Map;
34
35
public interface CredentialMetadata {
36
String getId();
37
Map<String, Object> getProperties();
38
void addProperty(String name, Object value);
39
ZonedDateTime getAuthenticationDate();
40
void setAuthenticationDate(ZonedDateTime authenticationDate);
41
Class<? extends Credential> getCredentialClass();
42
}
43
```
44
45
## Abstract Credential Base Class
46
47
```java { .api }
48
package org.apereo.cas.authentication.credential;
49
50
import org.apereo.cas.authentication.Credential;
51
import org.apereo.cas.authentication.CredentialMetadata;
52
import org.apereo.cas.authentication.metadata.BasicCredentialMetadata;
53
import org.springframework.binding.validation.ValidationContext;
54
55
public abstract class AbstractCredential implements Credential {
56
private CredentialMetadata credentialMetadata;
57
58
public CredentialMetadata getCredentialMetadata() {
59
if (credentialMetadata == null) {
60
this.credentialMetadata = new BasicCredentialMetadata(this);
61
}
62
return this.credentialMetadata;
63
}
64
65
public void setCredentialMetadata(CredentialMetadata credentialMetadata) {
66
this.credentialMetadata = credentialMetadata;
67
}
68
69
public boolean isValid() {
70
return StringUtils.isNotBlank(getId());
71
}
72
73
public void validate(ValidationContext context) {
74
// Default validation - subclasses can override
75
if (!isValid()) {
76
context.getMessageContext().addMessage(
77
new MessageBuilder()
78
.error()
79
.source(getClass().getSimpleName())
80
.defaultText("Invalid credential")
81
.code("credential.invalid")
82
.build());
83
}
84
}
85
}
86
```
87
88
## Username/Password Credentials
89
90
### Basic Username/Password Credential
91
92
The most common credential type supporting username/password authentication:
93
94
```java { .api }
95
package org.apereo.cas.authentication.credential;
96
97
import org.apereo.cas.authentication.MutableCredential;
98
import org.springframework.binding.validation.ValidationContext;
99
import jakarta.validation.constraints.Size;
100
import java.util.Map;
101
102
public class UsernamePasswordCredential extends AbstractCredential implements MutableCredential {
103
104
public static final String AUTHENTICATION_ATTRIBUTE_PASSWORD = "credential";
105
106
@Size(min = 1, message = "username.required")
107
private String username;
108
109
@Size(min = 1, message = "password.required")
110
private char[] password;
111
112
private String source;
113
private Map<String, Object> customFields = new LinkedHashMap<>();
114
115
// Constructors
116
public UsernamePasswordCredential() {}
117
118
public UsernamePasswordCredential(String username, String password) {
119
this.username = username;
120
assignPassword(StringUtils.defaultString(password));
121
}
122
123
public UsernamePasswordCredential(String username, char[] password,
124
String source, Map<String, Object> customFields) {
125
this.username = username;
126
this.password = password.clone();
127
this.source = source;
128
this.customFields = new HashMap<>(customFields);
129
}
130
131
// MutableCredential implementation
132
public String getId() {
133
return this.username;
134
}
135
136
public void setId(String id) {
137
this.username = id;
138
}
139
140
// Password handling
141
public char[] getPassword() {
142
return password != null ? password.clone() : null;
143
}
144
145
public void setPassword(char[] password) {
146
this.password = password != null ? password.clone() : null;
147
}
148
149
public String toPassword() {
150
return password != null ? new String(password) : null;
151
}
152
153
public void assignPassword(String password) {
154
if (password != null) {
155
this.password = password.toCharArray();
156
} else {
157
this.password = null;
158
}
159
}
160
161
// Properties
162
public String getUsername() { return username; }
163
public void setUsername(String username) { this.username = username; }
164
165
public String getSource() { return source; }
166
public void setSource(String source) { this.source = source; }
167
168
public Map<String, Object> getCustomFields() { return customFields; }
169
public void setCustomFields(Map<String, Object> customFields) {
170
this.customFields = customFields;
171
}
172
173
// Validation
174
@Override
175
public void validate(ValidationContext context) {
176
super.validate(context);
177
178
MessageContext messageContext = context.getMessageContext();
179
if (!"submit".equalsIgnoreCase(context.getUserEvent()) ||
180
messageContext.hasErrorMessages()) {
181
return;
182
}
183
184
// Validate username
185
if (StringUtils.isBlank(username)) {
186
messageContext.addMessage(new MessageBuilder()
187
.error()
188
.source("username")
189
.defaultText("Username is required")
190
.code("username.required")
191
.build());
192
}
193
194
// Validate password
195
if (password == null || password.length == 0) {
196
messageContext.addMessage(new MessageBuilder()
197
.error()
198
.source("password")
199
.defaultText("Password is required")
200
.code("password.required")
201
.build());
202
}
203
}
204
205
@Override
206
public boolean equals(Object obj) {
207
if (obj == null || !this.getClass().equals(obj.getClass())) {
208
return false;
209
}
210
UsernamePasswordCredential other = (UsernamePasswordCredential) obj;
211
return Objects.equals(this.username, other.username);
212
}
213
214
@Override
215
public int hashCode() {
216
return Objects.hashCode(this.username);
217
}
218
219
@Override
220
public String toString() {
221
return String.format("UsernamePasswordCredential[username=%s, source=%s]",
222
username, source);
223
}
224
}
225
```
226
227
### Remember-Me Username/Password Credential
228
229
Extended credential that supports "Remember Me" functionality:
230
231
```java { .api }
232
package org.apereo.cas.authentication.credential;
233
234
import org.apereo.cas.authentication.RememberMeCredential;
235
236
public class RememberMeUsernamePasswordCredential extends UsernamePasswordCredential
237
implements RememberMeCredential {
238
239
public static final String AUTHENTICATION_ATTRIBUTE_REMEMBER_ME = "rememberMe";
240
241
private boolean rememberMe;
242
243
// Constructors
244
public RememberMeUsernamePasswordCredential() {
245
super();
246
}
247
248
public RememberMeUsernamePasswordCredential(String username, String password,
249
boolean rememberMe) {
250
super(username, password);
251
this.rememberMe = rememberMe;
252
}
253
254
// RememberMeCredential implementation
255
public boolean isRememberMe() {
256
return this.rememberMe;
257
}
258
259
public void setRememberMe(boolean rememberMe) {
260
this.rememberMe = rememberMe;
261
}
262
263
@Override
264
public String toString() {
265
return String.format("RememberMeUsernamePasswordCredential[username=%s, rememberMe=%s]",
266
getUsername(), rememberMe);
267
}
268
}
269
```
270
271
## Token-Based Credentials
272
273
### One-Time Password Credential
274
275
For OTP-based authentication systems:
276
277
```java { .api }
278
package org.apereo.cas.authentication.credential;
279
280
import jakarta.validation.constraints.Size;
281
282
public class OneTimePasswordCredential extends AbstractCredential {
283
284
@Size(min = 1, message = "oneTimePassword.required")
285
private String password;
286
287
private String id;
288
289
// Constructors
290
public OneTimePasswordCredential() {}
291
292
public OneTimePasswordCredential(String id, String password) {
293
this.id = id;
294
this.password = password;
295
}
296
297
public String getId() { return id; }
298
public void setId(String id) { this.id = id; }
299
300
public String getPassword() { return password; }
301
public void setPassword(String password) { this.password = password; }
302
303
@Override
304
public void validate(ValidationContext context) {
305
super.validate(context);
306
307
MessageContext messageContext = context.getMessageContext();
308
309
if (StringUtils.isBlank(id)) {
310
messageContext.addMessage(new MessageBuilder()
311
.error()
312
.source("id")
313
.defaultText("ID is required")
314
.code("id.required")
315
.build());
316
}
317
318
if (StringUtils.isBlank(password)) {
319
messageContext.addMessage(new MessageBuilder()
320
.error()
321
.source("password")
322
.defaultText("One-time password is required")
323
.code("oneTimePassword.required")
324
.build());
325
}
326
}
327
}
328
```
329
330
### One-Time Token Credential
331
332
For single-use token authentication:
333
334
```java { .api }
335
package org.apereo.cas.authentication.credential;
336
337
import jakarta.validation.constraints.Size;
338
339
public class OneTimeTokenCredential extends AbstractCredential {
340
341
@Size(min = 1, message = "token.required")
342
private String token;
343
344
private String id;
345
346
// Constructors
347
public OneTimeTokenCredential() {}
348
349
public OneTimeTokenCredential(String id, String token) {
350
this.id = id;
351
this.token = token;
352
}
353
354
public String getId() { return id; }
355
public void setId(String id) { this.id = id; }
356
357
public String getToken() { return token; }
358
public void setToken(String token) { this.token = token; }
359
360
@Override
361
public void validate(ValidationContext context) {
362
super.validate(context);
363
364
MessageContext messageContext = context.getMessageContext();
365
366
if (StringUtils.isBlank(token)) {
367
messageContext.addMessage(new MessageBuilder()
368
.error()
369
.source("token")
370
.defaultText("Token is required")
371
.code("token.required")
372
.build());
373
}
374
}
375
}
376
```
377
378
## Service-Based Credentials
379
380
### Basic Identifiable Credential
381
382
Simple credential with just an identifier:
383
384
```java { .api }
385
package org.apereo.cas.authentication.credential;
386
387
public class BasicIdentifiableCredential extends AbstractCredential {
388
389
private String id;
390
391
// Constructors
392
public BasicIdentifiableCredential() {}
393
394
public BasicIdentifiableCredential(String id) {
395
this.id = id;
396
}
397
398
public String getId() { return id; }
399
public void setId(String id) { this.id = id; }
400
401
@Override
402
public String toString() {
403
return String.format("BasicIdentifiableCredential[id=%s]", id);
404
}
405
}
406
```
407
408
### HTTP-Based Service Credential
409
410
Credential for HTTP service callback authentication:
411
412
```java { .api }
413
package org.apereo.cas.authentication.credential;
414
415
import org.apereo.cas.services.RegisteredService;
416
import java.net.URL;
417
418
public class HttpBasedServiceCredential extends AbstractCredential {
419
420
private final URL callbackUrl;
421
private final RegisteredService registeredService;
422
423
public HttpBasedServiceCredential(URL callbackUrl, RegisteredService registeredService) {
424
this.callbackUrl = callbackUrl;
425
this.registeredService = registeredService;
426
}
427
428
public String getId() {
429
return this.callbackUrl != null ? this.callbackUrl.toExternalForm() : null;
430
}
431
432
public URL getCallbackUrl() { return callbackUrl; }
433
434
public RegisteredService getService() { return registeredService; }
435
436
@Override
437
public String toString() {
438
return String.format("HttpBasedServiceCredential[callbackUrl=%s, service=%s]",
439
callbackUrl, registeredService != null ? registeredService.getName() : "null");
440
}
441
}
442
```
443
444
## Credential Validation
445
446
### Validation Context Usage
447
448
```java { .api }
449
// Validate credential in Spring WebFlow context
450
ValidationContext context = new DefaultValidationContext(requestContext, "credentialValidation");
451
UsernamePasswordCredential credential = new UsernamePasswordCredential("user", "pass");
452
453
credential.validate(context);
454
455
if (context.getMessageContext().hasErrorMessages()) {
456
// Handle validation errors
457
for (Message message : context.getMessageContext().getAllMessages()) {
458
System.out.println("Validation error: " + message.getText());
459
}
460
}
461
```
462
463
### Custom Validation
464
465
```java { .api }
466
public class CustomUsernamePasswordCredential extends UsernamePasswordCredential {
467
468
@Override
469
public void validate(ValidationContext context) {
470
super.validate(context);
471
472
MessageContext messageContext = context.getMessageContext();
473
474
// Custom username validation
475
String username = getUsername();
476
if (username != null && username.contains("@")) {
477
if (!isValidEmail(username)) {
478
messageContext.addMessage(new MessageBuilder()
479
.error()
480
.source("username")
481
.defaultText("Invalid email format")
482
.code("username.invalid.email")
483
.build());
484
}
485
}
486
487
// Password complexity validation
488
String password = toPassword();
489
if (password != null && !isPasswordComplex(password)) {
490
messageContext.addMessage(new MessageBuilder()
491
.error()
492
.source("password")
493
.defaultText("Password does not meet complexity requirements")
494
.code("password.complexity.insufficient")
495
.build());
496
}
497
}
498
499
private boolean isValidEmail(String email) {
500
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
501
}
502
503
private boolean isPasswordComplex(String password) {
504
return password.length() >= 8 &&
505
password.matches(".*[A-Z].*") &&
506
password.matches(".*[a-z].*") &&
507
password.matches(".*[0-9].*") &&
508
password.matches(".*[!@#$%^&*()].*");
509
}
510
}
511
```
512
513
## Credential Security
514
515
### Password Security
516
517
```java { .api }
518
public class SecureUsernamePasswordCredential extends UsernamePasswordCredential {
519
520
@Override
521
public void setPassword(char[] password) {
522
super.setPassword(password);
523
524
// Clear the input parameter for security
525
if (password != null) {
526
Arrays.fill(password, '\0');
527
}
528
}
529
530
@Override
531
public void assignPassword(String password) {
532
super.assignPassword(password);
533
534
// Note: String parameter cannot be cleared,
535
// prefer char[] methods for security
536
}
537
538
public void clearPassword() {
539
char[] password = getPassword();
540
if (password != null) {
541
Arrays.fill(password, '\0');
542
}
543
setPassword(null);
544
}
545
}
546
```
547
548
### Credential Encryption
549
550
```java { .api }
551
public class EncryptedUsernamePasswordCredential extends UsernamePasswordCredential {
552
553
private final Cipher cipher;
554
555
public EncryptedUsernamePasswordCredential(Cipher cipher) {
556
this.cipher = cipher;
557
}
558
559
@Override
560
public void setPassword(char[] password) {
561
if (password != null && cipher != null) {
562
try {
563
byte[] encrypted = cipher.doFinal(new String(password).getBytes());
564
String encodedPassword = Base64.getEncoder().encodeToString(encrypted);
565
super.assignPassword(encodedPassword);
566
567
// Clear original password
568
Arrays.fill(password, '\0');
569
} catch (Exception e) {
570
throw new RuntimeException("Failed to encrypt password", e);
571
}
572
} else {
573
super.setPassword(password);
574
}
575
}
576
577
public char[] getDecryptedPassword() throws Exception {
578
String encryptedPassword = toPassword();
579
if (encryptedPassword != null && cipher != null) {
580
byte[] encrypted = Base64.getDecoder().decode(encryptedPassword);
581
byte[] decrypted = cipher.doFinal(encrypted);
582
return new String(decrypted).toCharArray();
583
}
584
return getPassword();
585
}
586
}
587
```
588
589
## Credential Metadata Management
590
591
### Basic Credential Metadata
592
593
```java { .api }
594
package org.apereo.cas.authentication.metadata;
595
596
import org.apereo.cas.authentication.Credential;
597
import org.apereo.cas.authentication.CredentialMetadata;
598
import java.time.ZonedDateTime;
599
import java.util.Map;
600
import java.util.concurrent.ConcurrentHashMap;
601
602
public class BasicCredentialMetadata implements CredentialMetadata {
603
604
private final String id;
605
private final Class<? extends Credential> credentialClass;
606
private final Map<String, Object> properties = new ConcurrentHashMap<>();
607
private ZonedDateTime authenticationDate;
608
609
public BasicCredentialMetadata(Credential credential) {
610
this.id = credential.getId();
611
this.credentialClass = credential.getClass();
612
this.authenticationDate = ZonedDateTime.now();
613
}
614
615
public String getId() { return id; }
616
617
public Class<? extends Credential> getCredentialClass() { return credentialClass; }
618
619
public Map<String, Object> getProperties() { return properties; }
620
621
public void addProperty(String name, Object value) {
622
properties.put(name, value);
623
}
624
625
public ZonedDateTime getAuthenticationDate() { return authenticationDate; }
626
627
public void setAuthenticationDate(ZonedDateTime authenticationDate) {
628
this.authenticationDate = authenticationDate;
629
}
630
}
631
```
632
633
## Integration Interfaces
634
635
### Remember Me Support
636
637
```java { .api }
638
package org.apereo.cas.authentication;
639
640
public interface RememberMeCredential {
641
String AUTHENTICATION_ATTRIBUTE_REMEMBER_ME = "rememberMe";
642
643
boolean isRememberMe();
644
void setRememberMe(boolean rememberMe);
645
}
646
```
647
648
### Credential Selection
649
650
```java { .api }
651
// Create credential selection predicates
652
Predicate<Credential> usernamePasswordPredicate =
653
credential -> credential instanceof UsernamePasswordCredential;
654
655
Predicate<Credential> tokenCredentialPredicate =
656
credential -> credential instanceof OneTimeTokenCredential;
657
658
Predicate<Credential> sourcePredicate = credential -> {
659
if (credential instanceof UsernamePasswordCredential) {
660
UsernamePasswordCredential upc = (UsernamePasswordCredential) credential;
661
return "LDAP".equals(upc.getSource());
662
}
663
return false;
664
};
665
666
// Using predicates for handler selection
667
public class ConditionalAuthenticationHandler extends AbstractAuthenticationHandler {
668
669
private final Predicate<Credential> credentialSelector;
670
671
public ConditionalAuthenticationHandler(Predicate<Credential> credentialSelector) {
672
this.credentialSelector = credentialSelector;
673
}
674
675
@Override
676
public boolean supports(Credential credential) {
677
return credentialSelector.test(credential);
678
}
679
}
680
```
681
682
## Configuration Examples
683
684
### Spring Configuration
685
686
```java { .api }
687
@Configuration
688
public class CredentialConfiguration {
689
690
@Bean
691
public Predicate<Credential> usernamePasswordCredentialSelector() {
692
return credential -> credential instanceof UsernamePasswordCredential;
693
}
694
695
@Bean
696
public Predicate<Credential> rememberMeCredentialSelector() {
697
return credential -> credential instanceof RememberMeCredential &&
698
((RememberMeCredential) credential).isRememberMe();
699
}
700
701
@Bean
702
public MessageSource credentialValidationMessageSource() {
703
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
704
messageSource.setBasename("credential-validation-messages");
705
return messageSource;
706
}
707
}
708
```
709
710
### Programmatic Usage
711
712
```java { .api }
713
// Create and validate credentials
714
UsernamePasswordCredential credential = new UsernamePasswordCredential("user", "password");
715
credential.setSource("LDAP");
716
credential.getCustomFields().put("department", "IT");
717
718
// Validate credential
719
ValidationContext validationContext = new DefaultValidationContext();
720
credential.validate(validationContext);
721
722
if (!validationContext.getMessageContext().hasErrorMessages()) {
723
// Credential is valid, proceed with authentication
724
AuthenticationHandler handler = getAppropriateHandler(credential);
725
AuthenticationHandlerExecutionResult result = handler.authenticate(credential);
726
}
727
728
// Working with remember-me credentials
729
RememberMeUsernamePasswordCredential rememberMeCredential =
730
new RememberMeUsernamePasswordCredential("user", "password", true);
731
732
// Check remember-me status
733
if (rememberMeCredential.isRememberMe()) {
734
// Apply remember-me logic
735
}
736
737
// Secure password handling
738
char[] password = "securePassword".toCharArray();
739
try {
740
UsernamePasswordCredential secureCredential =
741
new UsernamePasswordCredential("user", password);
742
// Use credential
743
} finally {
744
// Always clear sensitive data
745
Arrays.fill(password, '\0');
746
}
747
```
748
749
Credential handling provides the foundation for secure authentication data management, supporting various credential types, validation mechanisms, and security practices essential for enterprise authentication systems.