Skip to content

Commit

Permalink
Merge pull request quarkusio#46634 from sberyozkin/oidc_issuer_resolv…
Browse files Browse the repository at this point in the history
…er_required_claims

Check required claims in OIDC issuer-based resolver
  • Loading branch information
sberyozkin authored Mar 5, 2025
2 parents 50d31e2 + 2465448 commit 52a28da
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ public String validate(JwtContext jwtContext) throws MalformedClaimException {
var claimValue = claims.getStringClaimValue(claimName);
var targetValue = targetClaim.getValue();
if (!claimValue.equals(targetValue)) {
return "claim " + claimName + "does not match expected value of " + targetValue;
return "claim " + claimName + " does not match expected value of " + targetValue;
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.quarkus.oidc.common.runtime.OidcCommonUtils;
import io.quarkus.vertx.http.runtime.security.ImmutablePathMatcher;
import io.smallrye.mutiny.Uni;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;

final class StaticTenantResolver {
Expand Down Expand Up @@ -226,9 +227,17 @@ private static String getTenantId(RoutingContext context, TenantConfigContext te

final String iss = tokenJson.getString(Claims.iss.name());
if (tenantContext.getOidcMetadata().getIssuer().equals(iss)) {
OidcUtils.storeExtractedBearerToken(context, token);

final String tenantId = tenantContext.oidcConfig().tenantId().get();

if (!requiredClaimsMatch(tenantContext.oidcConfig().token().requiredClaims(), tokenJson)) {
LOG.debugf(
"OIDC tenant '%s' issuer matches the token issuer '%s' but does not match the token required claims",
tenantId, iss);
return null;
}

OidcUtils.storeExtractedBearerToken(context, token);
LOG.debugf("Resolved the '%s' OIDC tenant based on the matching issuer '%s'", tenantId, iss);
return tenantId;
}
Expand All @@ -237,6 +246,15 @@ private static String getTenantId(RoutingContext context, TenantConfigContext te
return null;
}

private static boolean requiredClaimsMatch(Map<String, String> requiredClaims, JsonObject tokenJson) {
for (Map.Entry<String, String> entry : requiredClaims.entrySet()) {
if (!entry.getValue().equals(tokenJson.getString(entry.getKey()))) {
return false;
}
}
return true;
}

private static boolean isTenantWithoutIssuer(TenantConfigContext tenantContext) {
return tenantContext.getOidcMetadata().getIssuer() == null
|| ANY_ISSUER.equals(tenantContext.getOidcMetadata().getIssuer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,16 @@ quarkus.grpc.clients.hello.host=localhost
quarkus.grpc.clients.hello.port=8081
quarkus.grpc.server.use-separate-server=false

%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver.auth-server-url=http://localhost:8185/auth/realms/quarkus2
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver.client-id=quarkus-app
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver.credentials.secret=secret
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver.token.audience=https://correct-issuer.edu
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver.token.allow-jwt-introspection=false
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-a.auth-server-url=http://localhost:8185/auth/realms/quarkus2
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-a.client-id=quarkus-app
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-a.token.required-claims.client-name=a
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-a.credentials.secret=secret
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-a.token.audience=https://correct-issuer.edu
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-a.token.allow-jwt-introspection=false
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-b.auth-server-url=http://localhost:8185/auth/realms/quarkus2
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-b.client-id=quarkus-app
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-b.token.required-claims.client-name=b
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-b.credentials.secret=secret
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-b.token.audience=https://correct-issuer.edu
%issuer-based-resolver.quarkus.oidc.bearer-issuer-resolver-b.token.allow-jwt-introspection=false
%issuer-based-resolver.quarkus.oidc.resolve-tenants-with-issuer=true
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,19 @@ public void testOidcServerUnavailableOnAppStartup() {
server.start();
try {
// 500 because default tenant has unavailable OIDC server (otherwise it assumes our issuer)
requestAdminRoles("https://wrong-issuer.edu").statusCode(500);
requestAdminRoles("https://wrong-issuer.edu", "a").statusCode(500);

requestAdminRoles("https://correct-issuer.edu").statusCode(200)
.body(Matchers.is("static.tenant.id=bearer-issuer-resolver"));
requestAdminRoles("https://correct-issuer.edu", "a").statusCode(200)
.body(Matchers.is("static.tenant.id=bearer-issuer-resolver-a"));
requestAdminRoles("https://correct-issuer.edu", "b").statusCode(200)
.body(Matchers.is("static.tenant.id=bearer-issuer-resolver-b"));
} finally {
server.stop();
}
}

private static ValidatableResponse requestAdminRoles(String issuer) {
return RestAssured.given().auth().oauth2(getAdminTokenWithRole(issuer))
private static ValidatableResponse requestAdminRoles(String issuer, String clientName) {
return RestAssured.given().auth().oauth2(getAdminTokenWithRole(issuer, clientName))
.when().get("/api/admin/bearer-issuer-resolver/issuer").then();
}

Expand All @@ -43,11 +45,12 @@ public String getConfigProfile() {
}
}

private static String getAdminTokenWithRole(String issuer) {
private static String getAdminTokenWithRole(String issuer, String clientName) {
return Jwt.preferredUserName("alice")
.groups(Set.of("admin"))
.issuer(issuer)
.audience(issuer)
.claim("client-name", clientName)
.jws()
.keyId("1")
.sign("privateKey.jwk");
Expand Down

0 comments on commit 52a28da

Please sign in to comment.