0
# JWT Utilities
1
2
JSON Web Token signing and encryption utilities with support for various algorithms, key management strategies, and integration with CAS authentication flows.
3
4
## JsonWebTokenSigner
5
6
JWT signing utilities for creating and validating signed tokens with configurable algorithms and key management.
7
8
```java { .api }
9
public class JsonWebTokenSigner {
10
11
// Supported algorithms (excludes 'none' for security)
12
public static final Set<String> ALGORITHM_ALL_EXCEPT_NONE = Set.of(
13
"HS256", "HS384", "HS512", // HMAC algorithms
14
"RS256", "RS384", "RS512", // RSA algorithms
15
"ES256", "ES384", "ES512", // ECDSA algorithms
16
"PS256", "PS384", "PS512" // RSA-PSS algorithms
17
);
18
19
// Constructors
20
public JsonWebTokenSigner(Key signingKey);
21
public JsonWebTokenSigner(Key signingKey, String algorithm);
22
public JsonWebTokenSigner(String secretKey, String algorithm);
23
24
// Signing operations
25
public byte[] sign(byte[] value);
26
public String sign(JwtClaims claims);
27
public String sign(JwtClaims claims, Map<String, Object> headers);
28
29
// Validation operations
30
public boolean verify(String token);
31
public JwtClaims parse(String token);
32
public JwtClaims parseAndVerify(String token);
33
34
// Configuration
35
public String getAlgorithm();
36
public Key getSigningKey();
37
}
38
```
39
40
### Usage Examples
41
42
**Basic JWT signing:**
43
```java
44
@Service
45
public class TokenService {
46
47
private final JsonWebTokenSigner jwtSigner;
48
49
public TokenService(@Value("${cas.jwt.signing-secret}") String secret) {
50
// HMAC-based signer with HS256
51
this.jwtSigner = new JsonWebTokenSigner(secret, "HS256");
52
}
53
54
public String createAccessToken(String userId, List<String> roles) {
55
// Create JWT claims
56
JwtClaims claims = new JwtClaims();
57
claims.setSubject(userId);
58
claims.setIssuer("cas-server");
59
claims.setAudience("cas-clients");
60
claims.setExpirationTimeMinutesInTheFuture(60); // 1 hour
61
claims.setIssuedAtToNow();
62
claims.setNotBeforeMinutesInThePast(1);
63
claims.setJwtId(UUID.randomUUID().toString());
64
65
// Add custom claims
66
claims.setClaim("roles", roles);
67
claims.setClaim("tokenType", "access");
68
69
// Sign and return token
70
return jwtSigner.sign(claims);
71
}
72
73
public String createRefreshToken(String userId) {
74
JwtClaims claims = new JwtClaims();
75
claims.setSubject(userId);
76
claims.setIssuer("cas-server");
77
claims.setExpirationTimeMinutesInTheFuture(10080); // 7 days
78
claims.setIssuedAtToNow();
79
claims.setClaim("tokenType", "refresh");
80
81
return jwtSigner.sign(claims);
82
}
83
84
public boolean validateToken(String token) {
85
try {
86
return jwtSigner.verify(token);
87
} catch (Exception e) {
88
log.warn("Token validation failed", e);
89
return false;
90
}
91
}
92
93
public Optional<TokenInfo> parseToken(String token) {
94
try {
95
JwtClaims claims = jwtSigner.parseAndVerify(token);
96
97
TokenInfo info = new TokenInfo();
98
info.setUserId(claims.getSubject());
99
info.setRoles((List<String>) claims.getClaimValue("roles"));
100
info.setTokenType((String) claims.getClaimValue("tokenType"));
101
info.setExpiresAt(claims.getExpirationTime().getValueInMillis());
102
103
return Optional.of(info);
104
105
} catch (Exception e) {
106
log.error("Failed to parse token", e);
107
return Optional.empty();
108
}
109
}
110
}
111
```
112
113
**RSA-based JWT signing:**
114
```java
115
@Configuration
116
public class JwtConfiguration {
117
118
@Bean
119
public JsonWebTokenSigner rsaJwtSigner() throws Exception {
120
// Load RSA private key for signing
121
PrivateKey privateKey = loadPrivateKey("classpath:jwt-private.pem");
122
return new JsonWebTokenSigner(privateKey, "RS256");
123
}
124
125
@Bean
126
public JsonWebTokenValidator rsaJwtValidator() throws Exception {
127
// Load RSA public key for validation
128
PublicKey publicKey = loadPublicKey("classpath:jwt-public.pem");
129
return new JsonWebTokenValidator(publicKey, "RS256");
130
}
131
132
private PrivateKey loadPrivateKey(String location) throws Exception {
133
Resource resource = resourceLoader.getResource(location);
134
return AbstractCipherExecutor.extractPrivateKeyFromResource(resource.getURI().toString());
135
}
136
137
private PublicKey loadPublicKey(String location) throws Exception {
138
Resource resource = resourceLoader.getResource(location);
139
return AbstractCipherExecutor.extractPublicKeyFromResource(resource.getURI().toString());
140
}
141
}
142
```
143
144
**Custom headers and claims:**
145
```java
146
@Service
147
public class AdvancedJwtService {
148
149
private final JsonWebTokenSigner jwtSigner;
150
151
public String createTokenWithCustomHeaders(String userId, String clientId) {
152
// Create claims
153
JwtClaims claims = new JwtClaims();
154
claims.setSubject(userId);
155
claims.setAudience(clientId);
156
claims.setExpirationTimeMinutesInTheFuture(30);
157
claims.setIssuedAtToNow();
158
159
// Custom claims for CAS context
160
claims.setClaim("authenticationMethod", "password");
161
claims.setClaim("serviceTicket", generateServiceTicket());
162
claims.setClaim("principalAttributes", getUserAttributes(userId));
163
164
// Custom headers
165
Map<String, Object> headers = new HashMap<>();
166
headers.put("typ", "JWT");
167
headers.put("alg", jwtSigner.getAlgorithm());
168
headers.put("kid", "cas-2024-01"); // Key ID for rotation
169
headers.put("cas-version", "7.2.4");
170
171
return jwtSigner.sign(claims, headers);
172
}
173
174
public String createServiceToken(String service, Map<String, Object> attributes) {
175
JwtClaims claims = new JwtClaims();
176
claims.setAudience(service);
177
claims.setIssuer("https://cas.example.com");
178
claims.setExpirationTimeMinutesInTheFuture(5); // Short-lived for services
179
claims.setIssuedAtToNow();
180
181
// Service-specific claims
182
claims.setClaim("serviceAttributes", attributes);
183
claims.setClaim("grantType", "service_ticket");
184
185
Map<String, Object> headers = Map.of(
186
"typ", "service+jwt",
187
"purpose", "service-validation"
188
);
189
190
return jwtSigner.sign(claims, headers);
191
}
192
}
193
```
194
195
## JsonWebTokenEncryptor
196
197
JWT encryption utilities for creating encrypted tokens with various key management strategies.
198
199
```java { .api }
200
public class JsonWebTokenEncryptor {
201
202
// Supported encryption algorithms (excludes 'none' for security)
203
public static final List<String> ALGORITHM_ALL_EXCEPT_NONE = List.of(
204
"RSA1_5", "RSA-OAEP", "RSA-OAEP-256", // RSA key encryption
205
"A128KW", "A192KW", "A256KW", // AES key wrapping
206
"dir", // Direct encryption
207
"A128GCMKW", "A192GCMKW", "A256GCMKW" // AES-GCM key wrapping
208
);
209
210
// Constructors
211
public JsonWebTokenEncryptor(Key encryptionKey);
212
public JsonWebTokenEncryptor(Key encryptionKey, String keyAlgorithm, String contentAlgorithm);
213
public JsonWebTokenEncryptor(String secretKey, String keyAlgorithm, String contentAlgorithm);
214
215
// Encryption operations
216
public String encrypt(Serializable payload);
217
public String encrypt(Object payload, Map<String, Object> headers);
218
public String encrypt(String plaintext);
219
220
// Decryption operations
221
public String decrypt(String encryptedToken);
222
public <T> T decrypt(String encryptedToken, Class<T> type);
223
public Map<String, Object> decryptToMap(String encryptedToken);
224
225
// Configuration
226
public String getKeyAlgorithm();
227
public String getContentAlgorithm();
228
public Key getEncryptionKey();
229
}
230
```
231
232
### Usage Examples
233
234
**Basic JWT encryption:**
235
```java
236
@Service
237
public class SecureTokenService {
238
239
private final JsonWebTokenEncryptor jwtEncryptor;
240
private final JsonWebTokenSigner jwtSigner;
241
242
public SecureTokenService(
243
@Value("${cas.jwt.encryption-secret}") String encryptionSecret,
244
@Value("${cas.jwt.signing-secret}") String signingSecret) {
245
246
// AES-GCM encryption
247
this.jwtEncryptor = new JsonWebTokenEncryptor(
248
encryptionSecret,
249
"dir", // Direct key agreement
250
"A256GCM" // AES-256-GCM content encryption
251
);
252
253
// HMAC signing
254
this.jwtSigner = new JsonWebTokenSigner(signingSecret, "HS256");
255
}
256
257
public String createSecureToken(SensitiveData data) {
258
// First encrypt the sensitive data
259
String encryptedData = jwtEncryptor.encrypt(data);
260
261
// Then sign the encrypted token
262
JwtClaims claims = new JwtClaims();
263
claims.setSubject(data.getUserId());
264
claims.setExpirationTimeMinutesInTheFuture(15);
265
claims.setIssuedAtToNow();
266
claims.setClaim("encryptedPayload", encryptedData);
267
268
return jwtSigner.sign(claims);
269
}
270
271
public Optional<SensitiveData> extractSecureData(String token) {
272
try {
273
// Verify and parse the signed token
274
JwtClaims claims = jwtSigner.parseAndVerify(token);
275
String encryptedPayload = (String) claims.getClaimValue("encryptedPayload");
276
277
// Decrypt the payload
278
SensitiveData data = jwtEncryptor.decrypt(encryptedPayload, SensitiveData.class);
279
return Optional.of(data);
280
281
} catch (Exception e) {
282
log.error("Failed to extract secure data from token", e);
283
return Optional.empty();
284
}
285
}
286
}
287
```
288
289
**RSA-based encryption with key rotation:**
290
```java
291
@Service
292
public class KeyRotationAwareJwtService {
293
294
private final Map<String, JsonWebTokenEncryptor> encryptors;
295
private final Map<String, JsonWebTokenSigner> signers;
296
private String currentKeyId;
297
298
public KeyRotationAwareJwtService() {
299
this.encryptors = new ConcurrentHashMap<>();
300
this.signers = new ConcurrentHashMap<>();
301
initializeKeys();
302
}
303
304
private void initializeKeys() {
305
try {
306
// Load current keys
307
String keyId = "2024-01";
308
PublicKey publicKey = loadPublicKey("jwt-public-" + keyId + ".pem");
309
PrivateKey privateKey = loadPrivateKey("jwt-private-" + keyId + ".pem");
310
311
// RSA encryption (recipient uses private key to decrypt)
312
encryptors.put(keyId, new JsonWebTokenEncryptor(publicKey, "RSA-OAEP", "A256GCM"));
313
314
// RSA signing (sender uses private key to sign)
315
signers.put(keyId, new JsonWebTokenSigner(privateKey, "RS256"));
316
317
this.currentKeyId = keyId;
318
319
} catch (Exception e) {
320
throw new RuntimeException("Failed to initialize JWT keys", e);
321
}
322
}
323
324
public String createRotationAwareToken(Object payload) {
325
// Encrypt with current public key
326
JsonWebTokenEncryptor encryptor = encryptors.get(currentKeyId);
327
328
Map<String, Object> headers = Map.of(
329
"kid", currentKeyId, // Key ID for rotation
330
"alg", "RSA-OAEP",
331
"enc", "A256GCM"
332
);
333
334
String encryptedToken = encryptor.encrypt(payload, headers);
335
336
// Sign with current private key
337
JwtClaims wrapperClaims = new JwtClaims();
338
wrapperClaims.setIssuedAtToNow();
339
wrapperClaims.setExpirationTimeMinutesInTheFuture(60);
340
wrapperClaims.setClaim("encryptedJwt", encryptedToken);
341
342
JsonWebTokenSigner signer = signers.get(currentKeyId);
343
Map<String, Object> signHeaders = Map.of("kid", currentKeyId);
344
345
return signer.sign(wrapperClaims, signHeaders);
346
}
347
348
public <T> Optional<T> extractPayload(String token, Class<T> type) {
349
try {
350
// Parse outer signed JWT to get key ID
351
String[] parts = token.split("\\.");
352
String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]));
353
JsonNode headerNode = objectMapper.readTree(headerJson);
354
String keyId = headerNode.get("kid").asText();
355
356
// Verify signature with appropriate key
357
JsonWebTokenSigner signer = signers.get(keyId);
358
if (signer == null) {
359
log.error("Unknown key ID: {}", keyId);
360
return Optional.empty();
361
}
362
363
JwtClaims claims = signer.parseAndVerify(token);
364
String encryptedJwt = (String) claims.getClaimValue("encryptedJwt");
365
366
// Decrypt with appropriate key (need corresponding private key)
367
JsonWebTokenEncryptor encryptor = getDecryptorForKeyId(keyId);
368
T payload = encryptor.decrypt(encryptedJwt, type);
369
370
return Optional.of(payload);
371
372
} catch (Exception e) {
373
log.error("Failed to extract payload from rotation-aware token", e);
374
return Optional.empty();
375
}
376
}
377
}
378
```
379
380
## Integration with CAS Authentication
381
382
### CAS Service Token Creation
383
384
```java
385
@Component
386
public class CasJwtTokenService {
387
388
private final JsonWebTokenSigner signer;
389
private final JsonWebTokenEncryptor encryptor;
390
391
public String createServiceTicketJwt(Authentication authentication, Service service) {
392
// Create CAS-specific claims
393
JwtClaims claims = new JwtClaims();
394
claims.setSubject(authentication.getPrincipal().getId());
395
claims.setIssuer("https://cas.example.com");
396
claims.setAudience(service.getId());
397
claims.setExpirationTimeMinutesInTheFuture(5); // Short-lived service tickets
398
claims.setIssuedAtToNow();
399
claims.setJwtId(generateServiceTicketId());
400
401
// CAS authentication context
402
claims.setClaim("authenticationMethod", getAuthenticationMethod(authentication));
403
claims.setClaim("authenticationTime", authentication.getAuthenticationDate().toEpochMilli());
404
claims.setClaim("principalAttributes", authentication.getPrincipal().getAttributes());
405
claims.setClaim("serviceId", service.getId());
406
407
// Service-specific attributes
408
if (service instanceof RegisteredService registeredService) {
409
claims.setClaim("serviceName", registeredService.getName());
410
claims.setClaim("serviceDescription", registeredService.getDescription());
411
}
412
413
return signer.sign(claims);
414
}
415
416
public String createTicketGrantingTicketJwt(Authentication authentication) {
417
JwtClaims claims = new JwtClaims();
418
claims.setSubject(authentication.getPrincipal().getId());
419
claims.setIssuer("https://cas.example.com");
420
claims.setExpirationTimeMinutesInTheFuture(480); // 8 hours
421
claims.setIssuedAtToNow();
422
claims.setJwtId(generateTicketGrantingTicketId());
423
424
// TGT-specific claims
425
claims.setClaim("ticketType", "TicketGrantingTicket");
426
claims.setClaim("authenticationTime", authentication.getAuthenticationDate().toEpochMilli());
427
claims.setClaim("principalId", authentication.getPrincipal().getId());
428
claims.setClaim("credentialType", getCredentialType(authentication));
429
430
// Encrypt TGT for additional security
431
String signedToken = signer.sign(claims);
432
return encryptor.encrypt(signedToken);
433
}
434
435
public boolean validateServiceTicketJwt(String token, Service service) {
436
try {
437
JwtClaims claims = signer.parseAndVerify(token);
438
439
// Validate audience matches service
440
if (!service.getId().equals(claims.getAudience().get(0))) {
441
return false;
442
}
443
444
// Check expiration
445
if (claims.getExpirationTime().isBefore(NumericDate.now())) {
446
return false;
447
}
448
449
return true;
450
451
} catch (Exception e) {
452
log.error("Service ticket JWT validation failed", e);
453
return false;
454
}
455
}
456
}
457
```
458
459
### JWT-based Authentication Provider
460
461
```java
462
@Component
463
public class JwtAuthenticationProvider implements AuthenticationProvider {
464
465
private final JsonWebTokenSigner jwtSigner;
466
private final PrincipalResolver principalResolver;
467
468
@Override
469
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
470
471
if (!(authentication instanceof JwtAuthenticationToken jwtAuth)) {
472
return null;
473
}
474
475
try {
476
String token = jwtAuth.getToken();
477
JwtClaims claims = jwtSigner.parseAndVerify(token);
478
479
// Extract principal information
480
String principalId = claims.getSubject();
481
Map<String, Object> attributes = (Map<String, Object>) claims.getClaimValue("principalAttributes");
482
483
// Create CAS principal
484
Principal principal = principalResolver.resolve(
485
new UsernamePasswordCredential(principalId, ""),
486
Optional.empty(),
487
Optional.empty(),
488
Optional.empty()
489
);
490
491
// Create authentication result
492
AuthenticationHandlerExecutionResult result = new DefaultAuthenticationHandlerExecutionResult(
493
this,
494
new BasicCredentialMetaData(new UsernamePasswordCredential(principalId, "")),
495
principal
496
);
497
498
return DefaultAuthenticationBuilder.newInstance()
499
.addCredential(new UsernamePasswordCredential(principalId, ""))
500
.addSuccess("jwtAuthenticationHandler", result)
501
.build();
502
503
} catch (Exception e) {
504
throw new AuthenticationException("JWT authentication failed", e);
505
}
506
}
507
508
@Override
509
public boolean supports(Class<?> authentication) {
510
return JwtAuthenticationToken.class.isAssignableFrom(authentication);
511
}
512
}
513
```
514
515
This JWT utilities library provides comprehensive support for secure token-based authentication in CAS environments, with proper key management, algorithm selection, and integration with CAS authentication flows.