0
# Text Processing
1
2
Message sanitization and text processing utilities for secure handling of user input, system messages, and principal name transformations in CAS applications.
3
4
## Message Sanitization
5
6
### MessageSanitizer Interface
7
8
Core interface for message sanitization providing secure handling of potentially malicious or sensitive content.
9
10
```java { .api }
11
public interface MessageSanitizer {
12
13
// Primary sanitization method
14
String sanitize(String message);
15
16
// Batch sanitization
17
Collection<String> sanitize(Collection<String> messages);
18
Map<String, String> sanitize(Map<String, String> messageMap);
19
20
// Configuration methods
21
boolean isEnabled();
22
Set<String> getSupportedPatterns();
23
}
24
```
25
26
### DefaultMessageSanitizer
27
28
Default implementation providing comprehensive message sanitization with configurable rules and patterns.
29
30
```java { .api }
31
public class DefaultMessageSanitizer implements MessageSanitizer {
32
33
// Configuration
34
private final boolean enabled;
35
private final Set<Pattern> sanitizationPatterns;
36
private final Map<String, String> replacementMap;
37
38
// Constructors
39
public DefaultMessageSanitizer();
40
public DefaultMessageSanitizer(boolean enabled);
41
public DefaultMessageSanitizer(Set<Pattern> patterns, Map<String, String> replacements);
42
43
// MessageSanitizer implementation
44
@Override
45
public String sanitize(String message);
46
47
@Override
48
public Collection<String> sanitize(Collection<String> messages);
49
50
@Override
51
public Map<String, String> sanitize(Map<String, String> messageMap);
52
53
@Override
54
public boolean isEnabled();
55
56
@Override
57
public Set<String> getSupportedPatterns();
58
59
// Configuration methods
60
public void addSanitizationPattern(Pattern pattern, String replacement);
61
public void addSanitizationRule(String regex, String replacement);
62
public void removeSanitizationPattern(Pattern pattern);
63
}
64
```
65
66
### Usage Examples
67
68
**Basic message sanitization:**
69
```java
70
@Service
71
public class UserInputService {
72
73
private final MessageSanitizer messageSanitizer;
74
75
public UserInputService(MessageSanitizer messageSanitizer) {
76
this.messageSanitizer = messageSanitizer;
77
}
78
79
public String processUserComment(String rawComment) {
80
if (StringUtils.isBlank(rawComment)) {
81
return rawComment;
82
}
83
84
// Sanitize potentially malicious content
85
String sanitized = messageSanitizer.sanitize(rawComment);
86
87
// Additional validation
88
if (sanitized.length() > 1000) {
89
throw new IllegalArgumentException("Comment too long after sanitization");
90
}
91
92
return sanitized;
93
}
94
95
public Map<String, String> processFormData(Map<String, String> formData) {
96
// Sanitize all form field values
97
Map<String, String> sanitized = messageSanitizer.sanitize(formData);
98
99
// Log sanitization if content was changed
100
formData.forEach((key, original) -> {
101
String cleaned = sanitized.get(key);
102
if (!Objects.equals(original, cleaned)) {
103
log.info("Sanitized field '{}': '{}' -> '{}'", key, original, cleaned);
104
}
105
});
106
107
return sanitized;
108
}
109
110
public List<String> processErrorMessages(List<String> errorMessages) {
111
// Sanitize error messages before display
112
Collection<String> sanitized = messageSanitizer.sanitize(errorMessages);
113
return new ArrayList<>(sanitized);
114
}
115
}
116
```
117
118
**Custom sanitizer configuration:**
119
```java
120
@Configuration
121
public class SanitizationConfiguration {
122
123
@Bean
124
public MessageSanitizer customMessageSanitizer() {
125
DefaultMessageSanitizer sanitizer = new DefaultMessageSanitizer(true);
126
127
// Remove script tags
128
sanitizer.addSanitizationRule("<script[^>]*>.*?</script>", "");
129
130
// Remove potentially dangerous HTML attributes
131
sanitizer.addSanitizationRule("\\s*javascript\\s*:", "");
132
sanitizer.addSanitizationRule("\\s*vbscript\\s*:", "");
133
sanitizer.addSanitizationRule("\\s*onload\\s*=", "");
134
sanitizer.addSanitizationRule("\\s*onerror\\s*=", "");
135
136
// Sanitize SQL injection attempts
137
sanitizer.addSanitizationRule("(?i)(union|select|insert|update|delete|drop)\\s", "");
138
139
// Remove excessive whitespace
140
sanitizer.addSanitizationRule("\\s+", " ");
141
142
// Replace sensitive patterns
143
sanitizer.addSanitizationRule("password\\s*[=:]\\s*\\S+", "password=[REDACTED]");
144
sanitizer.addSanitizationRule("token\\s*[=:]\\s*\\S+", "token=[REDACTED]");
145
146
return sanitizer;
147
}
148
149
@Bean
150
@ConditionalOnProperty(name = "cas.message.sanitization.strict", havingValue = "true")
151
public MessageSanitizer strictMessageSanitizer() {
152
DefaultMessageSanitizer sanitizer = new DefaultMessageSanitizer(true);
153
154
// Strict HTML removal
155
sanitizer.addSanitizationRule("<[^>]+>", "");
156
157
// Allow only alphanumeric and basic punctuation
158
sanitizer.addSanitizationRule("[^a-zA-Z0-9\\s.,!?-]", "");
159
160
return sanitizer;
161
}
162
}
163
```
164
165
## MessageSanitationContributor Interface
166
167
Interface for contributing custom sanitization rules and patterns to the message sanitization process.
168
169
```java { .api }
170
public interface MessageSanitationContributor {
171
172
// Contribution methods
173
Collection<Pattern> getPatterns();
174
Map<String, String> getReplacements();
175
176
// Priority and ordering
177
int getOrder();
178
179
// Conditional application
180
boolean supports(String messageType);
181
boolean isEnabled();
182
}
183
```
184
185
### TicketCatalogMessageSanitationContributor
186
187
Specialized contributor for ticket-related message sanitization.
188
189
```java { .api }
190
public class TicketCatalogMessageSanitationContributor implements MessageSanitationContributor {
191
192
// Constructor
193
public TicketCatalogMessageSanitationContributor();
194
195
@Override
196
public Collection<Pattern> getPatterns();
197
198
@Override
199
public Map<String, String> getReplacements();
200
201
@Override
202
public int getOrder();
203
204
@Override
205
public boolean supports(String messageType);
206
207
@Override
208
public boolean isEnabled();
209
}
210
```
211
212
### Usage Examples
213
214
**Custom sanitization contributor:**
215
```java
216
@Component
217
@Order(100)
218
public class AuthenticationMessageSanitationContributor implements MessageSanitationContributor {
219
220
private final Collection<Pattern> patterns;
221
private final Map<String, String> replacements;
222
223
public AuthenticationMessageSanitationContributor() {
224
this.patterns = Arrays.asList(
225
Pattern.compile("(?i)credential\\s*[=:]\\s*\\S+"),
226
Pattern.compile("(?i)password\\s*[=:]\\s*\\S+"),
227
Pattern.compile("(?i)secret\\s*[=:]\\s*\\S+"),
228
Pattern.compile("TGT-\\d+-\\S+"), // Ticket Granting Tickets
229
Pattern.compile("ST-\\d+-\\S+") // Service Tickets
230
);
231
232
this.replacements = Map.of(
233
"(?i)credential\\s*[=:]\\s*\\S+", "credential=[PROTECTED]",
234
"(?i)password\\s*[=:]\\s*\\S+", "password=[PROTECTED]",
235
"(?i)secret\\s*[=:]\\s*\\S+", "secret=[PROTECTED]",
236
"TGT-\\d+-\\S+", "TGT-***",
237
"ST-\\d+-\\S+", "ST-***"
238
);
239
}
240
241
@Override
242
public Collection<Pattern> getPatterns() {
243
return patterns;
244
}
245
246
@Override
247
public Map<String, String> getReplacements() {
248
return replacements;
249
}
250
251
@Override
252
public int getOrder() {
253
return 100;
254
}
255
256
@Override
257
public boolean supports(String messageType) {
258
return messageType != null && (
259
messageType.contains("authentication") ||
260
messageType.contains("login") ||
261
messageType.contains("ticket")
262
);
263
}
264
265
@Override
266
public boolean isEnabled() {
267
return true;
268
}
269
}
270
271
@Component
272
@Order(200)
273
public class SessionMessageSanitationContributor implements MessageSanitationContributor {
274
275
@Override
276
public Collection<Pattern> getPatterns() {
277
return Arrays.asList(
278
Pattern.compile("JSESSIONID=[^;\\s]+"),
279
Pattern.compile("sessionId=[^;\\s]+"),
280
Pattern.compile("sid=[^;\\s]+")
281
);
282
}
283
284
@Override
285
public Map<String, String> getReplacements() {
286
return Map.of(
287
"JSESSIONID=[^;\\s]+", "JSESSIONID=[HIDDEN]",
288
"sessionId=[^;\\s]+", "sessionId=[HIDDEN]",
289
"sid=[^;\\s]+", "sid=[HIDDEN]"
290
);
291
}
292
293
@Override
294
public int getOrder() {
295
return 200;
296
}
297
298
@Override
299
public boolean supports(String messageType) {
300
return true; // Apply to all message types
301
}
302
303
@Override
304
public boolean isEnabled() {
305
return true;
306
}
307
}
308
```
309
310
**Composite sanitizer with contributors:**
311
```java
312
@Service
313
public class CompositeMessageSanitizationService {
314
315
private final List<MessageSanitationContributor> contributors;
316
private final MessageSanitizer baseSanitizer;
317
318
public CompositeMessageSanitizationService(
319
List<MessageSanitationContributor> contributors,
320
MessageSanitizer baseSanitizer) {
321
322
// Sort contributors by order
323
this.contributors = contributors.stream()
324
.filter(MessageSanitationContributor::isEnabled)
325
.sorted(Comparator.comparingInt(MessageSanitationContributor::getOrder))
326
.collect(Collectors.toList());
327
328
this.baseSanitizer = baseSanitizer;
329
}
330
331
public String sanitizeMessage(String message, String messageType) {
332
if (StringUtils.isBlank(message)) {
333
return message;
334
}
335
336
String sanitized = message;
337
338
// Apply base sanitization first
339
if (baseSanitizer.isEnabled()) {
340
sanitized = baseSanitizer.sanitize(sanitized);
341
}
342
343
// Apply contributor-specific sanitization
344
for (MessageSanitationContributor contributor : contributors) {
345
if (contributor.supports(messageType)) {
346
sanitized = applyContributorSanitization(sanitized, contributor);
347
}
348
}
349
350
return sanitized;
351
}
352
353
private String applyContributorSanitization(String message, MessageSanitationContributor contributor) {
354
String sanitized = message;
355
356
// Apply replacement patterns
357
Map<String, String> replacements = contributor.getReplacements();
358
for (Map.Entry<String, String> entry : replacements.entrySet()) {
359
sanitized = sanitized.replaceAll(entry.getKey(), entry.getValue());
360
}
361
362
// Apply additional patterns
363
Collection<Pattern> patterns = contributor.getPatterns();
364
for (Pattern pattern : patterns) {
365
sanitized = pattern.matcher(sanitized).replaceAll("[SANITIZED]");
366
}
367
368
return sanitized;
369
}
370
}
371
```
372
373
## Principal Name Transformers
374
375
### PrincipalNameTransformer Interface
376
377
Interface for transforming principal names during authentication processing.
378
379
```java { .api }
380
public interface PrincipalNameTransformer {
381
382
// Primary transformation method
383
String transform(String formUserId);
384
385
// Configuration methods
386
String getName();
387
boolean isEnabled();
388
}
389
```
390
391
### Core Transformer Implementations
392
393
#### NoOpPrincipalNameTransformer
394
395
No-operation transformer that returns the input unchanged.
396
397
```java { .api }
398
public class NoOpPrincipalNameTransformer implements PrincipalNameTransformer {
399
400
@Override
401
public String transform(String formUserId);
402
403
@Override
404
public String getName();
405
406
@Override
407
public boolean isEnabled();
408
}
409
```
410
411
#### ConvertCasePrincipalNameTransformer
412
413
Transformer for case conversion (uppercase/lowercase).
414
415
```java { .api }
416
public class ConvertCasePrincipalNameTransformer implements PrincipalNameTransformer {
417
418
// Case conversion modes
419
public enum CaseConversion {
420
UPPERCASE, LOWERCASE, NONE
421
}
422
423
// Constructor
424
public ConvertCasePrincipalNameTransformer(CaseConversion conversion);
425
426
@Override
427
public String transform(String formUserId);
428
}
429
```
430
431
#### PrefixSuffixPrincipalNameTransformer
432
433
Transformer for adding prefixes and/or suffixes to principal names.
434
435
```java { .api }
436
public class PrefixSuffixPrincipalNameTransformer implements PrincipalNameTransformer {
437
438
// Constructor
439
public PrefixSuffixPrincipalNameTransformer(String prefix, String suffix);
440
441
@Override
442
public String transform(String formUserId);
443
}
444
```
445
446
#### RegexPrincipalNameTransformer
447
448
Transformer using regular expressions for complex name transformations.
449
450
```java { .api }
451
public class RegexPrincipalNameTransformer implements PrincipalNameTransformer {
452
453
// Constructor
454
public RegexPrincipalNameTransformer(String pattern, String replacement);
455
public RegexPrincipalNameTransformer(Pattern compiledPattern, String replacement);
456
457
@Override
458
public String transform(String formUserId);
459
}
460
```
461
462
#### BlockingPrincipalNameTransformer
463
464
Transformer that blocks/rejects certain principal names.
465
466
```java { .api }
467
public class BlockingPrincipalNameTransformer implements PrincipalNameTransformer {
468
469
// Constructor
470
public BlockingPrincipalNameTransformer(Set<String> blockedNames);
471
public BlockingPrincipalNameTransformer(Pattern blockingPattern);
472
473
@Override
474
public String transform(String formUserId);
475
}
476
```
477
478
#### ChainingPrincipalNameTransformer
479
480
Transformer that chains multiple transformers in sequence.
481
482
```java { .api }
483
public class ChainingPrincipalNameTransformer implements PrincipalNameTransformer {
484
485
// Constructor
486
public ChainingPrincipalNameTransformer(List<PrincipalNameTransformer> transformers);
487
488
@Override
489
public String transform(String formUserId);
490
491
// Chain management
492
public void addTransformer(PrincipalNameTransformer transformer);
493
public List<PrincipalNameTransformer> getTransformers();
494
}
495
```
496
497
#### GroovyPrincipalNameTransformer
498
499
Transformer using Groovy scripts for dynamic transformations.
500
501
```java { .api }
502
public class GroovyPrincipalNameTransformer implements PrincipalNameTransformer {
503
504
// Constructor
505
public GroovyPrincipalNameTransformer(String groovyScript);
506
public GroovyPrincipalNameTransformer(Resource groovyScriptResource);
507
508
@Override
509
public String transform(String formUserId);
510
}
511
```
512
513
### Usage Examples
514
515
**Basic transformers:**
516
```java
517
@Configuration
518
public class PrincipalTransformerConfiguration {
519
520
@Bean
521
@ConditionalOnProperty(name = "cas.principal.transform.case", havingValue = "lowercase")
522
public PrincipalNameTransformer lowercaseTransformer() {
523
return new ConvertCasePrincipalNameTransformer(CaseConversion.LOWERCASE);
524
}
525
526
@Bean
527
@ConditionalOnProperty(name = "cas.principal.transform.domain.enabled", havingValue = "true")
528
public PrincipalNameTransformer domainTransformer(
529
@Value("${cas.principal.transform.domain.suffix:@example.com}") String suffix) {
530
531
return new PrefixSuffixPrincipalNameTransformer("", suffix);
532
}
533
534
@Bean
535
public PrincipalNameTransformer emailToUsernameTransformer() {
536
// Transform email addresses to usernames
537
return new RegexPrincipalNameTransformer("(.+)@.+", "$1");
538
}
539
540
@Bean
541
public PrincipalNameTransformer blockingTransformer() {
542
// Block administrative accounts
543
Set<String> blockedNames = Set.of("admin", "root", "administrator", "guest");
544
return new BlockingPrincipalNameTransformer(blockedNames);
545
}
546
}
547
```
548
549
**Complex chained transformations:**
550
```java
551
@Configuration
552
public class ComplexPrincipalTransformation {
553
554
@Bean
555
public PrincipalNameTransformer chainedPrincipalTransformer() {
556
List<PrincipalNameTransformer> transformers = Arrays.asList(
557
// 1. Block dangerous usernames first
558
new BlockingPrincipalNameTransformer(Set.of("admin", "root")),
559
560
// 2. Extract username from email if present
561
new RegexPrincipalNameTransformer("(.+)@.+", "$1"),
562
563
// 3. Convert to lowercase
564
new ConvertCasePrincipalNameTransformer(CaseConversion.LOWERCASE),
565
566
// 4. Remove special characters
567
new RegexPrincipalNameTransformer("[^a-z0-9._-]", ""),
568
569
// 5. Add domain if not present
570
new RegexPrincipalNameTransformer("^([^@]+)$", "$1@company.com")
571
);
572
573
return new ChainingPrincipalNameTransformer(transformers);
574
}
575
}
576
```
577
578
**Groovy-based dynamic transformation:**
579
```java
580
@Bean
581
@ConditionalOnProperty(name = "cas.principal.transform.groovy.enabled", havingValue = "true")
582
public PrincipalNameTransformer groovyPrincipalTransformer() {
583
String groovyScript = """
584
// Dynamic principal transformation script
585
586
// Input: formUserId (String)
587
// Output: transformed principal name (String)
588
589
if (formUserId == null || formUserId.isEmpty()) {
590
return formUserId
591
}
592
593
def transformed = formUserId.toLowerCase()
594
595
// Handle email addresses
596
if (transformed.contains('@')) {
597
def parts = transformed.split('@')
598
def username = parts[0]
599
def domain = parts[1]
600
601
// Map domains to internal format
602
def domainMappings = [
603
'gmail.com': 'external',
604
'company.com': 'internal',
605
'contractor.com': 'contractor'
606
]
607
608
def mappedDomain = domainMappings[domain] ?: 'unknown'
609
return username + '.' + mappedDomain
610
}
611
612
// Handle employee ID format (EMP123456)
613
if (transformed.startsWith('emp')) {
614
return transformed.substring(3)
615
}
616
617
// Default transformation
618
return transformed.replaceAll('[^a-z0-9._-]', '')
619
""";
620
621
return new GroovyPrincipalNameTransformer(groovyScript);
622
}
623
```
624
625
**Usage in authentication handler:**
626
```java
627
@Component
628
public class CustomAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
629
630
private final PrincipalNameTransformer principalTransformer;
631
private final UserRepository userRepository;
632
633
public CustomAuthenticationHandler(
634
PrincipalNameTransformer principalTransformer,
635
UserRepository userRepository) {
636
this.principalTransformer = principalTransformer;
637
this.userRepository = userRepository;
638
}
639
640
@Override
641
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(
642
UsernamePasswordCredential credential,
643
String originalPassword) throws GeneralSecurityException {
644
645
try {
646
// Transform the principal name
647
String originalUsername = credential.getUsername();
648
String transformedUsername = principalTransformer.transform(originalUsername);
649
650
log.debug("Transformed principal: '{}' -> '{}'", originalUsername, transformedUsername);
651
652
// Authenticate with transformed username
653
User user = userRepository.findByUsername(transformedUsername);
654
if (user == null) {
655
throw new AccountNotFoundException("User not found: " + transformedUsername);
656
}
657
658
if (!passwordEncoder.matches(originalPassword, user.getPasswordHash())) {
659
throw new FailedLoginException("Invalid credentials");
660
}
661
662
// Create principal with transformed name
663
Principal principal = principalFactory.createPrincipal(
664
transformedUsername,
665
user.getAttributes()
666
);
667
668
return createHandlerResult(credential, principal);
669
670
} catch (Exception e) {
671
log.error("Authentication failed for user: {}", credential.getUsername(), e);
672
throw new FailedLoginException("Authentication failed", e);
673
}
674
}
675
}
676
```
677
678
This text processing library provides comprehensive capabilities for secure message handling and flexible principal name transformations, essential for production CAS deployments with security and compliance requirements.