diff options
-rw-r--r-- | src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieLoginFilter.java | 184 | ||||
-rw-r--r-- | src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieSetterFilter.java | 118 |
2 files changed, 0 insertions, 302 deletions
diff --git a/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieLoginFilter.java b/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieLoginFilter.java deleted file mode 100644 index 53903f7..0000000 --- a/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieLoginFilter.java +++ /dev/null @@ -1,184 +0,0 @@ -package eu.mulk.mulkcms2.authentication; - -import io.quarkus.security.credential.Credential; -import io.quarkus.security.identity.AuthenticationRequestContext; -import io.quarkus.security.identity.IdentityProvider; -import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.identity.request.TokenAuthenticationRequest; -import io.quarkus.smallrye.jwt.runtime.auth.JWTAuthMechanism; -import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal; -import java.io.FileInputStream; -import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.Permission; -import java.security.Principal; -import java.security.PublicKey; -import java.security.cert.CertificateException; -import java.time.Duration; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import javax.annotation.PostConstruct; -import javax.enterprise.context.ApplicationScoped; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.jose4j.jwa.AlgorithmConstraints; -import org.jose4j.jws.AlgorithmIdentifiers; -import org.jose4j.jwt.MalformedClaimException; -import org.jose4j.jwt.consumer.InvalidJwtException; -import org.jose4j.jwt.consumer.JwtConsumerBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Interprets a possibly present JWT cookie and uses it to authenticate the user. - * - * <p>JWT cookies are used to authenticate further requests based on initial authentication. This - * way, there is no need to route the user through an OpenID Connect IdP on each request, for - * example. - * - * @see JWTAuthMechanism - * @see JwtCookieSetterFilter - */ -@ApplicationScoped -public class JwtCookieLoginFilter implements IdentityProvider<TokenAuthenticationRequest> { - - @ConfigProperty(name = "mulkcms.jwt.signing-key") - String signingKeyAlias; - - @ConfigProperty(name = "mulkcms.jwt.keystore.file") - String signingKeyFile; - - @ConfigProperty(name = "mulkcms.jwt.keystore.passphrase") - String signingKeyPassphrase; - - @ConfigProperty(name = "mulkcms.jwt.issuer") - String issuer; - - @ConfigProperty(name = "mulkcms.jwt.validity") - Duration validity; - - private static final Logger log = LoggerFactory.getLogger(JwtCookieLoginFilter.class); - - private PublicKey signingKey; - - @PostConstruct - public void postCostruct() - throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - log.info("Hello!"); - try (var is = new FileInputStream(signingKeyFile)) { - var keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - keystore.load(is, signingKeyPassphrase.toCharArray()); - signingKey = keystore.getCertificate(signingKeyAlias).getPublicKey(); - Objects.requireNonNull(signingKey); - } - } - - @Override - public CompletionStage<SecurityIdentity> authenticate( - TokenAuthenticationRequest request, AuthenticationRequestContext context) { - - log.info("Starting JWT verification."); - - return context.runBlocking( - () -> { - try { - log.info("JWT verification started."); - - /* - AbstractBearerTokenExtractor extractor = - new BearerTokenExtractor(requestContext, authContextInfo); - String bearerToken = extractor.getBearerToken(); - */ - - // FIXME: But how does this know how the token is extracted? What passes it here? - // Look up JWTAuthMechanism. - var bearerToken = request.getToken().getToken(); - - var jwtConsumer = - new JwtConsumerBuilder() - .setJwsAlgorithmConstraints( - new AlgorithmConstraints( - AlgorithmConstraints.ConstraintType.WHITELIST, - AlgorithmIdentifiers.RSA_USING_SHA256, - AlgorithmIdentifiers.RSA_USING_SHA384, - AlgorithmIdentifiers.RSA_USING_SHA512, - AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256, - AlgorithmIdentifiers.ECDSA_USING_P384_CURVE_AND_SHA384, - AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512)) - .setVerificationKey(signingKey) - .setRequireExpirationTime() - .setAllowedClockSkewInSeconds(60) - .build(); - - var claims = jwtConsumer.process(bearerToken).getJwtClaims(); - claims.getSubject(); - - var jwtPrincipal = new DefaultJWTCallerPrincipal(claims); - log.info("JWT verified: {}", jwtPrincipal); - - return new CookieIdentity(jwtPrincipal); - } catch (InvalidJwtException | MalformedClaimException e) { - log.info("JWT verification failed", e); - return null; - } - }); - } - - @Override - public Class<TokenAuthenticationRequest> getRequestType() { - return TokenAuthenticationRequest.class; - } - - private static class CookieIdentity implements SecurityIdentity { - - private Principal jwtPrincipal; - - private CookieIdentity(Principal jwtPrincipal) { - this.jwtPrincipal = jwtPrincipal; - } - - @Override - public Principal getPrincipal() { - return jwtPrincipal; - } - - @Override - public boolean isAnonymous() { - return false; - } - - @Override - public Set<String> getRoles() { - return Set.of(); - } - - @Override - public <T extends Credential> T getCredential(Class<T> credentialType) { - return null; - } - - @Override - public Set<Credential> getCredentials() { - return Set.of(); - } - - @Override - public <T> T getAttribute(String name) { - return null; - } - - @Override - public Map<String, Object> getAttributes() { - return Map.of(); - } - - @Override - public CompletionStage<Boolean> checkPermission(Permission permission) { - return CompletableFuture.completedFuture(false); - } - } -} diff --git a/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieSetterFilter.java b/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieSetterFilter.java deleted file mode 100644 index baa51d4..0000000 --- a/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieSetterFilter.java +++ /dev/null @@ -1,118 +0,0 @@ -package eu.mulk.mulkcms2.authentication; - -import io.quarkus.security.identity.SecurityIdentity; -import io.smallrye.jwt.build.Jwt; -import java.io.FileInputStream; -import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.time.Duration; -import javax.annotation.PostConstruct; -import javax.annotation.Priority; -import javax.inject.Inject; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerResponseContext; -import javax.ws.rs.container.ContainerResponseFilter; -import javax.ws.rs.core.NewCookie; -import javax.ws.rs.ext.Provider; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.jwt.Claims; -import org.eclipse.microprofile.jwt.JsonWebToken; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Adds a JWT cookie to every authenticated request. - * - * <p>JWT cookies are used to authenticate further requests based on initial authentication. This - * way, there is no need to route the user through an OpenID Connect IdP on each request, for - * example. - * - * @see JwtCookieLoginFilter - */ -@Provider -@Priority(1100) -public class JwtCookieSetterFilter implements ContainerResponseFilter { - - @ConfigProperty(name = "mulkcms.jwt.signing-key") - String signingKeyAlias; - - @ConfigProperty(name = "mulkcms.jwt.keystore.file") - String signingKeyFile; - - @ConfigProperty(name = "mulkcms.jwt.keystore.passphrase") - String signingKeyPassphrase; - - @ConfigProperty(name = "mulkcms.jwt.issuer") - String issuer; - - @ConfigProperty(name = "mulkcms.jwt.validity") - Duration validity; - - @Inject SecurityIdentity identity; - - private static final Logger log = LoggerFactory.getLogger(JwtCookieSetterFilter.class); - - private PrivateKey signingKey; - - @PostConstruct - public void postCostruct() - throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, - UnrecoverableKeyException { - try (var is = new FileInputStream(signingKeyFile)) { - var keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - keystore.load(is, signingKeyPassphrase.toCharArray()); - signingKey = - (PrivateKey) keystore.getKey(signingKeyAlias, signingKeyPassphrase.toCharArray()); - } - } - - @Override - public void filter( - ContainerRequestContext requestContext, ContainerResponseContext responseContext) - throws IOException { - - if (identity.isAnonymous()) { - return; - } - - var currentTimeSeconds = System.currentTimeMillis() / 1000; - - if (identity instanceof JsonWebToken - && ((JsonWebToken) identity).getExpirationTime() < currentTimeSeconds) { - return; - } - - var claims = Jwt.claims(); - - claims.issuedAt(currentTimeSeconds); - claims.claim(Claims.auth_time.name(), currentTimeSeconds); - claims.expiresAt(currentTimeSeconds + validity.toSeconds()); - claims.issuer(issuer); - claims.preferredUserName(identity.getPrincipal().getName()); - claims.subject(identity.getPrincipal().getName()); - - var token = claims.jws().signatureKeyId(signingKeyAlias).sign(signingKey); - responseContext - .getHeaders() - .add( - "Set-Cookie", - new NewCookie( - "Bearer", - token, - null, - null, - 1, - null, - (int) validity.toSeconds(), - null, - false, - true) - .toString() - + ";SameSite=Strict"); - } -} |