0
# Adaptive Authentication
1
2
Adaptive authentication provides risk-based security that dynamically adjusts authentication requirements based on contextual factors such as location, device characteristics, user behavior patterns, and threat intelligence. The CAS authentication API includes comprehensive support for adaptive authentication policies and intelligence services.
3
4
## Core Adaptive Authentication Interface
5
6
```java { .api }
7
package org.apereo.cas.authentication.adaptive;
8
9
import org.apereo.cas.authentication.adaptive.geo.GeoLocationRequest;
10
import org.springframework.webflow.execution.RequestContext;
11
12
public interface AdaptiveAuthenticationPolicy {
13
boolean isAuthenticationRequestAllowed(RequestContext requestContext,
14
String userAgent,
15
GeoLocationRequest location) throws Throwable;
16
}
17
```
18
19
## Default Adaptive Authentication Policy
20
21
```java { .api }
22
package org.apereo.cas.authentication.adaptive;
23
24
import org.apereo.cas.authentication.adaptive.geo.GeoLocationRequest;
25
import org.apereo.cas.authentication.adaptive.geo.GeoLocationResponse;
26
import org.apereo.cas.authentication.adaptive.geo.GeoLocationService;
27
import org.apereo.cas.authentication.adaptive.intel.IPAddressIntelligenceService;
28
import org.apereo.cas.authentication.adaptive.intel.IPAddressIntelligenceResponse;
29
import org.apereo.cas.configuration.model.core.authentication.AdaptiveAuthenticationProperties;
30
import org.springframework.webflow.execution.RequestContext;
31
32
public class DefaultAdaptiveAuthenticationPolicy implements AdaptiveAuthenticationPolicy {
33
34
private final GeoLocationService geoLocationService;
35
private final IPAddressIntelligenceService ipAddressIntelligenceService;
36
private final AdaptiveAuthenticationProperties adaptiveAuthenticationProperties;
37
38
public DefaultAdaptiveAuthenticationPolicy(GeoLocationService geoLocationService,
39
IPAddressIntelligenceService ipAddressIntelligenceService,
40
AdaptiveAuthenticationProperties adaptiveAuthenticationProperties) {
41
this.geoLocationService = geoLocationService;
42
this.ipAddressIntelligenceService = ipAddressIntelligenceService;
43
this.adaptiveAuthenticationProperties = adaptiveAuthenticationProperties;
44
}
45
46
@Override
47
public boolean isAuthenticationRequestAllowed(RequestContext requestContext,
48
String userAgent,
49
GeoLocationRequest location) throws Throwable {
50
51
ClientInfo clientInfo = ClientInfoHolder.getClientInfo();
52
if (clientInfo == null || StringUtils.isBlank(userAgent)) {
53
// Allow if no client information available
54
return true;
55
}
56
57
String clientIp = clientInfo.getClientIpAddress();
58
59
// Check IP address restrictions
60
if (isIpAddressRejected(requestContext, clientIp)) {
61
return false;
62
}
63
64
// Check user agent restrictions
65
if (isUserAgentRejected(userAgent)) {
66
return false;
67
}
68
69
// Check geographical location restrictions
70
if (isGeoLocationRejected(location)) {
71
return false;
72
}
73
74
// Check time-based restrictions
75
if (isTimeBasedAuthenticationRejected()) {
76
return false;
77
}
78
79
return true;
80
}
81
82
private boolean isIpAddressRejected(RequestContext requestContext, String clientIp) {
83
// Check rejected IP patterns
84
Set<String> rejectedIps = adaptiveAuthenticationProperties.getRejectIpAddresses();
85
if (rejectedIps.contains(clientIp)) {
86
return true;
87
}
88
89
// Check with IP intelligence service
90
IPAddressIntelligenceResponse intelligence =
91
ipAddressIntelligenceService.examine(requestContext, clientIp);
92
93
if (intelligence != null && intelligence.getScore() > adaptiveAuthenticationProperties.getIpIntelligence().getRiskThreshold()) {
94
return true;
95
}
96
97
return false;
98
}
99
100
private boolean isUserAgentRejected(String userAgent) {
101
Set<String> rejectedUserAgents = adaptiveAuthenticationProperties.getRejectUserAgents();
102
return rejectedUserAgents.stream()
103
.anyMatch(pattern -> RegexUtils.matches(pattern, userAgent));
104
}
105
106
private boolean isGeoLocationRejected(GeoLocationRequest location) {
107
if (location == null || geoLocationService == null) {
108
return false;
109
}
110
111
try {
112
GeoLocationResponse response = geoLocationService.locate(location);
113
114
if (response != null) {
115
String country = response.getCountry();
116
Set<String> rejectedCountries = adaptiveAuthenticationProperties.getRejectCountries();
117
118
if (rejectedCountries.contains(country)) {
119
return true;
120
}
121
}
122
} catch (Exception e) {
123
// Log error but don't reject authentication due to geo-location failure
124
}
125
126
return false;
127
}
128
129
private boolean isTimeBasedAuthenticationRejected() {
130
LocalTime now = LocalTime.now();
131
LocalTime startTime = adaptiveAuthenticationProperties.getRequireTimePeriod().getStartTime();
132
LocalTime endTime = adaptiveAuthenticationProperties.getRequireTimePeriod().getEndTime();
133
134
if (startTime != null && endTime != null) {
135
return now.isBefore(startTime) || now.isAfter(endTime);
136
}
137
138
return false;
139
}
140
}
141
```
142
143
## IP Address Intelligence Services
144
145
### IP Address Intelligence Interface
146
147
```java { .api }
148
package org.apereo.cas.authentication.adaptive.intel;
149
150
import org.springframework.webflow.execution.RequestContext;
151
152
public interface IPAddressIntelligenceService {
153
IPAddressIntelligenceResponse examine(RequestContext requestContext, String clientIpAddress);
154
}
155
```
156
157
### IP Address Intelligence Response
158
159
```java { .api }
160
package org.apereo.cas.authentication.adaptive.intel;
161
162
import java.util.Map;
163
164
public class IPAddressIntelligenceResponse {
165
166
public enum IPAddressIntelligenceStatus {
167
ALLOWED, BLOCKED, SUSPICIOUS, UNKNOWN
168
}
169
170
private final IPAddressIntelligenceStatus status;
171
private final double score;
172
private final Map<String, Object> details;
173
174
public IPAddressIntelligenceResponse(IPAddressIntelligenceStatus status,
175
double score,
176
Map<String, Object> details) {
177
this.status = status;
178
this.score = score;
179
this.details = details != null ? details : Map.of();
180
}
181
182
public IPAddressIntelligenceStatus getStatus() { return status; }
183
public double getScore() { return score; }
184
public Map<String, Object> getDetails() { return details; }
185
186
public boolean isBanned() {
187
return status == IPAddressIntelligenceStatus.BLOCKED;
188
}
189
190
public boolean isAllowed() {
191
return status == IPAddressIntelligenceStatus.ALLOWED;
192
}
193
194
public boolean isSuspicious() {
195
return status == IPAddressIntelligenceStatus.SUSPICIOUS;
196
}
197
}
198
```
199
200
### Base IP Address Intelligence Service
201
202
```java { .api }
203
package org.apereo.cas.authentication.adaptive.intel;
204
205
import org.springframework.webflow.execution.RequestContext;
206
207
public abstract class BaseIPAddressIntelligenceService implements IPAddressIntelligenceService {
208
209
protected boolean isPrivateIpAddress(String ipAddress) {
210
return ipAddress.startsWith("10.") ||
211
ipAddress.startsWith("192.168.") ||
212
ipAddress.startsWith("172.") ||
213
ipAddress.equals("127.0.0.1") ||
214
ipAddress.equals("localhost");
215
}
216
217
protected double calculateRiskScore(Map<String, Object> factors) {
218
double score = 0.0;
219
220
// Example risk scoring logic
221
if (factors.containsKey("knownMalicious") && (Boolean) factors.get("knownMalicious")) {
222
score += 0.8;
223
}
224
225
if (factors.containsKey("recentActivity")) {
226
Integer activityCount = (Integer) factors.get("recentActivity");
227
if (activityCount > 100) {
228
score += 0.3;
229
}
230
}
231
232
if (factors.containsKey("geoAnomaly") && (Boolean) factors.get("geoAnomaly")) {
233
score += 0.4;
234
}
235
236
return Math.min(1.0, score);
237
}
238
}
239
```
240
241
### Default IP Address Intelligence Service
242
243
```java { .api }
244
package org.apereo.cas.authentication.adaptive.intel;
245
246
import org.springframework.webflow.execution.RequestContext;
247
import java.util.Map;
248
249
public class DefaultIPAddressIntelligenceService extends BaseIPAddressIntelligenceService {
250
251
private final Map<String, IPAddressIntelligenceResponse.IPAddressIntelligenceStatus> knownIpAddresses;
252
253
public DefaultIPAddressIntelligenceService() {
254
this.knownIpAddresses = new ConcurrentHashMap<>();
255
}
256
257
@Override
258
public IPAddressIntelligenceResponse examine(RequestContext requestContext,
259
String clientIpAddress) {
260
261
// Check private/local addresses
262
if (isPrivateIpAddress(clientIpAddress)) {
263
return new IPAddressIntelligenceResponse(
264
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.ALLOWED,
265
0.0,
266
Map.of("reason", "private IP address"));
267
}
268
269
// Check known IP addresses
270
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus knownStatus =
271
knownIpAddresses.get(clientIpAddress);
272
273
if (knownStatus != null) {
274
double score = knownStatus == IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.BLOCKED ? 1.0 : 0.0;
275
return new IPAddressIntelligenceResponse(knownStatus, score, Map.of("source", "known addresses"));
276
}
277
278
// Default to unknown/allowed
279
return new IPAddressIntelligenceResponse(
280
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.UNKNOWN,
281
0.0,
282
Map.of("reason", "no intelligence available"));
283
}
284
285
public void addKnownIpAddress(String ipAddress,
286
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus status) {
287
knownIpAddresses.put(ipAddress, status);
288
}
289
290
public void removeKnownIpAddress(String ipAddress) {
291
knownIpAddresses.remove(ipAddress);
292
}
293
}
294
```
295
296
### BlackDot IP Address Intelligence Service
297
298
Service that integrates with BlackDot threat intelligence:
299
300
```java { .api }
301
package org.apereo.cas.authentication.adaptive.intel;
302
303
import org.springframework.webflow.execution.RequestContext;
304
import org.springframework.web.client.RestTemplate;
305
import java.util.Map;
306
307
public class BlackDotIPAddressIntelligenceService extends BaseIPAddressIntelligenceService {
308
309
private final RestTemplate restTemplate;
310
private final String apiEndpoint;
311
private final String apiKey;
312
313
public BlackDotIPAddressIntelligenceService(RestTemplate restTemplate,
314
String apiEndpoint,
315
String apiKey) {
316
this.restTemplate = restTemplate;
317
this.apiEndpoint = apiEndpoint;
318
this.apiKey = apiKey;
319
}
320
321
@Override
322
public IPAddressIntelligenceResponse examine(RequestContext requestContext,
323
String clientIpAddress) {
324
325
if (isPrivateIpAddress(clientIpAddress)) {
326
return new IPAddressIntelligenceResponse(
327
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.ALLOWED,
328
0.0,
329
Map.of("reason", "private IP address"));
330
}
331
332
try {
333
// Call BlackDot API
334
Map<String, Object> request = Map.of(
335
"ip", clientIpAddress,
336
"apiKey", apiKey
337
);
338
339
Map<String, Object> response = restTemplate.postForObject(
340
apiEndpoint + "/check", request, Map.class);
341
342
if (response != null) {
343
Boolean isBlacklisted = (Boolean) response.get("blacklisted");
344
Double riskScore = (Double) response.get("riskScore");
345
346
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus status;
347
if (isBlacklisted != null && isBlacklisted) {
348
status = IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.BLOCKED;
349
} else if (riskScore != null && riskScore > 0.5) {
350
status = IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.SUSPICIOUS;
351
} else {
352
status = IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.ALLOWED;
353
}
354
355
return new IPAddressIntelligenceResponse(status, riskScore != null ? riskScore : 0.0, response);
356
}
357
358
} catch (Exception e) {
359
// Log error and return unknown status
360
}
361
362
return new IPAddressIntelligenceResponse(
363
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.UNKNOWN,
364
0.0,
365
Map.of("error", "BlackDot API unavailable"));
366
}
367
}
368
```
369
370
### RESTful IP Address Intelligence Service
371
372
Generic REST-based intelligence service:
373
374
```java { .api }
375
package org.apereo.cas.authentication.adaptive.intel;
376
377
import org.springframework.webflow.execution.RequestContext;
378
import org.springframework.web.client.RestTemplate;
379
import org.springframework.http.HttpEntity;
380
import org.springframework.http.HttpHeaders;
381
import org.springframework.http.HttpMethod;
382
import java.util.Map;
383
384
public class RestfulIPAddressIntelligenceService extends BaseIPAddressIntelligenceService {
385
386
private final RestTemplate restTemplate;
387
private final String endpoint;
388
private final HttpHeaders headers;
389
390
public RestfulIPAddressIntelligenceService(RestTemplate restTemplate,
391
String endpoint,
392
HttpHeaders headers) {
393
this.restTemplate = restTemplate;
394
this.endpoint = endpoint;
395
this.headers = headers;
396
}
397
398
@Override
399
public IPAddressIntelligenceResponse examine(RequestContext requestContext,
400
String clientIpAddress) {
401
402
if (isPrivateIpAddress(clientIpAddress)) {
403
return new IPAddressIntelligenceResponse(
404
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.ALLOWED,
405
0.0,
406
Map.of("reason", "private IP address"));
407
}
408
409
try {
410
Map<String, Object> requestBody = Map.of(
411
"ipAddress", clientIpAddress,
412
"context", extractContextFromRequest(requestContext)
413
);
414
415
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
416
417
Map<String, Object> response = restTemplate.exchange(
418
endpoint, HttpMethod.POST, entity, Map.class).getBody();
419
420
if (response != null) {
421
String statusStr = (String) response.get("status");
422
Double score = (Double) response.get("score");
423
424
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus status =
425
parseStatus(statusStr);
426
427
return new IPAddressIntelligenceResponse(status, score != null ? score : 0.0, response);
428
}
429
430
} catch (Exception e) {
431
// Log error and return unknown status
432
}
433
434
return new IPAddressIntelligenceResponse(
435
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.UNKNOWN,
436
0.0,
437
Map.of("error", "REST service unavailable"));
438
}
439
440
private Map<String, Object> extractContextFromRequest(RequestContext requestContext) {
441
// Extract additional context information
442
return Map.of(
443
"userAgent", requestContext.getExternalContext().getNativeRequest().getHeader("User-Agent"),
444
"timestamp", System.currentTimeMillis(),
445
"sessionId", requestContext.getFlowScope().getString("sessionId")
446
);
447
}
448
449
private IPAddressIntelligenceResponse.IPAddressIntelligenceStatus parseStatus(String statusStr) {
450
if (statusStr == null) {
451
return IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.UNKNOWN;
452
}
453
454
switch (statusStr.toUpperCase()) {
455
case "BLOCKED":
456
case "BANNED":
457
return IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.BLOCKED;
458
case "ALLOWED":
459
case "CLEAN":
460
return IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.ALLOWED;
461
case "SUSPICIOUS":
462
case "RISKY":
463
return IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.SUSPICIOUS;
464
default:
465
return IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.UNKNOWN;
466
}
467
}
468
}
469
```
470
471
### Groovy IP Address Intelligence Service
472
473
Scriptable intelligence service using Groovy:
474
475
```java { .api }
476
package org.apereo.cas.authentication.adaptive.intel;
477
478
import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;
479
import org.springframework.webflow.execution.RequestContext;
480
481
public class GroovyIPAddressIntelligenceService extends BaseIPAddressIntelligenceService {
482
483
private final ExecutableCompiledGroovyScript watchableScript;
484
485
public GroovyIPAddressIntelligenceService(ExecutableCompiledGroovyScript watchableScript) {
486
this.watchableScript = watchableScript;
487
}
488
489
@Override
490
public IPAddressIntelligenceResponse examine(RequestContext requestContext,
491
String clientIpAddress) {
492
493
try {
494
Object result = watchableScript.execute(requestContext, clientIpAddress,
495
IPAddressIntelligenceResponse.class);
496
497
if (result instanceof IPAddressIntelligenceResponse) {
498
return (IPAddressIntelligenceResponse) result;
499
}
500
501
} catch (Exception e) {
502
// Log error and return unknown status
503
}
504
505
return new IPAddressIntelligenceResponse(
506
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.UNKNOWN,
507
0.0,
508
Map.of("error", "Groovy script execution failed"));
509
}
510
}
511
```
512
513
## Geographical Location Services
514
515
### Geo-Location Request
516
517
```java { .api }
518
package org.apereo.cas.authentication.adaptive.geo;
519
520
public class GeoLocationRequest {
521
522
private final String ipAddress;
523
private final double latitude;
524
private final double longitude;
525
private final String userAgent;
526
527
public GeoLocationRequest(String ipAddress) {
528
this(ipAddress, 0.0, 0.0, null);
529
}
530
531
public GeoLocationRequest(String ipAddress, double latitude, double longitude, String userAgent) {
532
this.ipAddress = ipAddress;
533
this.latitude = latitude;
534
this.longitude = longitude;
535
this.userAgent = userAgent;
536
}
537
538
public String getIpAddress() { return ipAddress; }
539
public double getLatitude() { return latitude; }
540
public double getLongitude() { return longitude; }
541
public String getUserAgent() { return userAgent; }
542
}
543
```
544
545
### Geo-Location Response
546
547
```java { .api }
548
package org.apereo.cas.authentication.adaptive.geo;
549
550
public class GeoLocationResponse {
551
552
private final double latitude;
553
private final double longitude;
554
private final String city;
555
private final String region;
556
private final String country;
557
private final String countryCode;
558
private final String timezone;
559
private final String organization;
560
561
public GeoLocationResponse(double latitude, double longitude, String city, String region,
562
String country, String countryCode, String timezone, String organization) {
563
this.latitude = latitude;
564
this.longitude = longitude;
565
this.city = city;
566
this.region = region;
567
this.country = country;
568
this.countryCode = countryCode;
569
this.timezone = timezone;
570
this.organization = organization;
571
}
572
573
// Getters
574
public double getLatitude() { return latitude; }
575
public double getLongitude() { return longitude; }
576
public String getCity() { return city; }
577
public String getRegion() { return region; }
578
public String getCountry() { return country; }
579
public String getCountryCode() { return countryCode; }
580
public String getTimezone() { return timezone; }
581
public String getOrganization() { return organization; }
582
}
583
```
584
585
### Geo-Location Service
586
587
```java { .api }
588
package org.apereo.cas.authentication.adaptive.geo;
589
590
public interface GeoLocationService {
591
GeoLocationResponse locate(GeoLocationRequest request) throws Exception;
592
GeoLocationResponse locate(String ipAddress) throws Exception;
593
}
594
```
595
596
## Configuration Properties
597
598
### Adaptive Authentication Properties
599
600
```java { .api }
601
package org.apereo.cas.configuration.model.core.authentication;
602
603
import java.time.LocalTime;
604
import java.util.Set;
605
606
public class AdaptiveAuthenticationProperties implements Serializable {
607
608
private boolean enabled = false;
609
private Set<String> rejectCountries = new LinkedHashSet<>();
610
private Set<String> rejectBrowsers = new LinkedHashSet<>();
611
private Set<String> rejectIpAddresses = new LinkedHashSet<>();
612
private Set<String> rejectUserAgents = new LinkedHashSet<>();
613
614
private TimePeriod requireTimePeriod = new TimePeriod();
615
private IPIntelligence ipIntelligence = new IPIntelligence();
616
private GeoLocation geoLocation = new GeoLocation();
617
618
// Getters and setters
619
public boolean isEnabled() { return enabled; }
620
public void setEnabled(boolean enabled) { this.enabled = enabled; }
621
622
public Set<String> getRejectCountries() { return rejectCountries; }
623
public void setRejectCountries(Set<String> rejectCountries) { this.rejectCountries = rejectCountries; }
624
625
// Additional getters and setters...
626
627
public static class TimePeriod implements Serializable {
628
private LocalTime startTime;
629
private LocalTime endTime;
630
631
public LocalTime getStartTime() { return startTime; }
632
public void setStartTime(LocalTime startTime) { this.startTime = startTime; }
633
634
public LocalTime getEndTime() { return endTime; }
635
public void setEndTime(LocalTime endTime) { this.endTime = endTime; }
636
}
637
638
public static class IPIntelligence implements Serializable {
639
private boolean enabled = false;
640
private String provider = "DEFAULT";
641
private String endpoint;
642
private String apiKey;
643
private double riskThreshold = 0.7;
644
645
// Getters and setters
646
public boolean isEnabled() { return enabled; }
647
public void setEnabled(boolean enabled) { this.enabled = enabled; }
648
649
public double getRiskThreshold() { return riskThreshold; }
650
public void setRiskThreshold(double riskThreshold) { this.riskThreshold = riskThreshold; }
651
652
// Additional getters and setters...
653
}
654
655
public static class GeoLocation implements Serializable {
656
private boolean enabled = false;
657
private String provider = "DEFAULT";
658
private String endpoint;
659
private String apiKey;
660
661
// Getters and setters...
662
}
663
}
664
```
665
666
## Integration Examples
667
668
### Spring Configuration
669
670
```java { .api }
671
@Configuration
672
@EnableConfigurationProperties(CasConfigurationProperties.class)
673
public class AdaptiveAuthenticationConfiguration {
674
675
@Bean
676
@ConditionalOnMissingBean
677
public IPAddressIntelligenceService ipAddressIntelligenceService(
678
CasConfigurationProperties casProperties) {
679
680
AdaptiveAuthenticationProperties.IPIntelligence props =
681
casProperties.getAuthn().getAdaptive().getIpIntelligence();
682
683
if (!props.isEnabled()) {
684
return new DefaultIPAddressIntelligenceService();
685
}
686
687
switch (props.getProvider().toUpperCase()) {
688
case "BLACKDOT":
689
return new BlackDotIPAddressIntelligenceService(
690
new RestTemplate(), props.getEndpoint(), props.getApiKey());
691
case "REST":
692
return new RestfulIPAddressIntelligenceService(
693
new RestTemplate(), props.getEndpoint(), createHeaders(props));
694
default:
695
return new DefaultIPAddressIntelligenceService();
696
}
697
}
698
699
@Bean
700
@ConditionalOnMissingBean
701
public AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy(
702
GeoLocationService geoLocationService,
703
IPAddressIntelligenceService ipAddressIntelligenceService,
704
CasConfigurationProperties casProperties) {
705
706
return new DefaultAdaptiveAuthenticationPolicy(
707
geoLocationService,
708
ipAddressIntelligenceService,
709
casProperties.getAuthn().getAdaptive());
710
}
711
712
private HttpHeaders createHeaders(AdaptiveAuthenticationProperties.IPIntelligence props) {
713
HttpHeaders headers = new HttpHeaders();
714
headers.set("Authorization", "Bearer " + props.getApiKey());
715
headers.set("Content-Type", "application/json");
716
return headers;
717
}
718
}
719
```
720
721
### Programmatic Usage
722
723
```java { .api }
724
// Create adaptive authentication policy
725
AdaptiveAuthenticationProperties properties = new AdaptiveAuthenticationProperties();
726
properties.setEnabled(true);
727
properties.getRejectCountries().addAll(Set.of("XX", "YY"));
728
properties.getRejectUserAgents().add(".*bot.*");
729
properties.getIpIntelligence().setEnabled(true);
730
properties.getIpIntelligence().setRiskThreshold(0.8);
731
732
IPAddressIntelligenceService intelligenceService = new DefaultIPAddressIntelligenceService();
733
GeoLocationService geoLocationService = new DefaultGeoLocationService();
734
735
AdaptiveAuthenticationPolicy policy = new DefaultAdaptiveAuthenticationPolicy(
736
geoLocationService, intelligenceService, properties);
737
738
// Use in authentication flow
739
RequestContext requestContext = // ... get from WebFlow
740
String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
741
GeoLocationRequest geoRequest = new GeoLocationRequest("203.0.113.1");
742
743
boolean allowed = policy.isAuthenticationRequestAllowed(requestContext, userAgent, geoRequest);
744
745
if (!allowed) {
746
// Block authentication or require additional verification
747
throw new AdaptiveAuthenticationException("Authentication request blocked by adaptive policy");
748
}
749
750
// Custom IP intelligence implementation
751
IPAddressIntelligenceService customService = new IPAddressIntelligenceService() {
752
@Override
753
public IPAddressIntelligenceResponse examine(RequestContext requestContext, String clientIpAddress) {
754
// Custom threat intelligence logic
755
if (isKnownMaliciousIp(clientIpAddress)) {
756
return new IPAddressIntelligenceResponse(
757
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.BLOCKED,
758
1.0,
759
Map.of("reason", "Known malicious IP")
760
);
761
}
762
763
if (isHighRiskCountry(clientIpAddress)) {
764
return new IPAddressIntelligenceResponse(
765
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.SUSPICIOUS,
766
0.6,
767
Map.of("reason", "High-risk country")
768
);
769
}
770
771
return new IPAddressIntelligenceResponse(
772
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.ALLOWED,
773
0.1,
774
Map.of("reason", "Clean IP")
775
);
776
}
777
778
private boolean isKnownMaliciousIp(String ip) {
779
// Check against threat intelligence feeds
780
return false;
781
}
782
783
private boolean isHighRiskCountry(String ip) {
784
// Geo-location and country risk assessment
785
return false;
786
}
787
};
788
789
// Groovy-based adaptive policy
790
String groovyScript = """
791
import org.apereo.cas.authentication.adaptive.intel.IPAddressIntelligenceResponse
792
793
def examine(requestContext, clientIpAddress) {
794
// Custom Groovy intelligence logic
795
if (clientIpAddress.startsWith("10.")) {
796
return new IPAddressIntelligenceResponse(
797
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.ALLOWED,
798
0.0,
799
[reason: "Internal IP"]
800
)
801
}
802
803
if (clientIpAddress.contains("suspicious")) {
804
return new IPAddressIntelligenceResponse(
805
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.BLOCKED,
806
0.9,
807
[reason: "Suspicious pattern detected"]
808
)
809
}
810
811
return new IPAddressIntelligenceResponse(
812
IPAddressIntelligenceResponse.IPAddressIntelligenceStatus.UNKNOWN,
813
0.3,
814
[reason: "Unknown IP"]
815
)
816
}
817
818
examine(binding.variables.requestContext, binding.variables.clientIpAddress)
819
""";
820
821
ExecutableCompiledGroovyScript script = new ExecutableCompiledGroovyScript(groovyScript);
822
GroovyIPAddressIntelligenceService groovyService = new GroovyIPAddressIntelligenceService(script);
823
```
824
825
### Risk-Based Authentication Flow
826
827
```java { .api }
828
public class RiskBasedAuthenticationHandler extends AbstractAuthenticationHandler {
829
830
private final AdaptiveAuthenticationPolicy adaptivePolicy;
831
private final AuthenticationHandler delegateHandler;
832
833
public RiskBasedAuthenticationHandler(AdaptiveAuthenticationPolicy adaptivePolicy,
834
AuthenticationHandler delegateHandler) {
835
super("RiskBasedHandler", null, new DefaultPrincipalFactory(), 0);
836
this.adaptivePolicy = adaptivePolicy;
837
this.delegateHandler = delegateHandler;
838
}
839
840
@Override
841
public AuthenticationHandlerExecutionResult authenticate(Credential credential)
842
throws GeneralSecurityException, PreventedException {
843
844
RequestContext requestContext = RequestContextHolder.getRequestContext();
845
String userAgent = extractUserAgent(requestContext);
846
GeoLocationRequest geoRequest = createGeoLocationRequest(requestContext);
847
848
try {
849
boolean allowed = adaptivePolicy.isAuthenticationRequestAllowed(
850
requestContext, userAgent, geoRequest);
851
852
if (!allowed) {
853
// Risk too high - require additional authentication
854
throw new MultiFactorAuthenticationRequiredException(
855
"Additional authentication required due to risk assessment");
856
}
857
858
// Proceed with normal authentication
859
return delegateHandler.authenticate(credential);
860
861
} catch (Exception e) {
862
throw new PreventedException("Adaptive authentication failed", e);
863
}
864
}
865
866
@Override
867
public boolean supports(Credential credential) {
868
return delegateHandler.supports(credential);
869
}
870
871
private String extractUserAgent(RequestContext requestContext) {
872
return requestContext.getExternalContext().getNativeRequest().getHeader("User-Agent");
873
}
874
875
private GeoLocationRequest createGeoLocationRequest(RequestContext requestContext) {
876
ClientInfo clientInfo = ClientInfoHolder.getClientInfo();
877
return new GeoLocationRequest(clientInfo.getClientIpAddress());
878
}
879
}
880
```
881
882
Adaptive authentication provides sophisticated risk assessment capabilities that enhance security by dynamically adjusting authentication requirements based on contextual factors, threat intelligence, and behavioral analysis.