package org.apereo.cas.token.authentication;
import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWSAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.handler.PrincipalNameTransformer;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.integration.pac4j.authentication.handler.support.AbstractTokenWrapperAuthenticationHandler;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.RegisteredServiceProperty;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.services.UnauthorizedServiceException;
import org.apereo.cas.token.TokenConstants;
import org.pac4j.core.credentials.TokenCredentials;
import org.pac4j.core.credentials.authenticator.Authenticator;
import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration;
import org.pac4j.jwt.config.signature.SecretSignatureConfiguration;
import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
/**
* This is {@link TokenAuthenticationHandler} that authenticates instances of {@link TokenCredential}.
* There is no need for a separate {@link PrincipalResolver} component
* as this handler will auto-populate the principal attributes itself.
*
* @author Misagh Moayyed
* @since 4.2.0
*/
public class TokenAuthenticationHandler extends AbstractTokenWrapperAuthenticationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenAuthenticationHandler.class);
public TokenAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
final PrincipalNameTransformer principalNameTransformer) {
super(name, servicesManager, principalFactory, null, principalNameTransformer);
}
@Override
public HandlerResult postAuthenticate(final Credential credential, final HandlerResult result) {
final TokenCredential tokenCredential = (TokenCredential) credential;
tokenCredential.setId(result.getPrincipal().getId());
return super.postAuthenticate(credential, result);
}
@Override
protected Authenticator<TokenCredentials> getAuthenticator(final Credential credential) {
final TokenCredential tokenCredential = (TokenCredential) credential;
LOGGER.debug("Locating token secret for service [{}]", tokenCredential.getService());
final RegisteredService service = this.servicesManager.findServiceBy(tokenCredential.getService());
final String signingSecret = getRegisteredServiceJwtSigningSecret(service);
final String encryptionSecret = getRegisteredServiceJwtEncryptionSecret(service);
final String signingSecretAlg =
StringUtils.defaultString(getRegisteredServiceJwtSecret(service, TokenConstants.PROPERTY_NAME_TOKEN_SECRET_SIGNING_ALG),
JWSAlgorithm.HS256.getName());
final String encryptionSecretAlg =
StringUtils.defaultString(getRegisteredServiceJwtSecret(service, TokenConstants.PROPERTY_NAME_TOKEN_SECRET_ENCRYPTION_ALG),
JWEAlgorithm.DIR.getName());
final String encryptionSecretMethod =
StringUtils.defaultString(getRegisteredServiceJwtSecret(service, TokenConstants.PROPERTY_NAME_TOKEN_SECRET_ENCRYPTION_METHOD),
EncryptionMethod.A192CBC_HS384.getName());
if (StringUtils.isNotBlank(signingSecret)) {
Set<Algorithm> sets = new HashSet<>();
sets.addAll(JWSAlgorithm.Family.EC);
sets.addAll(JWSAlgorithm.Family.HMAC_SHA);
sets.addAll(JWSAlgorithm.Family.RSA);
sets.addAll(JWSAlgorithm.Family.SIGNATURE);
final JWSAlgorithm signingAlg = findAlgorithmFamily(sets, signingSecretAlg);
final JwtAuthenticator a = new JwtAuthenticator();
a.setSignatureConfiguration(new SecretSignatureConfiguration(signingSecret, signingAlg));
if (StringUtils.isNotBlank(encryptionSecret)) {
sets = new HashSet<>();
sets.addAll(JWEAlgorithm.Family.AES_GCM_KW);
sets.addAll(JWEAlgorithm.Family.AES_KW);
sets.addAll(JWEAlgorithm.Family.ASYMMETRIC);
sets.addAll(JWEAlgorithm.Family.ECDH_ES);
sets.addAll(JWEAlgorithm.Family.PBES2);
sets.addAll(JWEAlgorithm.Family.RSA);
sets.addAll(JWEAlgorithm.Family.SYMMETRIC);
final JWEAlgorithm encAlg = findAlgorithmFamily(sets, encryptionSecretAlg);
sets = new HashSet<>();
sets.addAll(EncryptionMethod.Family.AES_CBC_HMAC_SHA);
sets.addAll(EncryptionMethod.Family.AES_GCM);
final EncryptionMethod encMethod = findAlgorithmFamily(sets, encryptionSecretMethod);
a.setEncryptionConfiguration(new SecretEncryptionConfiguration(encryptionSecret, encAlg, encMethod));
} else {
LOGGER.warn("JWT authentication is configured to share a single key for both signing/encryption");
}
return a;
}
LOGGER.warn("No token signing secret is defined for service [{}]. Ensure [{}] property is defined for service",
service.getServiceId(), TokenConstants.PROPERTY_NAME_TOKEN_SECRET_SIGNING);
return null;
}
private static <T extends Algorithm> T findAlgorithmFamily(final Set<Algorithm> family, final String alg) {
return (T) family.stream().filter(l -> l.getName().equalsIgnoreCase(alg)).findFirst().get();
}
/**
* Gets registered service jwt encryption secret.
*
* @param service the service
* @return the registered service jwt secret
*/
private String getRegisteredServiceJwtEncryptionSecret(final RegisteredService service) {
return getRegisteredServiceJwtSecret(service, TokenConstants.PROPERTY_NAME_TOKEN_SECRET_ENCRYPTION);
}
/**
* Gets registered service jwt signing secret.
*
* @param service the service
* @return the registered service jwt secret
*/
private String getRegisteredServiceJwtSigningSecret(final RegisteredService service) {
return getRegisteredServiceJwtSecret(service, TokenConstants.PROPERTY_NAME_TOKEN_SECRET_SIGNING);
}
/**
* Gets registered service jwt secret.
*
* @param service the service
* @param propName the prop name
* @return the registered service jwt secret
*/
protected String getRegisteredServiceJwtSecret(final RegisteredService service, final String propName) {
if (service == null || !service.getAccessStrategy().isServiceAccessAllowed()) {
LOGGER.debug("Service is not defined/found or its access is disabled in the registry");
throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE);
}
if (service.getProperties().containsKey(propName)) {
final RegisteredServiceProperty propSigning = service.getProperties().get(propName);
final String tokenSigningSecret = propSigning.getValue();
if (StringUtils.isNotBlank(tokenSigningSecret)) {
LOGGER.debug("Found the secret value [{}] for service [{}]", propName, service.getServiceId());
return tokenSigningSecret;
}
}
LOGGER.warn("Service [{}] does not define a property [{}] in the registry",
service.getServiceId(), propName);
return null;
}
}