From 914bb6606d7db1de378ac134b77be07e100ee637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kraus?= Date: Wed, 16 Aug 2023 13:00:23 +0200 Subject: [PATCH] Add config option to turn audience claim check off. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Kraus --- .../java/io/helidon/security/jwt/Jwt.java | 48 ++++++++++++++++--- .../providers/oidc/common/BaseBuilder.java | 23 ++++++++- .../providers/oidc/common/OidcConfig.java | 5 ++ .../providers/oidc/common/TenantConfig.java | 7 +++ .../oidc/common/TenantConfigImpl.java | 7 +++ .../common/OidcConfigFromBuilderTest.java | 11 +++++ .../oidc/common/OidcConfigFromConfigTest.java | 7 +++ .../src/test/resources/application.yaml | 6 +++ .../oidc/TenantAuthenticationHandler.java | 4 +- 9 files changed, 109 insertions(+), 9 deletions(-) diff --git a/security/jwt/src/main/java/io/helidon/security/jwt/Jwt.java b/security/jwt/src/main/java/io/helidon/security/jwt/Jwt.java index 881679f3a54..da8db8d77b5 100644 --- a/security/jwt/src/main/java/io/helidon/security/jwt/Jwt.java +++ b/security/jwt/src/main/java/io/helidon/security/jwt/Jwt.java @@ -951,6 +951,22 @@ public Errors validate(String issuer, String audience) { return validate(issuer, audience == null ? Set.of() : Set.of(audience)); } + /** + * Validates all default values. + * Values validated: {@link #validate(String, Set, boolean)} + * + * @param issuer validates that this JWT was issued by this issuer. Setting this to non-null value will make + * issuer claim mandatory + * @param audience validates that this JWT was issued for this audience. Setting this to non-null value will make + * audience claim mandatory + * @param checkAudience whether audience claim check is turned on. Validation will fail when {@code true} + * and audience claim is null + * @return errors instance to check for validation result + */ + public Errors validate(String issuer, String audience, boolean checkAudience) { + return validate(issuer, audience == null ? Set.of() : Set.of(audience), checkAudience); + } + /** * Validates all default values. * Values validated: @@ -966,23 +982,43 @@ public Errors validate(String issuer, String audience) { * issuer claim mandatory * @param audience validates that this JWT was issued for this audience. Setting this to non-null value and with * any non-null value in the Set will make audience claim mandatory + * @param checkAudience whether audience claim check is configured as mandatory. Validation will fail when {@code true} + * and audience claim is null * @return errors instance to check for validation result */ - public Errors validate(String issuer, Set audience) { + public Errors validate(String issuer, Set audience, boolean checkAudience) { List> validators = defaultTimeValidators(); if (null != issuer) { addIssuerValidator(validators, issuer, true); } - if (null != audience) { - audience.stream() - .filter(Objects::nonNull) - .findAny() - .ifPresent(it -> addAudienceValidator(validators, audience, true)); + // Audience check is turned on + if (checkAudience) { + if (null != audience) { + audience.stream() + .filter(Objects::nonNull) + .findAny() + .ifPresent(it -> addAudienceValidator(validators, audience, true)); + } } addUserPrincipalValidator(validators); return validate(validators); } + /** + * Validates all default values. + * Audience claim check is not mandatory. + * Values validated: {@link #validate(String, Set, boolean)} + * + * @param issuer validates that this JWT was issued by this issuer. Setting this to non-null value will make + * issuer claim mandatory + * @param audience validates that this JWT was issued for this audience. Setting this to non-null value and with + * any non-null value in the Set will make audience claim mandatory + * @return errors instance to check for validation result + */ + public Errors validate(String issuer, Set audience) { + return validate(issuer, audience, true); + } + /** * Adds a validator that makes sure the {@link Jwt#userPrincipal()} is present. * diff --git a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/BaseBuilder.java b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/BaseBuilder.java index a22ce38cd09..3b5fa044310 100644 --- a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/BaseBuilder.java +++ b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/BaseBuilder.java @@ -65,8 +65,10 @@ abstract class BaseBuilder, T> implements Builder{@code false} * Allow audience claim to be optional. * + * + * {@code check-audience} + * {@code true} + * Turn audience claim check on when {@code true} or off when {@code false}. + * * */ public final class OidcConfig extends TenantConfigImpl { diff --git a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/TenantConfig.java b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/TenantConfig.java index d9e9dbf9933..4291b1041d9 100644 --- a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/TenantConfig.java +++ b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/TenantConfig.java @@ -116,6 +116,13 @@ static Builder tenantBuilder() { */ String audience(); + /** + * Whether to validate audience token. + * + * @return audience + */ + boolean checkAudience(); + /** * Audience URI of custom scopes. * diff --git a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/TenantConfigImpl.java b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/TenantConfigImpl.java index 104c4ff6c3d..3a637712071 100644 --- a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/TenantConfigImpl.java +++ b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/TenantConfigImpl.java @@ -40,6 +40,7 @@ class TenantConfigImpl implements TenantConfig { private final boolean validateJwtWithJwk; private final String issuer; private final String audience; + private final boolean checkAudience; private final String realm; private final OidcConfig.ClientAuthentication tokenEndpointAuthentication; private final Duration clientTimeout; @@ -60,6 +61,7 @@ class TenantConfigImpl implements TenantConfig { this.validateJwtWithJwk = builder.validateJwtWithJwk(); this.issuer = builder.issuer(); this.audience = builder.audience(); + this.checkAudience = builder.checkAudience(); this.identityUri = builder.identityUri(); this.realm = builder.realm(); this.tokenEndpointUri = builder.tokenEndpointUri(); @@ -144,6 +146,11 @@ public String audience() { return audience; } + @Override + public boolean checkAudience() { + return checkAudience; + } + @Override public String scopeAudience() { return scopeAudience; diff --git a/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromBuilderTest.java b/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromBuilderTest.java index c8b81580c96..4d2e97177d9 100644 --- a/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromBuilderTest.java +++ b/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromBuilderTest.java @@ -205,6 +205,17 @@ void testOptionalAudience() { assertThat(audience, nullValue()); } + @Test + void testCheckAudience() { + OidcConfig config = OidcConfig.builder() + .identityUri(URI.create("http://localhost/identity")) + .clientSecret("top-secret") + .clientId("client-id") + .checkAudience(false) + .build(); + assertThat(config.checkAudience(), is(false)); + } + // Stub the Builder class to be able to retrieve the cookie-encryption-password value private static class TestOidcConfigBuilder extends OidcConfig.Builder { diff --git a/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromConfigTest.java b/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromConfigTest.java index ba4a4c58fa7..0340ea0c8e1 100644 --- a/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromConfigTest.java +++ b/security/providers/oidc-common/src/test/java/io/helidon/security/providers/oidc/common/OidcConfigFromConfigTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -51,4 +52,10 @@ void testOptionalAudience() { assertThat(audience, nullValue()); } + @Test + void testDisabledAudience() { + OidcConfig oidcConfig = OidcConfig.create(config.get("security.oidc-disabled-aud")); + assertThat(oidcConfig.checkAudience(), is(false)); + } + } diff --git a/security/providers/oidc-common/src/test/resources/application.yaml b/security/providers/oidc-common/src/test/resources/application.yaml index 19941db5684..561bc9c5b92 100644 --- a/security/providers/oidc-common/src/test/resources/application.yaml +++ b/security/providers/oidc-common/src/test/resources/application.yaml @@ -36,3 +36,9 @@ security: client-id: "my-id" client-secret: "my-well-known-secret" optional-audience: true + + oidc-disabled-aud: + identity-uri: "https://my.identity" + client-id: "my-id" + client-secret: "my-well-known-secret" + check-audience: false \ No newline at end of file diff --git a/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/TenantAuthenticationHandler.java b/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/TenantAuthenticationHandler.java index b73f6fea01d..4abb5d8e3f0 100644 --- a/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/TenantAuthenticationHandler.java +++ b/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/TenantAuthenticationHandler.java @@ -439,7 +439,9 @@ private AuthenticationResponse processValidationResult(ProviderRequest providerR Errors.Collector collector) { Jwt jwt = signedJwt.getJwt(); Errors errors = collector.collect(); - Errors validationErrors = jwt.validate(tenant.issuer(), tenantConfig.audience()); + Errors validationErrors = jwt.validate(tenant.issuer(), + tenantConfig.audience(), + tenantConfig.checkAudience()); if (errors.isValid() && validationErrors.isValid()) {