0
# Authentication Policies
1
2
Authentication policies define the rules and requirements that must be satisfied for an authentication attempt to be considered successful. The CAS authentication API provides a comprehensive set of configurable policies that can be combined to implement complex authentication requirements.
3
4
## Core Policy Interface
5
6
```java { .api }
7
package org.apereo.cas.authentication;
8
9
public interface AuthenticationPolicy {
10
AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction) throws Exception;
11
boolean shouldResumeOnFailure(Throwable failure);
12
String getName();
13
void setName(String name);
14
int getOrder();
15
void setOrder(int order);
16
}
17
```
18
19
## Policy Execution Result
20
21
```java { .api }
22
package org.apereo.cas.authentication;
23
24
import java.util.Optional;
25
26
public interface AuthenticationPolicyExecutionResult {
27
boolean isSuccess();
28
Optional<Throwable> getFailure();
29
30
static AuthenticationPolicyExecutionResult success() {
31
return new DefaultAuthenticationPolicyExecutionResult(true, null);
32
}
33
34
static AuthenticationPolicyExecutionResult failure(Throwable failure) {
35
return new DefaultAuthenticationPolicyExecutionResult(false, failure);
36
}
37
}
38
39
class DefaultAuthenticationPolicyExecutionResult implements AuthenticationPolicyExecutionResult {
40
private final boolean success;
41
private final Throwable failure;
42
43
public DefaultAuthenticationPolicyExecutionResult(boolean success, Throwable failure) {
44
this.success = success;
45
this.failure = failure;
46
}
47
48
public boolean isSuccess() { return success; }
49
public Optional<Throwable> getFailure() { return Optional.ofNullable(failure); }
50
}
51
```
52
53
## Base Authentication Policy
54
55
```java { .api }
56
package org.apereo.cas.authentication.policy;
57
58
import org.apereo.cas.authentication.AuthenticationPolicy;
59
import org.springframework.core.Ordered;
60
61
public abstract class BaseAuthenticationPolicy implements AuthenticationPolicy {
62
private int order = Ordered.LOWEST_PRECEDENCE;
63
private String name = getClass().getSimpleName();
64
65
public int getOrder() { return order; }
66
public void setOrder(int order) { this.order = order; }
67
public String getName() { return name; }
68
public void setName(String name) { this.name = name; }
69
70
public boolean shouldResumeOnFailure(Throwable failure) {
71
return false;
72
}
73
}
74
```
75
76
## Authentication Handler Policies
77
78
### All Authentication Handlers Succeeded Policy
79
80
Requires all configured authentication handlers to succeed:
81
82
```java { .api }
83
package org.apereo.cas.authentication.policy;
84
85
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
86
import org.apereo.cas.authentication.AuthenticationTransaction;
87
88
public class AllAuthenticationHandlersSucceededAuthenticationPolicy
89
extends BaseAuthenticationPolicy {
90
91
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
92
throws Exception {
93
94
Set<AuthenticationHandler> handlers = transaction.getAuthenticationHandlers();
95
Map<String, Throwable> failures = transaction.getFailures();
96
97
if (handlers.isEmpty()) {
98
return AuthenticationPolicyExecutionResult.failure(
99
new AuthenticationException("No authentication handlers configured"));
100
}
101
102
for (AuthenticationHandler handler : handlers) {
103
if (failures.containsKey(handler.getName())) {
104
return AuthenticationPolicyExecutionResult.failure(
105
new AuthenticationException("Handler " + handler.getName() + " failed"));
106
}
107
}
108
109
return AuthenticationPolicyExecutionResult.success();
110
}
111
}
112
```
113
114
### Required Authentication Handler Policy
115
116
Requires specific authentication handlers to succeed:
117
118
```java { .api }
119
package org.apereo.cas.authentication.policy;
120
121
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
122
import org.apereo.cas.authentication.AuthenticationTransaction;
123
import java.util.Set;
124
125
public class RequiredAuthenticationHandlerAuthenticationPolicy
126
extends BaseAuthenticationHandlerAuthenticationPolicy {
127
128
private final Set<String> requiredHandlerNames;
129
private final boolean tryAll;
130
131
public RequiredAuthenticationHandlerAuthenticationPolicy(Set<String> requiredHandlerNames) {
132
this(requiredHandlerNames, false);
133
}
134
135
public RequiredAuthenticationHandlerAuthenticationPolicy(Set<String> requiredHandlerNames,
136
boolean tryAll) {
137
this.requiredHandlerNames = requiredHandlerNames;
138
this.tryAll = tryAll;
139
}
140
141
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
142
throws Exception {
143
144
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
145
146
for (String requiredHandler : requiredHandlerNames) {
147
if (!successes.containsKey(requiredHandler)) {
148
return AuthenticationPolicyExecutionResult.failure(
149
new AuthenticationException("Required handler " + requiredHandler + " did not succeed"));
150
}
151
}
152
153
return AuthenticationPolicyExecutionResult.success();
154
}
155
156
public boolean shouldResumeOnFailure(Throwable failure) {
157
return tryAll;
158
}
159
}
160
```
161
162
### Excluded Authentication Handler Policy
163
164
Excludes specific authentication handlers from being required:
165
166
```java { .api }
167
package org.apereo.cas.authentication.policy;
168
169
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
170
import org.apereo.cas.authentication.AuthenticationTransaction;
171
import java.util.Set;
172
173
public class ExcludedAuthenticationHandlerAuthenticationPolicy
174
extends BaseAuthenticationHandlerAuthenticationPolicy {
175
176
private final Set<String> excludedHandlerNames;
177
178
public ExcludedAuthenticationHandlerAuthenticationPolicy(Set<String> excludedHandlerNames) {
179
this.excludedHandlerNames = excludedHandlerNames;
180
}
181
182
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
183
throws Exception {
184
185
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
186
Map<String, Throwable> failures = transaction.getFailures();
187
188
// Check if any non-excluded handlers succeeded
189
boolean hasNonExcludedSuccess = successes.keySet().stream()
190
.anyMatch(handlerName -> !excludedHandlerNames.contains(handlerName));
191
192
if (hasNonExcludedSuccess) {
193
return AuthenticationPolicyExecutionResult.success();
194
}
195
196
// Check if only excluded handlers failed
197
boolean onlyExcludedFailures = failures.keySet().stream()
198
.allMatch(excludedHandlerNames::contains);
199
200
if (onlyExcludedFailures && !successes.isEmpty()) {
201
return AuthenticationPolicyExecutionResult.success();
202
}
203
204
return AuthenticationPolicyExecutionResult.failure(
205
new AuthenticationException("No non-excluded authentication handlers succeeded"));
206
}
207
}
208
```
209
210
## Credential Validation Policies
211
212
### All Credentials Validated Policy
213
214
Requires all provided credentials to be validated successfully:
215
216
```java { .api }
217
package org.apereo.cas.authentication.policy;
218
219
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
220
import org.apereo.cas.authentication.AuthenticationTransaction;
221
import org.apereo.cas.authentication.Credential;
222
223
public class AllCredentialsValidatedAuthenticationPolicy extends BaseAuthenticationPolicy {
224
225
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
226
throws Exception {
227
228
Collection<Credential> credentials = transaction.getCredentials();
229
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
230
231
if (credentials.isEmpty()) {
232
return AuthenticationPolicyExecutionResult.failure(
233
new AuthenticationException("No credentials provided"));
234
}
235
236
for (Credential credential : credentials) {
237
boolean credentialValidated = successes.values().stream()
238
.anyMatch(result -> result.getCredential().equals(credential));
239
240
if (!credentialValidated) {
241
return AuthenticationPolicyExecutionResult.failure(
242
new AuthenticationException("Credential not validated: " + credential.getId()));
243
}
244
}
245
246
return AuthenticationPolicyExecutionResult.success();
247
}
248
}
249
```
250
251
### At Least One Credential Validated Policy
252
253
Requires at least one credential to be validated successfully:
254
255
```java { .api }
256
package org.apereo.cas.authentication.policy;
257
258
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
259
import org.apereo.cas.authentication.AuthenticationTransaction;
260
261
public class AtLeastOneCredentialValidatedAuthenticationPolicy extends BaseAuthenticationPolicy {
262
263
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
264
throws Exception {
265
266
Collection<Credential> credentials = transaction.getCredentials();
267
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
268
269
if (credentials.isEmpty()) {
270
return AuthenticationPolicyExecutionResult.failure(
271
new AuthenticationException("No credentials provided"));
272
}
273
274
if (successes.isEmpty()) {
275
return AuthenticationPolicyExecutionResult.failure(
276
new AuthenticationException("No authentication handlers succeeded"));
277
}
278
279
// At least one credential was validated if we have successes
280
return AuthenticationPolicyExecutionResult.success();
281
}
282
}
283
```
284
285
## Attribute-Based Policies
286
287
### Required Attributes Policy
288
289
Requires specific attributes to be present in the authentication:
290
291
```java { .api }
292
package org.apereo.cas.authentication.policy;
293
294
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
295
import org.apereo.cas.authentication.AuthenticationTransaction;
296
import org.apereo.cas.authentication.principal.Principal;
297
import java.util.Map;
298
import java.util.Set;
299
300
public class RequiredAttributesAuthenticationPolicy extends BaseAuthenticationPolicy {
301
302
private final Map<String, Set<Object>> requiredAttributes;
303
private final boolean tryAll;
304
305
public RequiredAttributesAuthenticationPolicy(Map<String, Set<Object>> requiredAttributes) {
306
this(requiredAttributes, false);
307
}
308
309
public RequiredAttributesAuthenticationPolicy(Map<String, Set<Object>> requiredAttributes,
310
boolean tryAll) {
311
this.requiredAttributes = requiredAttributes;
312
this.tryAll = tryAll;
313
}
314
315
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
316
throws Exception {
317
318
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
319
320
for (AuthenticationHandlerExecutionResult result : successes.values()) {
321
Principal principal = result.getPrincipal();
322
Map<String, List<Object>> principalAttributes = principal.getAttributes();
323
324
for (Map.Entry<String, Set<Object>> requiredEntry : requiredAttributes.entrySet()) {
325
String attributeName = requiredEntry.getKey();
326
Set<Object> requiredValues = requiredEntry.getValue();
327
328
List<Object> actualValues = principalAttributes.get(attributeName);
329
if (actualValues == null || actualValues.isEmpty()) {
330
return AuthenticationPolicyExecutionResult.failure(
331
new AuthenticationException("Required attribute missing: " + attributeName));
332
}
333
334
if (!requiredValues.isEmpty()) {
335
boolean hasRequiredValue = actualValues.stream()
336
.anyMatch(requiredValues::contains);
337
if (!hasRequiredValue) {
338
return AuthenticationPolicyExecutionResult.failure(
339
new AuthenticationException("Required attribute value not found: " + attributeName));
340
}
341
}
342
}
343
}
344
345
return AuthenticationPolicyExecutionResult.success();
346
}
347
348
public boolean shouldResumeOnFailure(Throwable failure) {
349
return tryAll;
350
}
351
}
352
```
353
354
## Prevention Policies
355
356
### Not Prevented Policy
357
358
Ensures that no authentication attempts are marked as prevented:
359
360
```java { .api }
361
package org.apereo.cas.authentication.policy;
362
363
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
364
import org.apereo.cas.authentication.AuthenticationTransaction;
365
import org.apereo.cas.authentication.PreventedException;
366
367
public class NotPreventedAuthenticationPolicy extends BaseAuthenticationPolicy {
368
369
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
370
throws Exception {
371
372
Map<String, Throwable> failures = transaction.getFailures();
373
374
for (Throwable failure : failures.values()) {
375
if (failure instanceof PreventedException) {
376
return AuthenticationPolicyExecutionResult.failure(failure);
377
}
378
}
379
380
return AuthenticationPolicyExecutionResult.success();
381
}
382
}
383
```
384
385
## Principal Uniqueness Policy
386
387
### Unique Principal Policy
388
389
Ensures that all successful authentications resolve to the same principal:
390
391
```java { .api }
392
package org.apereo.cas.authentication.policy;
393
394
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
395
import org.apereo.cas.authentication.AuthenticationTransaction;
396
import org.apereo.cas.authentication.principal.Principal;
397
import java.util.Set;
398
import java.util.stream.Collectors;
399
400
public class UniquePrincipalAuthenticationPolicy extends BaseAuthenticationPolicy {
401
402
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
403
throws Exception {
404
405
Map<String, AuthenticationHandlerExecutionResult> successes = transaction.getSuccesses();
406
407
if (successes.size() <= 1) {
408
return AuthenticationPolicyExecutionResult.success();
409
}
410
411
Set<String> principalIds = successes.values().stream()
412
.map(AuthenticationHandlerExecutionResult::getPrincipal)
413
.map(Principal::getId)
414
.collect(Collectors.toSet());
415
416
if (principalIds.size() > 1) {
417
return AuthenticationPolicyExecutionResult.failure(
418
new MixedPrincipalException("Multiple principals found: " + principalIds));
419
}
420
421
return AuthenticationPolicyExecutionResult.success();
422
}
423
}
424
```
425
426
## Scriptable Policies
427
428
### Groovy Script Policy
429
430
Allows defining authentication policies using Groovy scripts:
431
432
```java { .api }
433
package org.apereo.cas.authentication.policy;
434
435
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
436
import org.apereo.cas.authentication.AuthenticationTransaction;
437
import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;
438
439
public class GroovyScriptAuthenticationPolicy extends BaseAuthenticationPolicy {
440
441
private final ExecutableCompiledGroovyScript watchableScript;
442
443
public GroovyScriptAuthenticationPolicy(ExecutableCompiledGroovyScript watchableScript) {
444
this.watchableScript = watchableScript;
445
}
446
447
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
448
throws Exception {
449
450
Object result = watchableScript.execute(transaction,
451
AuthenticationPolicyExecutionResult.class);
452
453
if (result instanceof AuthenticationPolicyExecutionResult) {
454
return (AuthenticationPolicyExecutionResult) result;
455
} else if (result instanceof Boolean) {
456
return (Boolean) result
457
? AuthenticationPolicyExecutionResult.success()
458
: AuthenticationPolicyExecutionResult.failure(
459
new AuthenticationException("Groovy script returned false"));
460
}
461
462
return AuthenticationPolicyExecutionResult.success();
463
}
464
}
465
```
466
467
## RESTful Policies
468
469
### RESTful Authentication Policy
470
471
Delegates policy decisions to external REST services:
472
473
```java { .api }
474
package org.apereo.cas.authentication.policy;
475
476
import org.apereo.cas.authentication.AuthenticationPolicyExecutionResult;
477
import org.apereo.cas.authentication.AuthenticationTransaction;
478
import org.springframework.http.HttpEntity;
479
import org.springframework.http.HttpMethod;
480
import org.springframework.http.ResponseEntity;
481
import org.springframework.web.client.RestTemplate;
482
import java.util.Map;
483
484
public class RestfulAuthenticationPolicy extends BaseAuthenticationPolicy {
485
486
private final RestTemplate restTemplate;
487
private final String endpoint;
488
489
public RestfulAuthenticationPolicy(RestTemplate restTemplate, String endpoint) {
490
this.restTemplate = restTemplate;
491
this.endpoint = endpoint;
492
}
493
494
public AuthenticationPolicyExecutionResult execute(AuthenticationTransaction transaction)
495
throws Exception {
496
497
try {
498
Map<String, Object> request = Map.of(
499
"transaction", transaction,
500
"credentials", transaction.getCredentials(),
501
"successes", transaction.getSuccesses(),
502
"failures", transaction.getFailures()
503
);
504
505
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(request);
506
ResponseEntity<Map> response = restTemplate.exchange(
507
endpoint, HttpMethod.POST, entity, Map.class);
508
509
Map<String, Object> responseBody = response.getBody();
510
if (responseBody != null) {
511
Boolean success = (Boolean) responseBody.get("success");
512
if (success != null && success) {
513
return AuthenticationPolicyExecutionResult.success();
514
}
515
516
String message = (String) responseBody.get("message");
517
return AuthenticationPolicyExecutionResult.failure(
518
new AuthenticationException(message != null ? message : "REST policy failed"));
519
}
520
521
} catch (Exception e) {
522
return AuthenticationPolicyExecutionResult.failure(
523
new AuthenticationException("REST policy endpoint error", e));
524
}
525
526
return AuthenticationPolicyExecutionResult.failure(
527
new AuthenticationException("REST policy evaluation failed"));
528
}
529
}
530
```
531
532
## Policy Resolvers
533
534
Policy resolvers determine which policies should be applied to authentication transactions:
535
536
```java { .api }
537
package org.apereo.cas.authentication;
538
539
import java.util.Set;
540
541
public interface AuthenticationPolicyResolver {
542
Set<AuthenticationPolicy> resolve(AuthenticationTransaction transaction);
543
boolean supports(AuthenticationTransaction transaction);
544
}
545
```
546
547
### Registered Service Policy Resolver
548
549
Resolves policies based on the registered service configuration:
550
551
```java { .api }
552
package org.apereo.cas.authentication.policy;
553
554
import org.apereo.cas.authentication.AuthenticationPolicy;
555
import org.apereo.cas.authentication.AuthenticationPolicyResolver;
556
import org.apereo.cas.authentication.AuthenticationTransaction;
557
import org.apereo.cas.services.RegisteredService;
558
import org.apereo.cas.services.ServicesManager;
559
import java.util.Set;
560
import java.util.LinkedHashSet;
561
562
public class RegisteredServiceAuthenticationPolicyResolver
563
implements AuthenticationPolicyResolver {
564
565
private final ServicesManager servicesManager;
566
567
public RegisteredServiceAuthenticationPolicyResolver(ServicesManager servicesManager) {
568
this.servicesManager = servicesManager;
569
}
570
571
public Set<AuthenticationPolicy> resolve(AuthenticationTransaction transaction) {
572
Set<AuthenticationPolicy> policies = new LinkedHashSet<>();
573
574
Service service = transaction.getService();
575
if (service != null) {
576
RegisteredService registeredService = servicesManager.findServiceBy(service);
577
if (registeredService != null && registeredService.getAuthenticationPolicy() != null) {
578
579
// Add required handler policy if specified
580
Set<String> requiredHandlers = registeredService.getAuthenticationPolicy()
581
.getRequiredAuthenticationHandlers();
582
if (!requiredHandlers.isEmpty()) {
583
policies.add(new RequiredAuthenticationHandlerAuthenticationPolicy(requiredHandlers));
584
}
585
586
// Add required attribute policy if specified
587
Map<String, Set<Object>> requiredAttributes = registeredService.getAuthenticationPolicy()
588
.getRequiredAttributes();
589
if (!requiredAttributes.isEmpty()) {
590
policies.add(new RequiredAttributesAuthenticationPolicy(requiredAttributes));
591
}
592
}
593
}
594
595
return policies;
596
}
597
598
public boolean supports(AuthenticationTransaction transaction) {
599
return transaction.getService() != null;
600
}
601
}
602
```
603
604
## Configuration Examples
605
606
### Spring Configuration
607
608
```java { .api }
609
@Configuration
610
public class AuthenticationPolicyConfiguration {
611
612
@Bean
613
public AuthenticationPolicy allCredentialsValidatedAuthenticationPolicy() {
614
return new AllCredentialsValidatedAuthenticationPolicy();
615
}
616
617
@Bean
618
public AuthenticationPolicy requiredHandlersAuthenticationPolicy() {
619
Set<String> requiredHandlers = Set.of("LdapAuthenticationHandler", "DatabaseAuthenticationHandler");
620
return new RequiredAuthenticationHandlerAuthenticationPolicy(requiredHandlers);
621
}
622
623
@Bean
624
public AuthenticationPolicy requiredAttributesAuthenticationPolicy() {
625
Map<String, Set<Object>> requiredAttributes = Map.of(
626
"memberOf", Set.of("CN=Users,DC=example,DC=com"),
627
"accountEnabled", Set.of("true")
628
);
629
return new RequiredAttributesAuthenticationPolicy(requiredAttributes);
630
}
631
632
@Bean
633
public AuthenticationPolicyResolver registeredServiceAuthenticationPolicyResolver(
634
ServicesManager servicesManager) {
635
return new RegisteredServiceAuthenticationPolicyResolver(servicesManager);
636
}
637
}
638
```
639
640
### Programmatic Configuration
641
642
```java { .api }
643
// Create policy chain
644
List<AuthenticationPolicy> policies = List.of(
645
new NotPreventedAuthenticationPolicy(),
646
new AtLeastOneCredentialValidatedAuthenticationPolicy(),
647
new UniquePrincipalAuthenticationPolicy()
648
);
649
650
// Register policies in execution plan
651
for (AuthenticationPolicy policy : policies) {
652
authenticationEventExecutionPlan.registerAuthenticationPolicy(policy);
653
}
654
655
// Create Groovy-based policy
656
ExecutableCompiledGroovyScript script = // ... create script
657
GroovyScriptAuthenticationPolicy groovyPolicy = new GroovyScriptAuthenticationPolicy(script);
658
authenticationEventExecutionPlan.registerAuthenticationPolicy(groovyPolicy);
659
660
// Create REST-based policy
661
RestTemplate restTemplate = new RestTemplate();
662
RestfulAuthenticationPolicy restPolicy = new RestfulAuthenticationPolicy(
663
restTemplate, "https://policy.example.com/validate");
664
authenticationEventExecutionPlan.registerAuthenticationPolicy(restPolicy);
665
```
666
667
Authentication policies provide fine-grained control over authentication requirements and enable complex security scenarios by composing multiple policy types to meet specific organizational needs.