Repository: wso2/product-microgateway
Issue: JsonSyntaxException when invoking API in choreo-connect with API-M 4.1.0.36 access token

### Description

The following error occurs when invoking an API with an API-M access token.
```
Oct 27, 2022 6:18:53 AM io.grpc.internal.SerializingExecutor run
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: SEVERE: Exception while executing runnable io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$1HalfClosed@558d1139
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 600 
```

Choreo Connect 1.1.0
API Manager 4.1.0.36 (docker image)


### Steps to Reproduce

1. Deploy and publish an API to Choreo Connect via API-M
2. Create an access token from the Developer portal
3. Invoke the API

### Version

1.1.0

### Environment Details (with versions)

_No response_

### Relevant Log Output

```shell
[INFO] [main] ERROR org.wso2.choreo.connect.tests.testcases.withapim.ExistingApiTestCase - testExistingApiWithSandboxKey: FAILED -> Status code mismatched. Endpoint:https://localhost:9095/existing_api/1.0.0/pet/findByStatus HttpResponse  expected [200] but found [500]
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: Oct 27, 2022 6:18:53 AM io.grpc.internal.SerializingExecutor run
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: SEVERE: Exception while executing runnable io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$1HalfClosed@558d1139
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 600 path $.
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.Gson.fromJson(Gson.java:975)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.Gson.fromJson(Gson.java:928)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.Gson.fromJson(Gson.java:877)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.throttle.utils.ThrottleUtils.getJWTClaims(ThrottleUtils.java:154)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.throttle.ThrottleFilter.getProperties(ThrottleFilter.java:497)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.throttle.ThrottleFilter.getThrottleEventMap(ThrottleFilter.java:384)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.throttle.ThrottleFilter.handleEventPublish(ThrottleFilter.java:124)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.throttle.ThrottleFilter.handleRequest(ThrottleFilter.java:113)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.api.API.executeFilterChain(API.java:42)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.api.RestAPI.process(RestAPI.java:190)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.server.HttpRequestHandler.process(HttpRequestHandler.java:69)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at org.wso2.choreo.connect.enforcer.grpc.ExtAuthService.check(ExtAuthService.java:80)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.envoyproxy.envoy.service.auth.v3.AuthorizationGrpc$MethodHandlers.invoke(AuthorizationGrpc.java:245)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.stub.ServerCalls$UnaryServerCallHandler$UnaryServerCallListener.onHalfClose(ServerCalls.java:180)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.PartialForwardingServerCallListener.onHalfClose(PartialForwardingServerCallListener.java:35)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.ForwardingServerCallListener.onHalfClose(ForwardingServerCallListener.java:23)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.ForwardingServerCallListener$SimpleForwardingServerCallListener.onHalfClose(ForwardingServerCallListener.java:40)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.Contexts$ContextualizedServerCallListener.onHalfClose(Contexts.java:86)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.internal.ServerCallImpl$ServerStreamListenerImpl.halfClosed(ServerCallImpl.java:340)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$1HalfClosed.runInContext(ServerImpl.java:866)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at java.base/java.lang.Thread.run(Unknown Source)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: Caused by: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 600 path $.
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.stream.JsonReader.nextString(JsonReader.java:824)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.internal.bind.TypeAdapters$15.read(TypeAdapters.java:380)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.internal.bind.TypeAdapters$15.read(TypeAdapters.java:368)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:187)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	at com.google.gson.Gson.fromJson(Gson.java:963)
[INFO] [docker-java-stream--1732710743] INFO Enforcer - STDERR: 	... 24 more
```


### Related Issues

_No response_

### Suggested Labels

_No response_

Changed files:
enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleFilter.java
enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleDataHolder.java
enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/websocket/WebSocketThrottleFilter.java
enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/utils/ThrottleUtils.java
integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testcases/standalone/jwtValidator/JwtTestCase.java

diff --git a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleDataHolder.java b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleDataHolder.java
index fcf7e635c..e72e145a4 100644
--- a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleDataHolder.java
+++ b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleDataHolder.java
@@ -18,6 +18,7 @@
 
 package org.wso2.choreo.connect.enforcer.throttle;
 
