From b9be05fc2138be29bbd8b5b7c0a9b89cdb6fb00c Mon Sep 17 00:00:00 2001 From: Ivan Puntev Date: Mon, 15 Apr 2024 19:26:10 +0300 Subject: [PATCH] Adding possibility to supply the jwt private key as a string. --- .../security-oidc-code-flow-authentication.adoc | 9 +++++++++ ...security-openid-connect-client-reference.adoc | 9 +++++++++ ...dcClientCredentialsJwtPrivateKeyTestCase.java | 10 ++++++++++ .../quarkus/oidc/client/OidcClientResource.java | 14 ++++++++++++-- .../quarkus/oidc/client/OidcClientsResource.java | 2 +- ...client-credentials-jwt-private-key.properties | 4 ++++ .../quarkus/oidc/common/OidcRequestFilter.java | 2 +- .../oidc/common/runtime/OidcCommonConfig.java | 16 ++++++++++++++++ .../oidc/common/runtime/OidcCommonUtils.java | 9 ++++++--- 9 files changed, 68 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc index 6eb16fe378aef..06868101eb6d5 100644 --- a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc +++ b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc @@ -169,6 +169,15 @@ quarkus.oidc.credentials.jwt.secret-provider.key=mysecret-key quarkus.oidc.credentials.jwt.secret-provider.name=oidc-credentials-provider ---- +Example of `private_key_jwt` with the PEM key inlined in application.properties, and where the signature algorithm is `RS256`: + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.jwt.key=Base64-encoded private key representation +---- + Example of `private_key_jwt` with the PEM key file, and where the signature algorithm is RS256: [source,properties] diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 48a482d4b2091..db214a15dbe15 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -757,6 +757,15 @@ quarkus.oidc-client.credentials.jwt.secret-provider.key=mysecret-key quarkus.oidc-client.credentials.jwt.secret-provider.name=oidc-credentials-provider ---- +`private_key_jwt` with the PEM key inlined in application.properties, and where the signature algorithm is `RS256`: + +[source,properties] +---- +quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc-client.client-id=quarkus-app +quarkus.oidc-client.credentials.jwt.key=Base64-encoded private key representation +---- + `private_key_jwt` with the PEM key file, signature algorithm is `RS256`: [source,properties] diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsJwtPrivateKeyTestCase.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsJwtPrivateKeyTestCase.java index 46e96feb39b37..d8252bd1a9614 100644 --- a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsJwtPrivateKeyTestCase.java +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsJwtPrivateKeyTestCase.java @@ -34,4 +34,14 @@ public void testClientCredentialsToken() { .statusCode(200) .body(equalTo("service-account-quarkus-app")); } + + @Test + public void testPrivateKeyToken() { + String token = RestAssured.when().get("/client/token-key").body().asString(); + RestAssured.given().auth().oauth2(token) + .when().get("/protected") + .then() + .statusCode(200) + .body(equalTo("service-account-quarkus-app")); + } } diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientResource.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientResource.java index 75c38a064d0ec..bad7734d50387 100644 --- a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientResource.java +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientResource.java @@ -14,6 +14,16 @@ public class OidcClientResource { @Inject OidcClient client; + @Inject + @NamedOidcClient("key") + OidcClient keyClient; + + @GET + @Path("token-key") + public Uni tokenFromPrivateKeyUni() { + return keyClient.getTokens().flatMap(tokens -> Uni.createFrom().item(tokens.getAccessToken())); + } + @GET @Path("token") public Uni tokenUni() { @@ -23,13 +33,13 @@ public Uni tokenUni() { @GET @Path("tokens") public Uni grantTokensUni() { - return client.getTokens().flatMap(tokens -> createTokensString(tokens)); + return client.getTokens().flatMap(this::createTokensString); } @GET @Path("refresh-tokens") public Uni refreshGrantTokens(@QueryParam("refreshToken") String refreshToken) { - return client.refreshTokens(refreshToken).flatMap(tokens -> createTokensString(tokens)); + return client.refreshTokens(refreshToken).flatMap(this::createTokensString); } private Uni createTokensString(Tokens tokens) { diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientsResource.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientsResource.java index e575a1e45fa92..2cb8795032d7a 100644 --- a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientsResource.java +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientsResource.java @@ -27,7 +27,7 @@ public Uni tokenUni(@PathParam("id") String oidcClientId) { @GET @Path("tokens/{id}") public Uni grantTokensUni(@PathParam("id") String oidcClientId) { - return getClient(oidcClientId).getTokens().flatMap(tokens -> createTokensString(tokens)); + return getClient(oidcClientId).getTokens().flatMap(this::createTokensString); } @GET diff --git a/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials-jwt-private-key.properties b/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials-jwt-private-key.properties index 593d2f8161cdf..93ce6084ed4f8 100644 --- a/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials-jwt-private-key.properties +++ b/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials-jwt-private-key.properties @@ -4,3 +4,7 @@ quarkus.oidc.client-id=quarkus-app quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url} quarkus.oidc-client.client-id=${quarkus.oidc.client-id} quarkus.oidc-client.credentials.jwt.key-file=/privateKey.pem + +quarkus.oidc-client.key.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.key.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.key.credentials.jwt.key=MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyXwKqKL/hQWDkurdHyRn/9aZqmrgpCfiT5+gQ7KZ9RvDjgTqkJT6IIrRFvIpeBMwSsw3dkUPGmgN1J4QOhLaR2VEXhc20UbxFbr6HXAskZGPuCL1tzRWDkLNMZaEO8jqhPbcq1Ro4GMhaSdm0sBHmcQnu8wAOrdAowdzGh/HUaaYBDY0OZVAm9N8zzBXTahna9frJCMHq3e9szIiv6HYZTy1672/+hR/0D1HY+bqpQtJnzSrKjkFeXDAbYPgewYLEJ2Dk+oo6L1I6S+UTrl4FRHw1fHAd2i75JD+vL/8w/AtKkej0CCBUSZJiV+KDJWjnDUVRWjq5hQb9pu4qEJKhAgMBAAECggEAJvBs4X7B3MfsAiLszgQN4/3ZlZ4vI+5kUM2osMEo22J4RgI5Lgpfa1LALhUp07qSXmauWTdUJ3AJ3zKANrcsMAzUEiGItZu+UR4LA/vJBunPkvBfgi/qSW12ZvAsx9mDiR2y9evNrH9khalnmHVzgu4ccAimc43oSm1/5+tXlLoZ1QK/FohxBxAshtuDHGs8yKUL0jpv7dOrjhCj2ibmPYe6AUk9F61sVWO0/i0Q8UAOcYT3L5nCS5WnLhdCdYpIJJ7xl2PrVE/BAD+JEG5uCOYfVeYh+iCZVfpX17ryfNNUaBtyxKEGVtHbje3mO86mYN3noaS0w/zpUjBPgV+KEQKBgQDsp6VTmDIqHFTp2cC2yrDMxRznif92EGv7ccJDZtbTC37mAuf2J7x5b6AiE1EfxEXyGYzSk99sCns+GbL1EHABUt5pimDCl33b6XvuccQNpnJ0MfM5eRX9Ogyt/OKdDRnQsvrTPNCWOyJjvG01HQM4mfxaBBnxnvl5meH2pyG/ZQKBgQDA87DnyqEFhTDLX5c1TtwHSRj2xeTPGKG0GyxOJXcxR8nhtY9ee0kyLZ14RytnOxKarCFgYXeG4IoGEc/I42WbA4sq88tZcbe4IJkdX0WLMqOTdMrdx9hMU1ytKVUglUJZBVm7FaTQjA+ArMwqkXAA5HBMtArUsfJKUt3l0hMIjQKBgQDS1vmAZJQs2Fj+jzYWpLaneOWrk1K5yR+rQUql6jVyiUdhfS1ULUrJlh3Avh0EhEUc0I6Z/YyMITpztUmu9BoV09K7jMFwHK/RAU+cvFbDIovN4cKkbbCdjt5FFIyBB278dLjrAb+EWOLmoLVbIKICB47AU+8ZSV1SbTrYGUcD0QKBgQCAliZv4na6sg9ZiUPAr+QsKserNSiN5zFkULOPBKLRQbFFbPS1l12pRgLqNCu1qQV19H5tt6arSRpSfy5FB14gFxV4s23yFrnDyF2h2GsFH+MpEq1bbaI1A10AvUnQ5AeKQemRpxPmM2DldMK/H5tPzO0WAOoy4r/ATkc4sG4kxQKBgBL9neT0TmJtxlYGzjNcjdJXs3Q91+nZt3DRMGT9s0917SuP77+FdJYocDiH1rVa9sGG8rkh1jTdqliAxDXwIm5IGS/0OBnkaN1nnGDk5yTiYxOutC5NSj7ecI5Erud8swW6iGqgz2ioFpGxxIYqRlgTv/6mVt41KALfKrYIkVLw \ No newline at end of file diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestFilter.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestFilter.java index 93834a53fb41e..959265accfed3 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestFilter.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestFilter.java @@ -15,7 +15,7 @@ public interface OidcRequestFilter { * Filter OIDC requests * * @param request HTTP request that can have its headers customized - * @param body request body, will be null for HTTP GET methods, may be null for other HTTP methods + * @param requestBody request body, will be null for HTTP GET methods, may be null for other HTTP methods * @param contextProperties context properties that can be available in context of some requests */ void filter(HttpRequest request, Buffer requestBody, OidcRequestContextProperties contextProperties); diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java index 2da30b8da5bf5..c16645774ba65 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java @@ -274,6 +274,14 @@ public static enum Source { @ConfigItem public Provider secretProvider = new Provider(); + /** + * String representation of a private key. If provided, indicates that JWT is signed using a private key in PEM or + * JWK format. + * You can use the {@link #signatureAlgorithm} property to override the default key algorithm, `RS256`. + */ + @ConfigItem + public Optional key = Optional.empty(); + /** * If provided, indicates that JWT is signed using a private key in PEM or JWK format. * You can use the {@link #signatureAlgorithm} property to override the default key algorithm, `RS256`. @@ -399,6 +407,14 @@ public void setAudience(String audience) { this.audience = Optional.of(audience); } + public Optional getKey() { + return key; + } + + public void setKey(String key) { + this.key = Optional.of(key); + } + public Optional getKeyFile() { return keyFile; } diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index 6997a29ec767c..fa855e47ec827 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -280,8 +280,8 @@ public static boolean isClientSecretBasicAuthRequired(Credentials creds) { } public static boolean isClientJwtAuthRequired(Credentials creds) { - return creds.jwt.secret.isPresent() || creds.jwt.secretProvider.key.isPresent() || creds.jwt.keyFile.isPresent() - || creds.jwt.keyStoreFile.isPresent(); + return creds.jwt.secret.isPresent() || creds.jwt.secretProvider.key.isPresent() || creds.jwt.key.isPresent() + || creds.jwt.keyFile.isPresent() || creds.jwt.keyStoreFile.isPresent(); } public static boolean isClientSecretPostAuthRequired(Credentials creds) { @@ -329,7 +329,10 @@ public static Key clientJwtKey(Credentials creds) { } else { Key key = null; try { - if (creds.jwt.getKeyFile().isPresent()) { + if (creds.jwt.getKey().isPresent()) { + key = KeyUtils.tryAsPemSigningPrivateKey(creds.jwt.getKey().get(), + getSignatureAlgorithm(creds, SignatureAlgorithm.RS256)); + } else if (creds.jwt.getKeyFile().isPresent()) { key = KeyUtils.readSigningKey(creds.jwt.getKeyFile().get(), creds.jwt.keyId.orElse(null), getSignatureAlgorithm(creds, SignatureAlgorithm.RS256)); } else if (creds.jwt.keyStoreFile.isPresent()) {