Apereo CAS Core Utilities - A comprehensive utility library providing functional programming constructs, encryption utilities, configuration helpers, and core infrastructure components for the Central Authentication Service framework
—
JSON Web Token signing and encryption utilities with support for various algorithms, key management strategies, and integration with CAS authentication flows.
JWT signing utilities for creating and validating signed tokens with configurable algorithms and key management.
public class JsonWebTokenSigner {
// Supported algorithms (excludes 'none' for security)
public static final Set<String> ALGORITHM_ALL_EXCEPT_NONE = Set.of(
"HS256", "HS384", "HS512", // HMAC algorithms
"RS256", "RS384", "RS512", // RSA algorithms
"ES256", "ES384", "ES512", // ECDSA algorithms
"PS256", "PS384", "PS512" // RSA-PSS algorithms
);
// Constructors
public JsonWebTokenSigner(Key signingKey);
public JsonWebTokenSigner(Key signingKey, String algorithm);
public JsonWebTokenSigner(String secretKey, String algorithm);
// Signing operations
public byte[] sign(byte[] value);
public String sign(JwtClaims claims);
public String sign(JwtClaims claims, Map<String, Object> headers);
// Validation operations
public boolean verify(String token);
public JwtClaims parse(String token);
public JwtClaims parseAndVerify(String token);
// Configuration
public String getAlgorithm();
public Key getSigningKey();
}Basic JWT signing:
@Service
public class TokenService {
private final JsonWebTokenSigner jwtSigner;
public TokenService(@Value("${cas.jwt.signing-secret}") String secret) {
// HMAC-based signer with HS256
this.jwtSigner = new JsonWebTokenSigner(secret, "HS256");
}
public String createAccessToken(String userId, List<String> roles) {
// Create JWT claims
JwtClaims claims = new JwtClaims();
claims.setSubject(userId);
claims.setIssuer("cas-server");
claims.setAudience("cas-clients");
claims.setExpirationTimeMinutesInTheFuture(60); // 1 hour
claims.setIssuedAtToNow();
claims.setNotBeforeMinutesInThePast(1);
claims.setJwtId(UUID.randomUUID().toString());
// Add custom claims
claims.setClaim("roles", roles);
claims.setClaim("tokenType", "access");
// Sign and return token
return jwtSigner.sign(claims);
}
public String createRefreshToken(String userId) {
JwtClaims claims = new JwtClaims();
claims.setSubject(userId);
claims.setIssuer("cas-server");
claims.setExpirationTimeMinutesInTheFuture(10080); // 7 days
claims.setIssuedAtToNow();
claims.setClaim("tokenType", "refresh");
return jwtSigner.sign(claims);
}
public boolean validateToken(String token) {
try {
return jwtSigner.verify(token);
} catch (Exception e) {
log.warn("Token validation failed", e);
return false;
}
}
public Optional<TokenInfo> parseToken(String token) {
try {
JwtClaims claims = jwtSigner.parseAndVerify(token);
TokenInfo info = new TokenInfo();
info.setUserId(claims.getSubject());
info.setRoles((List<String>) claims.getClaimValue("roles"));
info.setTokenType((String) claims.getClaimValue("tokenType"));
info.setExpiresAt(claims.getExpirationTime().getValueInMillis());
return Optional.of(info);
} catch (Exception e) {
log.error("Failed to parse token", e);
return Optional.empty();
}
}
}RSA-based JWT signing:
@Configuration
public class JwtConfiguration {
@Bean
public JsonWebTokenSigner rsaJwtSigner() throws Exception {
// Load RSA private key for signing
PrivateKey privateKey = loadPrivateKey("classpath:jwt-private.pem");
return new JsonWebTokenSigner(privateKey, "RS256");
}
@Bean
public JsonWebTokenValidator rsaJwtValidator() throws Exception {
// Load RSA public key for validation
PublicKey publicKey = loadPublicKey("classpath:jwt-public.pem");
return new JsonWebTokenValidator(publicKey, "RS256");
}
private PrivateKey loadPrivateKey(String location) throws Exception {
Resource resource = resourceLoader.getResource(location);
return AbstractCipherExecutor.extractPrivateKeyFromResource(resource.getURI().toString());
}
private PublicKey loadPublicKey(String location) throws Exception {
Resource resource = resourceLoader.getResource(location);
return AbstractCipherExecutor.extractPublicKeyFromResource(resource.getURI().toString());
}
}Custom headers and claims:
@Service
public class AdvancedJwtService {
private final JsonWebTokenSigner jwtSigner;
public String createTokenWithCustomHeaders(String userId, String clientId) {
// Create claims
JwtClaims claims = new JwtClaims();
claims.setSubject(userId);
claims.setAudience(clientId);
claims.setExpirationTimeMinutesInTheFuture(30);
claims.setIssuedAtToNow();
// Custom claims for CAS context
claims.setClaim("authenticationMethod", "password");
claims.setClaim("serviceTicket", generateServiceTicket());
claims.setClaim("principalAttributes", getUserAttributes(userId));
// Custom headers
Map<String, Object> headers = new HashMap<>();
headers.put("typ", "JWT");
headers.put("alg", jwtSigner.getAlgorithm());
headers.put("kid", "cas-2024-01"); // Key ID for rotation
headers.put("cas-version", "7.2.4");
return jwtSigner.sign(claims, headers);
}
public String createServiceToken(String service, Map<String, Object> attributes) {
JwtClaims claims = new JwtClaims();
claims.setAudience(service);
claims.setIssuer("https://cas.example.com");
claims.setExpirationTimeMinutesInTheFuture(5); // Short-lived for services
claims.setIssuedAtToNow();
// Service-specific claims
claims.setClaim("serviceAttributes", attributes);
claims.setClaim("grantType", "service_ticket");
Map<String, Object> headers = Map.of(
"typ", "service+jwt",
"purpose", "service-validation"
);
return jwtSigner.sign(claims, headers);
}
}JWT encryption utilities for creating encrypted tokens with various key management strategies.
public class JsonWebTokenEncryptor {
// Supported encryption algorithms (excludes 'none' for security)
public static final List<String> ALGORITHM_ALL_EXCEPT_NONE = List.of(
"RSA1_5", "RSA-OAEP", "RSA-OAEP-256", // RSA key encryption
"A128KW", "A192KW", "A256KW", // AES key wrapping
"dir", // Direct encryption
"A128GCMKW", "A192GCMKW", "A256GCMKW" // AES-GCM key wrapping
);
// Constructors
public JsonWebTokenEncryptor(Key encryptionKey);
public JsonWebTokenEncryptor(Key encryptionKey, String keyAlgorithm, String contentAlgorithm);
public JsonWebTokenEncryptor(String secretKey, String keyAlgorithm, String contentAlgorithm);
// Encryption operations
public String encrypt(Serializable payload);
public String encrypt(Object payload, Map<String, Object> headers);
public String encrypt(String plaintext);
// Decryption operations
public String decrypt(String encryptedToken);
public <T> T decrypt(String encryptedToken, Class<T> type);
public Map<String, Object> decryptToMap(String encryptedToken);
// Configuration
public String getKeyAlgorithm();
public String getContentAlgorithm();
public Key getEncryptionKey();
}Basic JWT encryption:
@Service
public class SecureTokenService {
private final JsonWebTokenEncryptor jwtEncryptor;
private final JsonWebTokenSigner jwtSigner;
public SecureTokenService(
@Value("${cas.jwt.encryption-secret}") String encryptionSecret,
@Value("${cas.jwt.signing-secret}") String signingSecret) {
// AES-GCM encryption
this.jwtEncryptor = new JsonWebTokenEncryptor(
encryptionSecret,
"dir", // Direct key agreement
"A256GCM" // AES-256-GCM content encryption
);
// HMAC signing
this.jwtSigner = new JsonWebTokenSigner(signingSecret, "HS256");
}
public String createSecureToken(SensitiveData data) {
// First encrypt the sensitive data
String encryptedData = jwtEncryptor.encrypt(data);
// Then sign the encrypted token
JwtClaims claims = new JwtClaims();
claims.setSubject(data.getUserId());
claims.setExpirationTimeMinutesInTheFuture(15);
claims.setIssuedAtToNow();
claims.setClaim("encryptedPayload", encryptedData);
return jwtSigner.sign(claims);
}
public Optional<SensitiveData> extractSecureData(String token) {
try {
// Verify and parse the signed token
JwtClaims claims = jwtSigner.parseAndVerify(token);
String encryptedPayload = (String) claims.getClaimValue("encryptedPayload");
// Decrypt the payload
SensitiveData data = jwtEncryptor.decrypt(encryptedPayload, SensitiveData.class);
return Optional.of(data);
} catch (Exception e) {
log.error("Failed to extract secure data from token", e);
return Optional.empty();
}
}
}RSA-based encryption with key rotation:
@Service
public class KeyRotationAwareJwtService {
private final Map<String, JsonWebTokenEncryptor> encryptors;
private final Map<String, JsonWebTokenSigner> signers;
private String currentKeyId;
public KeyRotationAwareJwtService() {
this.encryptors = new ConcurrentHashMap<>();
this.signers = new ConcurrentHashMap<>();
initializeKeys();
}
private void initializeKeys() {
try {
// Load current keys
String keyId = "2024-01";
PublicKey publicKey = loadPublicKey("jwt-public-" + keyId + ".pem");
PrivateKey privateKey = loadPrivateKey("jwt-private-" + keyId + ".pem");
// RSA encryption (recipient uses private key to decrypt)
encryptors.put(keyId, new JsonWebTokenEncryptor(publicKey, "RSA-OAEP", "A256GCM"));
// RSA signing (sender uses private key to sign)
signers.put(keyId, new JsonWebTokenSigner(privateKey, "RS256"));
this.currentKeyId = keyId;
} catch (Exception e) {
throw new RuntimeException("Failed to initialize JWT keys", e);
}
}
public String createRotationAwareToken(Object payload) {
// Encrypt with current public key
JsonWebTokenEncryptor encryptor = encryptors.get(currentKeyId);
Map<String, Object> headers = Map.of(
"kid", currentKeyId, // Key ID for rotation
"alg", "RSA-OAEP",
"enc", "A256GCM"
);
String encryptedToken = encryptor.encrypt(payload, headers);
// Sign with current private key
JwtClaims wrapperClaims = new JwtClaims();
wrapperClaims.setIssuedAtToNow();
wrapperClaims.setExpirationTimeMinutesInTheFuture(60);
wrapperClaims.setClaim("encryptedJwt", encryptedToken);
JsonWebTokenSigner signer = signers.get(currentKeyId);
Map<String, Object> signHeaders = Map.of("kid", currentKeyId);
return signer.sign(wrapperClaims, signHeaders);
}
public <T> Optional<T> extractPayload(String token, Class<T> type) {
try {
// Parse outer signed JWT to get key ID
String[] parts = token.split("\\.");
String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]));
JsonNode headerNode = objectMapper.readTree(headerJson);
String keyId = headerNode.get("kid").asText();
// Verify signature with appropriate key
JsonWebTokenSigner signer = signers.get(keyId);
if (signer == null) {
log.error("Unknown key ID: {}", keyId);
return Optional.empty();
}
JwtClaims claims = signer.parseAndVerify(token);
String encryptedJwt = (String) claims.getClaimValue("encryptedJwt");
// Decrypt with appropriate key (need corresponding private key)
JsonWebTokenEncryptor encryptor = getDecryptorForKeyId(keyId);
T payload = encryptor.decrypt(encryptedJwt, type);
return Optional.of(payload);
} catch (Exception e) {
log.error("Failed to extract payload from rotation-aware token", e);
return Optional.empty();
}
}
}@Component
public class CasJwtTokenService {
private final JsonWebTokenSigner signer;
private final JsonWebTokenEncryptor encryptor;
public String createServiceTicketJwt(Authentication authentication, Service service) {
// Create CAS-specific claims
JwtClaims claims = new JwtClaims();
claims.setSubject(authentication.getPrincipal().getId());
claims.setIssuer("https://cas.example.com");
claims.setAudience(service.getId());
claims.setExpirationTimeMinutesInTheFuture(5); // Short-lived service tickets
claims.setIssuedAtToNow();
claims.setJwtId(generateServiceTicketId());
// CAS authentication context
claims.setClaim("authenticationMethod", getAuthenticationMethod(authentication));
claims.setClaim("authenticationTime", authentication.getAuthenticationDate().toEpochMilli());
claims.setClaim("principalAttributes", authentication.getPrincipal().getAttributes());
claims.setClaim("serviceId", service.getId());
// Service-specific attributes
if (service instanceof RegisteredService registeredService) {
claims.setClaim("serviceName", registeredService.getName());
claims.setClaim("serviceDescription", registeredService.getDescription());
}
return signer.sign(claims);
}
public String createTicketGrantingTicketJwt(Authentication authentication) {
JwtClaims claims = new JwtClaims();
claims.setSubject(authentication.getPrincipal().getId());
claims.setIssuer("https://cas.example.com");
claims.setExpirationTimeMinutesInTheFuture(480); // 8 hours
claims.setIssuedAtToNow();
claims.setJwtId(generateTicketGrantingTicketId());
// TGT-specific claims
claims.setClaim("ticketType", "TicketGrantingTicket");
claims.setClaim("authenticationTime", authentication.getAuthenticationDate().toEpochMilli());
claims.setClaim("principalId", authentication.getPrincipal().getId());
claims.setClaim("credentialType", getCredentialType(authentication));
// Encrypt TGT for additional security
String signedToken = signer.sign(claims);
return encryptor.encrypt(signedToken);
}
public boolean validateServiceTicketJwt(String token, Service service) {
try {
JwtClaims claims = signer.parseAndVerify(token);
// Validate audience matches service
if (!service.getId().equals(claims.getAudience().get(0))) {
return false;
}
// Check expiration
if (claims.getExpirationTime().isBefore(NumericDate.now())) {
return false;
}
return true;
} catch (Exception e) {
log.error("Service ticket JWT validation failed", e);
return false;
}
}
}@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final JsonWebTokenSigner jwtSigner;
private final PrincipalResolver principalResolver;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof JwtAuthenticationToken jwtAuth)) {
return null;
}
try {
String token = jwtAuth.getToken();
JwtClaims claims = jwtSigner.parseAndVerify(token);
// Extract principal information
String principalId = claims.getSubject();
Map<String, Object> attributes = (Map<String, Object>) claims.getClaimValue("principalAttributes");
// Create CAS principal
Principal principal = principalResolver.resolve(
new UsernamePasswordCredential(principalId, ""),
Optional.empty(),
Optional.empty(),
Optional.empty()
);
// Create authentication result
AuthenticationHandlerExecutionResult result = new DefaultAuthenticationHandlerExecutionResult(
this,
new BasicCredentialMetaData(new UsernamePasswordCredential(principalId, "")),
principal
);
return DefaultAuthenticationBuilder.newInstance()
.addCredential(new UsernamePasswordCredential(principalId, ""))
.addSuccess("jwtAuthenticationHandler", result)
.build();
} catch (Exception e) {
throw new AuthenticationException("JWT authentication failed", e);
}
}
@Override
public boolean supports(Class<?> authentication) {
return JwtAuthenticationToken.class.isAssignableFrom(authentication);
}
}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.
Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-core-util-api