+import net.minidev.json.JSONObject;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -365,7 +366,7 @@ public class ThrottleDataHolder {
             }
             if (conf.isJwtClaimConditionsEnabled()
                     && (claimConditions != null && !claimConditions.getValues().isEmpty())) {
-                Map<String, String> c = ThrottleUtils.getJWTClaims(req.getAuthenticationContext().getCallerToken());
+                JSONObject c = ThrottleUtils.getJWTClaims(req.getAuthenticationContext().getCallerToken());
                 if (c == null || !isJwtClaimPresent(c, claimConditions)) {
                     isThrottled = false;
                 }
@@ -521,11 +522,11 @@ public class ThrottleDataHolder {
         return status;
     }
 
-    private boolean isJwtClaimPresent(Map<String, String> claims, ThrottleCondition.JWTClaimConditions conditions) {
+    private boolean isJwtClaimPresent(JSONObject claims, ThrottleCondition.JWTClaimConditions conditions) {
         boolean status = true;
 
         for (Map.Entry<String, String> jwtClaim : conditions.getValues().entrySet()) {
-            String value = claims.get(jwtClaim.getKey());
+            String value = claims.getAsString(jwtClaim.getKey());
             if (value == null) {
                 status = false;
                 break;
diff --git a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleFilter.java b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleFilter.java
index 6c4711bd9..917778228 100644
--- a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleFilter.java
+++ b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/ThrottleFilter.java
@@ -498,7 +498,7 @@ public class ThrottleFilter implements Filter {
 
         String callerToken = requestContext.getAuthenticationContext().getCallerToken();
         if (config.isJwtClaimConditionsEnabled() && callerToken != null) {
-            Map<String, String> claims = ThrottleUtils.getJWTClaims(callerToken);
+            net.minidev.json.JSONObject claims = ThrottleUtils.getJWTClaims(callerToken);
             for (String key : claims.keySet()) {
                 jsonObMap.put(key, claims.get(key));
             }
diff --git a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/utils/ThrottleUtils.java b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/utils/ThrottleUtils.java
index d2c36d57d..2eea4c8d4 100644
--- a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/utils/ThrottleUtils.java
+++ b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/throttle/utils/ThrottleUtils.java
@@ -19,23 +19,25 @@
 package org.wso2.choreo.connect.enforcer.throttle.utils;
 
 import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
+import com.nimbusds.jose.util.JSONObjectUtils;
 import org.apache.commons.codec.binary.Base64;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.json.JSONTokener;
 import org.wso2.choreo.connect.enforcer.commons.model.RequestContext;
 import org.wso2.choreo.connect.enforcer.throttle.PolicyConstants;
 import org.wso2.choreo.connect.enforcer.throttle.ThrottleConstants;
+import org.wso2.choreo.connect.enforcer.throttle.ThrottleFilter;
 import org.wso2.choreo.connect.enforcer.throttle.dto.ThrottleCondition;
 
-import java.lang.reflect.Type;
 import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
-import java.util.Map;
 import java.util.TimeZone;
 import java.util.regex.Pattern;
 
@@ -43,6 +45,9 @@ import java.util.regex.Pattern;
  * Utilities related to throttling.
  */
 public class ThrottleUtils {
+
+    private static final Logger log = LogManager.getLogger(ThrottleFilter.class);
+
     /**
      * Extract a {@code ThrottleCondition} from a provided compatible base64 encoded string.
      *
@@ -135,23 +140,30 @@ public class ThrottleUtils {
     }
 
     /**
-     * Parse a JWT and returns it's claims and a Map.
+     * Parse a JWT and return its claims.
      *
      * @param token JWT token to parse.
-     * @return Map of claims.
+     * @return JSONObject containing the claims.
      */
-    public static Map<String, String> getJWTClaims(String token) {
-        if (token == null) {
-            return null;
-        }
-
-        // decoding JWT
-        String[] jwtTokenArray = token.split(Pattern.quote("."));
-        byte[] jwtByteArray = Base64.decodeBase64(jwtTokenArray[1].getBytes(StandardCharsets.UTF_8));
-        String jwtAssertion = new String(jwtByteArray, StandardCharsets.UTF_8);
-        Type mapType = new TypeToken<Map<String, String>>() { }.getType();
+    public static net.minidev.json.JSONObject getJWTClaims(String token) {
+        try {
+            if (token == null) {
+                return null;
+            }
 
-        return new Gson().fromJson(jwtAssertion, mapType);
+            // decoding JWT
+            String[] jwtTokenArray = token.split(Pattern.quote("."));
+            byte[] jwtByteArray = Base64.decodeBase64(jwtTokenArray[1].getBytes(StandardCharsets.UTF_8));
+            String jwtAssertion = new String(jwtByteArray, StandardCharsets.UTF_8);
+            net.minidev.json.JSONObject claims = JSONObjectUtils.parse(jwtAssertion);
+            return claims;
+        } catch (ParseException e) {
+            // This exception is supposed to be unreachable.
+            // JSONObjectUtils.parse() used above is used within SignedJWT.parse(accessToken)
+            // and therefore has already been used in the auth filters.
+            log.error("Error while parsing the JWT payload.", e);
+            throw new RuntimeException(e);
+        }
     }
 
     /**
diff --git a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/websocket/WebSocketThrottleFilter.java b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/websocket/WebSocketThrottleFilter.java
index c02759036..145d6300d 100644
--- a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/websocket/WebSocketThrottleFilter.java
+++ b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/websocket/WebSocketThrottleFilter.java
@@ -284,7 +284,7 @@ public class WebSocketThrottleFilter implements Filter {
 
         String callerToken = requestContext.getAuthenticationContext().getCallerToken();
         if (config.isJwtClaimConditionsEnabled() && callerToken != null) {
-            Map<String, String> claims = ThrottleUtils.getJWTClaims(callerToken);
+            net.minidev.json.JSONObject claims = ThrottleUtils.getJWTClaims(callerToken);
             for (String key : claims.keySet()) {
                 jsonObMap.put(key, claims.get(key));
             }
diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testcases/standalone/jwtValidator/JwtTestCase.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testcases/standalone/jwtValidator/JwtTestCase.java
index 90d2ceb03..142d0fd62 100644
--- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testcases/standalone/jwtValidator/JwtTestCase.java
+++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testcases/standalone/jwtValidator/JwtTestCase.java
@@ -20,9 +20,12 @@ package org.wso2.choreo.connect.tests.testcases.standalone.jwtValidator;
 
 import com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.HttpStatus;
 import io.netty.handler.codec.http.HttpHeaderNames;
+import org.json.JSONObject;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 import org.testng.annotations.BeforeClass;
+import org.wso2.choreo.connect.tests.common.model.API;
+import org.wso2.choreo.connect.tests.common.model.ApplicationDTO;
 import org.wso2.choreo.connect.tests.util.HttpResponse;
 import org.wso2.choreo.connect.tests.util.HttpsClientRequest;
 import org.wso2.choreo.connect.tests.util.TestConstant;
@@ -141,4 +144,29 @@ public class JwtTestCase {
         Assert.assertTrue(response.getHeaders().containsKey("www-authenticate"),
                 "\\"www-authenticate\\" is not available");
     }
+
+    @Test(description = "Test to check a JWT that contains an object as a claim")
+    public void invokeWithJwtContainingObjectClaimSuccessTest() throws Exception {
+        // Generate JWT with an object as a claim
+        JSONObject realm = new JSONObject();
+        realm.put("signing_tenant", "carbon.super");
+
+        JSONObject specificClaims = new JSONObject();
+        specificClaims.put("realm", realm);
+        API api = new API();
+        ApplicationDTO application = new ApplicationDTO();
+        String jwtWithObjectClaim = TokenUtil.getJWT(api, application, "Unlimited",
+                TestConstant.KEY_TYPE_PRODUCTION, 3600, specificClaims);
+
+        // Set header
+        Map<String, String> headers = new HashMap<String, String>();
+        headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtWithObjectClaim);
+        HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps(
+                "/v2/standard/pet/2") , headers);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_OK,"Response code mismatched");
+        Assert.assertFalse(response.getHeaders().containsKey("www-authenticate"),
+                "\\"www-authenticate\\" is available");
+    }
 }