diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java index 69478ee361dd..5e6c53f2d0cc 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java @@ -39,6 +39,7 @@ public class JwtDecoder * @param jwt the JWT to decode. * @return the map of claims encoded in the JWT. */ + @SuppressWarnings("unchecked") public static Map decode(String jwt) { if (LOG.isDebugEnabled()) @@ -56,7 +57,7 @@ public static Map decode(String jwt) Object parsedJwtHeader = JSON.parse(jwtHeaderString); if (!(parsedJwtHeader instanceof Map)) throw new IllegalStateException("Invalid JWT header"); - Map jwtHeader = (Map)parsedJwtHeader; + Map jwtHeader = (Map)parsedJwtHeader; if (LOG.isDebugEnabled()) LOG.debug("JWT Header: {}", jwtHeader); @@ -69,7 +70,7 @@ and the Token Endpoint (which it is in this flow), the TLS server validation Object parsedClaims = JSON.parse(jwtClaimString); if (!(parsedClaims instanceof Map)) throw new IllegalStateException("Could not decode JSON for JWT claims."); - return (Map)parsedClaims; + return (Map)parsedClaims; } static byte[] padJWTSection(String unpaddedEncodedJwtSection) diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java index a22af818ac67..863e7f26ca80 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -100,7 +101,7 @@ public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception claims = JwtDecoder.decode(idToken); if (LOG.isDebugEnabled()) LOG.debug("claims {}", claims); - validateClaims(configuration); + validateClaims(claims, configuration); } finally { @@ -110,14 +111,14 @@ public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception } } - private void validateClaims(OpenIdConfiguration configuration) throws Exception + static void validateClaims(Map claims, OpenIdConfiguration configuration) throws Exception { // Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim. if (!configuration.getIssuer().equals(claims.get("iss"))) throw new AuthenticationException("Issuer Identifier MUST exactly match the iss Claim"); // The aud (audience) Claim MUST contain the client_id value. - validateAudience(configuration); + validateAudience(claims, configuration); // If an azp (authorized party) Claim is present, verify that its client_id is the Claim Value. Object azp = claims.get("azp"); @@ -131,7 +132,7 @@ private void validateClaims(OpenIdConfiguration configuration) throws Exception throw new AuthenticationException("ID Token has expired"); } - private void validateAudience(OpenIdConfiguration configuration) throws AuthenticationException + private static void validateAudience(Map claims, OpenIdConfiguration configuration) throws AuthenticationException { Object aud = claims.get("aud"); String clientId = configuration.getClientId(); @@ -143,10 +144,11 @@ private void validateAudience(OpenIdConfiguration configuration) throws Authenti throw new AuthenticationException("Audience Claim MUST contain the client_id value"); else if (isList) { - if (!Arrays.asList((Object[])aud).contains(clientId)) + List list = Arrays.asList((Object[])aud); + if (!list.contains(clientId)) throw new AuthenticationException("Audience Claim MUST contain the client_id value"); - if (claims.get("azp") == null) + if (list.size() > 1 && claims.get("azp") == null) throw new AuthenticationException("A multi-audience ID token needs to contain an azp claim"); } else if (!isValidType) diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java new file mode 100644 index 000000000000..b816d9752363 --- /dev/null +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java @@ -0,0 +1,40 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.openid; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.client.HttpClient; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class OpenIdCredentialsTest +{ + @Test + public void testSingleAudienceValueInArray() throws Exception + { + String issuer = "myIssuer123"; + String clientId = "myClientId456"; + OpenIdConfiguration configuration = new OpenIdConfiguration(issuer, "", "", clientId, "", new HttpClient()); + + Map claims = new HashMap<>(); + claims.put("iss", issuer); + claims.put("aud", new String[]{clientId}); + claims.put("exp", System.currentTimeMillis() + 5000); + + assertDoesNotThrow(() -> OpenIdCredentials.validateClaims(claims, configuration)); + } +}