You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by ng...@apache.org on 2022/03/24 17:38:31 UTC

[hive] branch master updated: HIVE-25575: Add support for JWT authentication in HTTP mode (Yu-Wen Lai reviewed by Sourabh Goyal)

This is an automated email from the ASF dual-hosted git repository.

ngangam pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git


The following commit(s) were added to refs/heads/master by this push:
     new c3fa88a  HIVE-25575: Add support for JWT authentication in HTTP mode (Yu-Wen Lai reviewed by Sourabh Goyal)
c3fa88a is described below

commit c3fa88a1b7d1475f44383fca913aecf9c664bab0
Author: Yu-Wen Lai <yu...@cloudera.com>
AuthorDate: Fri Aug 27 01:32:54 2021 -0700

    HIVE-25575: Add support for JWT authentication in HTTP mode (Yu-Wen Lai reviewed by Sourabh Goyal)
    
    HS2 side change:
    * Fetches JWKS from a URL into memory while HS2 starts up.
    * Accepts JWT in Authorization: Bearer header and verifies it with JWKS
    
    JDBC driver side change:
    * JDBC client can pick up JWT from env variable
    * JDBC client can accept JWT in JDBC url
    * sends JWT in authorization header
    
    Test:
    mvn test -Dtest=org.apache.hive.service.auth.jwt.TestHttpJwtAuthentication
    
    Co-authored-by: Shubham Chaurasia <sc...@cloudera.com>
---
 .../java/org/apache/hadoop/hive/conf/HiveConf.java |  11 +-
 itests/hive-unit/pom.xml                           |   6 +
 .../auth/jwt/TestHttpJwtAuthentication.java        | 223 +++++++++++++++++++++
 .../resources/auth.jwt/jwt-authorized-key.json     |  12 ++
 .../resources/auth.jwt/jwt-unauthorized-key.json   |  12 ++
 .../resources/auth.jwt/jwt-verification-jwks.json  |  20 ++
 .../java/org/apache/hive/jdbc/HiveConnection.java  |  45 +++++
 jdbc/src/java/org/apache/hive/jdbc/Utils.java      |   3 +
 .../jdbc/jwt/HttpJwtAuthRequestInterceptor.java    |  49 +++++
 .../jdbc/saml/HttpSamlAuthRequestInterceptor.java  |   4 +-
 service/pom.xml                                    |   6 +
 .../hive/service/auth/HiveAuthConstants.java       |   3 +-
 .../apache/hive/service/auth/HttpAuthUtils.java    |   1 +
 .../apache/hive/service/auth/jwt/JWTValidator.java | 111 ++++++++++
 .../service/auth/jwt/URLBasedJWKSProvider.java     |  81 ++++++++
 .../hive/service/cli/thrift/ThriftHttpServlet.java |  62 ++++--
 16 files changed, 632 insertions(+), 17 deletions(-)

diff --git a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
index ce8ca7b..3b42210 100644
--- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
+++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
@@ -4122,7 +4122,7 @@ public class HiveConf extends Configuration {
 
     // HiveServer2 auth configuration
     HIVE_SERVER2_AUTHENTICATION("hive.server2.authentication", "NONE",
-      new StringSet("NOSASL", "NONE", "LDAP", "KERBEROS", "PAM", "CUSTOM", "SAML"),
+      new StringSet("NOSASL", "NONE", "LDAP", "KERBEROS", "PAM", "CUSTOM", "SAML", "JWT"),
         "Client authentication types.\n" +
         "  NONE: no authentication check\n" +
         "  LDAP: LDAP/AD based authentication\n" +
@@ -4131,7 +4131,9 @@ public class HiveConf extends Configuration {
         "          (Use with property hive.server2.custom.authentication.class)\n" +
         "  PAM: Pluggable authentication module\n" +
         "  NOSASL:  Raw transport\n" +
-        "  SAML: SAML 2.0 compliant authentication. This is only supported in http transport mode."),
+        "  SAML: SAML 2.0 compliant authentication. This is only supported in http transport mode.\n" +
+        "  JWT: JWT based authentication. HS2 expects JWT contains the user name as subject and was signed by an\n" +
+        "       asymmetric key. This is only supported in http transport mode."),
     HIVE_SERVER2_TRUSTED_DOMAIN("hive.server2.trusted.domain", "",
         "Specifies the host or a domain to trust connections from. Authentication is skipped " +
         "for any connection coming from a host whose hostname ends with the value of this" +
@@ -4236,6 +4238,10 @@ public class HiveConf extends Configuration {
     HIVE_SERVER2_PAM_SERVICES("hive.server2.authentication.pam.services", null,
       "List of the underlying pam services that should be used when auth type is PAM\n" +
       "A file with the same name must exist in /etc/pam.d"),
+    // JWT Auth configs
+    HIVE_SERVER2_AUTHENTICATION_JWT_JWKS_URL("hive.server2.authentication.jwt.jwks.url", "",
+        "URL of the file from where URLBasedJWKSProvider will try to load JWKS if JWT is enabled for the\n" +
+        "authentication mode."),
 
     // HS2 SAML2.0 configuration
     HIVE_SERVER2_SAML_KEYSTORE_PATH("hive.server2.saml2.keystore.path", "",
@@ -4301,6 +4307,7 @@ public class HiveConf extends Configuration {
     HIVE_SERVER2_SAML_GROUP_FILTER("hive.server2.saml2.group.filter", "",
         "Comma separated list of group names which will be allowed when SAML\n"
             + " authentication is enabled."),
+
     HIVE_SERVER2_ENABLE_DOAS("hive.server2.enable.doAs", true,
         "Setting this property to true will have HiveServer2 execute\n" +
         "Hive operations as the user making the calls to it."),
diff --git a/itests/hive-unit/pom.xml b/itests/hive-unit/pom.xml
index 92d4b4f..042924c 100644
--- a/itests/hive-unit/pom.xml
+++ b/itests/hive-unit/pom.xml
@@ -185,6 +185,12 @@
     </dependency>
     <!-- test inter-project -->
     <dependency>
+      <groupId>com.github.tomakehurst</groupId>
+      <artifactId>wiremock-jre8-standalone</artifactId>
+      <version>2.32.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>test</scope>
diff --git a/itests/hive-unit/src/test/java/org/apache/hive/service/auth/jwt/TestHttpJwtAuthentication.java b/itests/hive-unit/src/test/java/org/apache/hive/service/auth/jwt/TestHttpJwtAuthentication.java
new file mode 100644
index 0000000..202ff0d
--- /dev/null
+++ b/itests/hive-unit/src/test/java/org/apache/hive/service/auth/jwt/TestHttpJwtAuthentication.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hive.service.auth.jwt;
+
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
+import org.apache.hive.jdbc.HiveConnection;
+import org.apache.hive.jdbc.Utils;
+import org.apache.hive.jdbc.miniHS2.MiniHS2;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.ok;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+public class TestHttpJwtAuthentication {
+  private static final Map<String, String> DEFAULTS = new HashMap<>(System.getenv());
+  private static Map<String, String> envMap;
+
+  private static final File jwtAuthorizedKeyFile =
+      new File("src/test/resources/auth.jwt/jwt-authorized-key.json");
+  private static final File jwtUnauthorizedKeyFile =
+      new File("src/test/resources/auth.jwt/jwt-unauthorized-key.json");
+  private static final File jwtVerificationJWKSFile =
+      new File("src/test/resources/auth.jwt/jwt-verification-jwks.json");
+
+  public static final String USER_1 = "USER_1";
+
+  private static MiniHS2 miniHS2;
+
+  private static final int MOCK_JWKS_SERVER_PORT = 8089;
+  @ClassRule
+  public static final WireMockRule MOCK_JWKS_SERVER = new WireMockRule(MOCK_JWKS_SERVER_PORT);
+
+  /**
+   * This is a hack to make environment variables modifiable.
+   * Ref: https://stackoverflow.com/questions/318239/how-do-i-set-environment-variables-from-java.
+   */
+  @BeforeClass
+  public static void makeEnvModifiable() throws Exception {
+    envMap = new HashMap<>();
+    Class<?> envClass = Class.forName("java.lang.ProcessEnvironment");
+    Field theEnvironmentField = envClass.getDeclaredField("theEnvironment");
+    Field theUnmodifiableEnvironmentField = envClass.getDeclaredField("theUnmodifiableEnvironment");
+    removeStaticFinalAndSetValue(theEnvironmentField, envMap);
+    removeStaticFinalAndSetValue(theUnmodifiableEnvironmentField, envMap);
+  }
+
+  private static void removeStaticFinalAndSetValue(Field field, Object value) throws Exception {
+    field.setAccessible(true);
+    Field modifiersField = Field.class.getDeclaredField("modifiers");
+    modifiersField.setAccessible(true);
+    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+    field.set(null, value);
+  }
+
+  @Before
+  public void initEnvMap() {
+    envMap.clear();
+    envMap.putAll(DEFAULTS);
+  }
+
+  @BeforeClass
+  public static void setupHS2() throws Exception {
+    MOCK_JWKS_SERVER.stubFor(get("/jwks")
+        .willReturn(ok()
+            .withBody(Files.readAllBytes(jwtVerificationJWKSFile.toPath()))));
+
+    HiveConf conf = new HiveConf();
+    conf.setBoolVar(ConfVars.HIVE_SUPPORT_CONCURRENCY, false);
+    conf.setBoolVar(ConfVars.HIVE_SERVER2_LOGGING_OPERATION_ENABLED, false);
+    conf.setBoolVar(ConfVars.HIVESTATSCOLAUTOGATHER, false);
+    conf.setVar(ConfVars.HIVE_SERVER2_AUTHENTICATION, "JWT");
+    // the content of the URL below is the same as jwtVerificationJWKSFile
+    conf.setVar(ConfVars.HIVE_SERVER2_AUTHENTICATION_JWT_JWKS_URL, "http://localhost:" + MOCK_JWKS_SERVER_PORT +
+        "/jwks");
+    miniHS2 = new MiniHS2.Builder().withConf(conf).withHTTPTransport().build();
+
+    miniHS2.start(new HashMap<>());
+  }
+
+  @AfterClass
+  public static void stopServices() throws Exception {
+    if (miniHS2 != null && miniHS2.isStarted()) {
+      miniHS2.stop();
+      miniHS2.cleanup();
+      miniHS2 = null;
+      MiniHS2.cleanupLocalDir();
+    }
+  }
+
+  @Test
+  public void testAuthorizedUser() throws Exception {
+    String jwt = generateJWT(USER_1, jwtAuthorizedKeyFile.toPath(), TimeUnit.MINUTES.toMillis(5));
+    HiveConnection connection = getConnection(jwt, true);
+    assertLoggedInUser(connection, USER_1);
+    connection.close();
+
+    connection = getConnection(jwt, false);
+    assertLoggedInUser(connection, USER_1);
+    connection.close();
+  }
+
+  @Test(expected = SQLException.class)
+  public void testExpiredJwt() throws Exception {
+    String jwt = generateJWT(USER_1, jwtAuthorizedKeyFile.toPath(), 1);
+    Thread.sleep(1);
+    HiveConnection connection = getConnection(jwt, true);
+  }
+
+  @Test(expected = SQLException.class)
+  public void testUnauthorizedUser() throws Exception {
+    String unauthorizedJwt = generateJWT(USER_1, jwtUnauthorizedKeyFile.toPath(), TimeUnit.MINUTES.toMillis(5));
+    HiveConnection connection = getConnection(unauthorizedJwt, true);
+  }
+
+  @Test(expected = SQLException.class)
+  public void testWithoutJwtProvided() throws Exception {
+    HiveConnection connection = getConnection(null, true);
+  }
+
+  private HiveConnection getConnection(String jwt, Boolean putJwtInEnv) throws Exception {
+    String url = getJwtJdbcConnectionUrl();
+    if (jwt != null && putJwtInEnv) {
+      System.getenv().put(Utils.JdbcConnectionParams.AUTH_JWT_ENV, jwt);
+    } else if (jwt != null) {
+      url += "jwt=" + jwt;
+    }
+    Class.forName("org.apache.hive.jdbc.HiveDriver");
+    Connection connection = DriverManager.getConnection(url, null, null);
+    return (HiveConnection) connection;
+  }
+
+  private String generateJWT(String user, Path keyFile, long lifeTimeMillis) throws Exception {
+    RSAKey rsaKeyPair = RSAKey.parse(new String(java.nio.file.Files.readAllBytes(keyFile), StandardCharsets.UTF_8));
+
+    // Create RSA-signer with the private key
+    JWSSigner signer = new RSASSASigner(rsaKeyPair);
+
+    JWSHeader header = new JWSHeader
+        .Builder(JWSAlgorithm.RS256)
+        .keyID(rsaKeyPair.getKeyID())
+        .build();
+
+    Date now = new Date();
+    Date expirationTime = new Date(now.getTime() + lifeTimeMillis);
+    JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+        .jwtID(UUID.randomUUID().toString())
+        .issueTime(now)
+        .issuer("auth-server")
+        .subject(user)
+        .expirationTime(expirationTime)
+        .claim("custom-claim-or-payload", "custom-claim-or-payload")
+        .build();
+
+    SignedJWT signedJWT = new SignedJWT(header, claimsSet);
+
+    // Compute the RSA signature
+    signedJWT.sign(signer);
+
+    return signedJWT.serialize();
+  }
+
+  private String getJwtJdbcConnectionUrl() throws Exception {
+    return miniHS2.getHttpJdbcURL() + "auth=jwt;";
+  }
+
+  private void assertLoggedInUser(HiveConnection connection, String expectedUser)
+      throws SQLException {
+    Statement stmt = connection.createStatement();
+    ResultSet resultSet = stmt.executeQuery("select logged_in_user()");
+    assertTrue(resultSet.next());
+    String loggedInUser = resultSet.getString(1);
+    assertEquals(expectedUser, loggedInUser);
+  }
+}
diff --git a/itests/hive-unit/src/test/resources/auth.jwt/jwt-authorized-key.json b/itests/hive-unit/src/test/resources/auth.jwt/jwt-authorized-key.json
new file mode 100644
index 0000000..b5b4fb4
--- /dev/null
+++ b/itests/hive-unit/src/test/resources/auth.jwt/jwt-authorized-key.json
@@ -0,0 +1,12 @@
+{
+  "p": "-8lxjB9JZA44XBLLVGnY20x28uT8NQ1BlbqI0Tlr96An4B_PzgPL5_bFFB7SWs8ehSWn9z2SJfClhQpBLfy-2mXvJek_xgibESIlPXqY9Qrg7-PhRmPs3whyiIsnn8tpPMm2XJ_4n0Y-Yfx4nwErGdy84LiKFMDXPEk2a7ndYWs",
+  "kty": "RSA",
+  "q": "0YAcTLBnTrSUiciE0lliIkAidW0TnHP48v-vJitLEz0d8mlTZ_aeOQJm6CUOqF7BqQv3Z8OK_HYKXfOr7xzUlfROONybUXRFE0LvT5Fjvrq-56QGB6GeFq5i6HKlRcC_8TD6WwUJWIzeYuPqhp_FYIpT4ds131d5VYPKDCdY_dM",
+  "d": "VsxW72idEAtoZQDphvxJ0t54EyRfcIJVB9BZuqnyNTfH-VsaUO3st86w_PMU_i0lmyIc8dkCmwOb8R2pRXDo6UxEYUe5YfBnvn9iYF3Ll2QfPOKfZhDBOfqSjEb1po20is7mXTQORBv3bhSo664pasHItTwDz-KKI-FiIu_PYq0lYihuaedUUMp3MQTvDFulpFWEKzqseBDat07BholvxjzlnBK-Ez3KI9qGH8VIIk5TGW5pVu3cQe1WC8NJOe3xR9vu7XX6xvhVLPP7fvKiXJWJ_I_SagAhR1JW0uDJl_b0CrYYeVUnt_pzvW1BeJGz7ysCXcHlLBUh72XrpW-O7Q",
+  "e": "AQAB",
+  "kid": "123",
+  "qi": "9yk0mg4LY48YS8cvG51wMVfKfEjSbt2ygKxqabdsP-qSVpz-KVJtCmbKa57jm2BaMV_mRBQFodxu4XN58VGsj5MzXC5Jb_CkLeQfkp6ZKvehZhiJn3HF0Kb19u9xPvKDclHpKl-UMM1Pcu8Ww52DOyOYcHa1_SLZ05CcOWvMkS8",
+  "dp": "HYtToYeCSxVIE7W42hzZb1IXmwS3e1ok2fbbWwGL47CNPUU-UwQrBvrzwRqkwDcRc7opbV9yKLWGFohPgZ_onSPc3evyqcAUwfvptr8N96LhJgTtSB8tijYpilAZxCxQGuvoVBIJUFcjtsezN6Uhc5VtLEk7GphOKSrGEfnrOiU",
+  "dq": "tF2uf5v0JT-1DnazW4IWydQblqtlEfKKp3LX8W2egh7BNJ3XcA9UI1LdFAord2u1IXwq8YvZkgdyX3bVVNSmdb_SxIOxuMv4WF_tNry-eku-5iFCC7nqKC7U-rkRb19GIToAoPJSHImTQOJmXKcbQEV3eGDJHdLqpGQFRLdvl38",
+  "n": "zg12QaFTsez1EijOYRFzNZdowOt79ePqxCMQ-EEHynUhEZ6TIDnXfjWfuWocS1qRRglUUbHerEtmACUKPQShaG8uL0ZXiLqDr2QSuqrTtr2VUGesxZc6GiqkZlnWFNu5kSUvtemcKxWl8OLFf-5kNnGW4_4xM6BIwosYZnddfFqQT5IP6iTMZIUIKXxY4s1dadYRIiMteNutro67fhOLKabHkyC6ILE6f6VZsYbb_NXC5yC--7DiC2GYKzy7TKmaczuDfQZVgVY-nL9kTPIdhf334EYHQfYmLdvLc56g8-cxY3xh2GnwAj1JcT2u3hsS4KS05bUFHFnveO5uxIYKMQ"
+}
\ No newline at end of file
diff --git a/itests/hive-unit/src/test/resources/auth.jwt/jwt-unauthorized-key.json b/itests/hive-unit/src/test/resources/auth.jwt/jwt-unauthorized-key.json
new file mode 100644
index 0000000..f4845de
--- /dev/null
+++ b/itests/hive-unit/src/test/resources/auth.jwt/jwt-unauthorized-key.json
@@ -0,0 +1,12 @@
+{
+  "p": "wvzuDSY6dIsIJB0UM5BIncN6ui5ee-KHpCmBhh_ia2iX3DluQODEgITw7gDATTDdQsBD-nJLjrqUs5g5Gmt0UgZucXQ5PCt1CK6dLEZCaLivw2fsHYvOKeTkdA49wqLkTc8pkfQs09N-b6NspDDqVJPFffBvFpR_IBFay-xKa5k",
+  "kty": "RSA",
+  "q": "sQzza69VkEmgUm50pEGjgu-OxugOrjcHrjQ42A23YVwAAJ90qPNQa62O7dv5oWmSX2PJ7TgjkzbvtTycLfT_vUeapwfCcJe4WoDg54xF3E35yBvBIwReRiavxf5nWsHEtd5kBg6wRIndGwGUBE91xaLg21spjH7nQKtG9vKeNM8",
+  "d": "UbiPIpr7agQqpM3ERfaXsKNMETyBrIYr3yoggHQ7XQkSPepCgRhE86puRmjf76FtZ3RwpJwjLfO6Ap0fIE9LXXE8otTF9sMnC9fe7odHkEu61Wr3aQM-53dgZoJL7XU53LOo0cNO44SBbw11d2cYlAR3KuCEK7bCLMBOkK1gdxVpgDC7DgxVgnP39bUlf4fA5gQeT5nNGnCWTV4jMVWCyEb0Ck5CvGJp1cCKaMSEvV4j6AM72EkAn8PogTSOJpurRJaTky0De7-ncT2Sv5DCuOIkMhsHqayLbm7a84ORHqsnWpZV85WVW-xxiivkVpqtSDRKCI94pMa9DWszjNJW8Q",
+  "e": "AQAB",
+  "kid": "sig-1642039368",
+  "qi": "CXP_tewCHyXk6PNDcbI0wtXsaWJryOJfMsc7roBCoOwDbTekUFXhOfRmFX5ZTNetRNDpw9nNiQDXt8pyw7UZ-0EhD1cLst1slS__hBi5QEAGo9cUxl3RGeMAFtY9O8B1gjFyKkG5BzdddGBKGQT3Tg23Eyzn6EA_NCw4XAKnkwQ",
+  "dp": "aAdzphZQN595n3LYNU50P59sWeqlRCkuvvnZ_coDDdUGuFr3pKuGix7iP8is0EISuitD2VmjUCnhbhP3202bCKwfvm4Inz58OT6X4mg1xBNMys8mHPla6-UPsY9rie1IKu8suY7xX65FlaA2NT9XtfoE8tUVH5HoZR59N7EAX3k",
+  "dq": "mTkZDO-fgBCH4-7dmS2JIY7KpI897T2IsxVUwH4WXvastd1Jq9FuntGEKYu_HRbtawpEPbzg5M2dY97BVvB5xshKKhWIC8Lx87knapw19XOyIKEMY46rO9DNO-9waNXatH5zV96sY5RgOrgB7j0KMnFEYfIiIgnNfmT8NElB63c",
+  "n": "htq92ltGQrZv19TlhluoqmXjjRXw_NWEd0nPZsWrbLnr8lZ-gOxsjIsDMjb5HNDNmuAS7pg2d_o5ZZAY1sSjKf_EuUPZN-MOej8ZBOtrMxEH7e_t37kYIbbJSuzt55poZdRli6BE8CVDesS4W-wsFZ0MvUazAUADh3onARN7Arf3jwknm5CLafE_JzKrNKZadBElEFEAEu5y9n_SuTlemw3P81lOVmZmjGjfqtPx01O5aV_truMjrQa3NUivu1ihrjvJl0xc3rwJe7qDrfEqgvpBQ-vrAsvg3Jiz5Idj6cU3J0hNtV4ixYxcDQecNlgR7gBeIp3E8BXL1kGOOHYUtw"
+}
\ No newline at end of file
diff --git a/itests/hive-unit/src/test/resources/auth.jwt/jwt-verification-jwks.json b/itests/hive-unit/src/test/resources/auth.jwt/jwt-verification-jwks.json
new file mode 100644
index 0000000..a6fd935
--- /dev/null
+++ b/itests/hive-unit/src/test/resources/auth.jwt/jwt-verification-jwks.json
@@ -0,0 +1,20 @@
+{
+  "keys": [
+    {
+      "kty": "RSA",
+      "e": "AQAB",
+      "alg": "RS256",
+      "kid": "819d1e61429dd3d3caef129c0ac2bae8c6d46fbc",
+      "use": "sig",
+      "n": "qfR12Bcs_hSL0Y1fN5TYZeUQIFmuVRYa210na81BFj91xxwtICJY6ckZCI3Jf0v2tPLOT_iKVk4WBCZ7AVJVvZqHuttkyrdFROMVTe6DwmcjbbkgACMVildTnHy9xy2KuX-OZsEYzgHuRgfe_Y-JN6LoxBYZx6VoBLpgK-F0Q-0O_bRgZhHifVG4ZzARjhgz0PvBb700GtOTHS6mQIfToPErbgqcowKN9k-mJqJr8xpXSHils-Yw97LHjICZmvA5B8EPNW28DwFOE5JrsPcyrFKOAYl4NcSYQgjl-17TWE5_tFdZ8Lz-srjiPMoHlBjZD1C7aO03LI-_9u8lVsktMw"
+    },
+    {
+      "kty": "RSA",
+      "e": "AQAB",
+      "alg": "RS256",
+      "kid": "123",
+      "use": "sig",
+      "n": "zg12QaFTsez1EijOYRFzNZdowOt79ePqxCMQ-EEHynUhEZ6TIDnXfjWfuWocS1qRRglUUbHerEtmACUKPQShaG8uL0ZXiLqDr2QSuqrTtr2VUGesxZc6GiqkZlnWFNu5kSUvtemcKxWl8OLFf-5kNnGW4_4xM6BIwosYZnddfFqQT5IP6iTMZIUIKXxY4s1dadYRIiMteNutro67fhOLKabHkyC6ILE6f6VZsYbb_NXC5yC--7DiC2GYKzy7TKmaczuDfQZVgVY-nL9kTPIdhf334EYHQfYmLdvLc56g8-cxY3xh2GnwAj1JcT2u3hsS4KS05bUFHFnveO5uxIYKMQ"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
index 5fcad06..abc5438 100644
--- a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
+++ b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
@@ -65,6 +65,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Properties;
 import java.util.concurrent.Executor;
 import java.util.concurrent.locks.ReentrantLock;
@@ -88,6 +89,7 @@ import org.apache.hadoop.security.Credentials;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.token.Token;
 import org.apache.hadoop.security.token.TokenIdentifier;
+import org.apache.hive.jdbc.jwt.HttpJwtAuthRequestInterceptor;
 import org.apache.hive.jdbc.saml.HiveJdbcBrowserClientFactory;
 import org.apache.hive.jdbc.saml.HiveJdbcSamlRedirectStrategy;
 import org.apache.hive.jdbc.saml.HttpSamlAuthRequestInterceptor;
@@ -594,6 +596,12 @@ public class HiveConnection implements java.sql.Connection {
           host, getServerHttpUrl(useSsl), loggedInSubject, cookieStore, cookieName,
           useSsl, additionalHttpHeaders,
           customCookies);
+    } else if (isJwtAuthMode()) {
+      final String signedJwt = getJWT();
+      Preconditions.checkArgument(signedJwt != null && !signedJwt.isEmpty(), "For jwt auth mode," +
+          " a signed jwt must be provided");
+      requestInterceptor = new HttpJwtAuthRequestInterceptor(signedJwt, cookieStore,
+          cookieName, useSsl, additionalHttpHeaders, customCookies);
     } else if (isBrowserAuthMode()) {
       requestInterceptor = new HttpSamlAuthRequestInterceptor(browserClient, cookieStore,
           cookieName, useSsl, additionalHttpHeaders, customCookies);
@@ -804,6 +812,38 @@ public class HiveConnection implements java.sql.Connection {
     return httpClientBuilder.build();
   }
 
+  private String getJWT() {
+    String jwtCredential = getJWTStringFromSession();
+    if (jwtCredential == null || jwtCredential.isEmpty()) {
+      jwtCredential = getJWTStringFromEnv();
+    }
+    return jwtCredential;
+  }
+
+  private String getJWTStringFromEnv() {
+    String jwtCredential = System.getenv(JdbcConnectionParams.AUTH_JWT_ENV);
+    if (jwtCredential == null || jwtCredential.isEmpty()) {
+      LOG.debug("No JWT is specified in env variable {}", JdbcConnectionParams.AUTH_JWT_ENV);
+    } else {
+      int startIndex = Math.max(0, jwtCredential.length() - 7);
+      String lastSevenChars = jwtCredential.substring(startIndex);
+      LOG.debug("Fetched JWT (ends with {}) from the env.", lastSevenChars);
+    }
+    return jwtCredential;
+  }
+
+  private String getJWTStringFromSession() {
+    String jwtCredential = sessConfMap.get(JdbcConnectionParams.AUTH_TYPE_JWT_KEY);
+    if (jwtCredential == null || jwtCredential.isEmpty()) {
+      LOG.debug("No JWT is specified in connection string.");
+    } else {
+      int startIndex = Math.max(0, jwtCredential.length() - 7);
+      String lastSevenChars = jwtCredential.substring(startIndex);
+      LOG.debug("Fetched JWT (ends with {}) from the session.", lastSevenChars);
+    }
+    return jwtCredential;
+  }
+
   /**
    * Create underlying SSL or non-SSL transport
    *
@@ -1245,6 +1285,11 @@ public class HiveConnection implements java.sql.Connection {
         .equals(sessConfMap.get(JdbcConnectionParams.AUTH_TYPE));
   }
 
+  private boolean isJwtAuthMode() {
+    return JdbcConnectionParams.AUTH_TYPE_JWT.equalsIgnoreCase(sessConfMap.get(JdbcConnectionParams.AUTH_TYPE))
+        || sessConfMap.containsKey(JdbcConnectionParams.AUTH_TYPE_JWT_KEY);
+  }
+
   /**
    * This checks for {@code JdbcConnectionParams.AUTH_BROWSER_DISABLE_SSL_VALIDATION}
    * on the connection url and returns the boolean value of it. Returns false if the
diff --git a/jdbc/src/java/org/apache/hive/jdbc/Utils.java b/jdbc/src/java/org/apache/hive/jdbc/Utils.java
index 0fd4820..764ae11 100644
--- a/jdbc/src/java/org/apache/hive/jdbc/Utils.java
+++ b/jdbc/src/java/org/apache/hive/jdbc/Utils.java
@@ -99,6 +99,9 @@ public class Utils {
     public static final String AUTH_PASSWD = "password";
     public static final String AUTH_KERBEROS_AUTH_TYPE = "kerberosAuthType";
     public static final String AUTH_KERBEROS_AUTH_TYPE_FROM_SUBJECT = "fromSubject";
+    public static final String AUTH_TYPE_JWT = "jwt";
+    public static final String AUTH_TYPE_JWT_KEY = "jwt";
+    public static final String AUTH_JWT_ENV = "JWT";
     // JdbcConnection param which specifies if we need to use a browser to do
     // authentication.
     // JdbcConnectionParam which specifies if the authMode is done via a browser
diff --git a/jdbc/src/java/org/apache/hive/jdbc/jwt/HttpJwtAuthRequestInterceptor.java b/jdbc/src/java/org/apache/hive/jdbc/jwt/HttpJwtAuthRequestInterceptor.java
new file mode 100644
index 0000000..51390c6
--- /dev/null
+++ b/jdbc/src/java/org/apache/hive/jdbc/jwt/HttpJwtAuthRequestInterceptor.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hive.jdbc.jwt;
+
+import org.apache.hive.jdbc.HttpRequestInterceptorBase;
+import org.apache.hive.service.auth.HttpAuthUtils;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpRequest;
+import org.apache.http.client.CookieStore;
+import org.apache.http.protocol.HttpContext;
+
+import java.util.Map;
+
+/**
+ * This implements the logic to intercept the HTTP requests from the Hive Jdbc connection
+ * and adds JWT auth header.
+ */
+public class HttpJwtAuthRequestInterceptor extends HttpRequestInterceptorBase {
+
+  private final String signedJwt;
+
+  public HttpJwtAuthRequestInterceptor(String signedJwt, CookieStore cookieStore, String cn,
+                                       boolean isSSL, Map<String, String> additionalHeaders,
+                                       Map<String, String> customCookies) {
+    super(cookieStore, cn, isSSL, additionalHeaders, customCookies);
+    this.signedJwt = signedJwt;
+  }
+
+  @Override
+  protected void addHttpAuthHeader(HttpRequest httpRequest, HttpContext httpContext) {
+    httpRequest.addHeader(HttpHeaders.AUTHORIZATION, HttpAuthUtils.BEARER + " " + signedJwt);
+  }
+}
diff --git a/jdbc/src/java/org/apache/hive/jdbc/saml/HttpSamlAuthRequestInterceptor.java b/jdbc/src/java/org/apache/hive/jdbc/saml/HttpSamlAuthRequestInterceptor.java
index ad3d89d..430dc8d 100644
--- a/jdbc/src/java/org/apache/hive/jdbc/saml/HttpSamlAuthRequestInterceptor.java
+++ b/jdbc/src/java/org/apache/hive/jdbc/saml/HttpSamlAuthRequestInterceptor.java
@@ -21,6 +21,7 @@ package org.apache.hive.jdbc.saml;
 import com.google.common.base.Preconditions;
 import java.util.Map;
 import org.apache.hive.jdbc.HttpRequestInterceptorBase;
+import org.apache.hive.service.auth.HttpAuthUtils;
 import org.apache.hive.service.auth.saml.HiveSamlUtils;
 import org.apache.http.HttpHeaders;
 import org.apache.http.HttpRequest;
@@ -37,7 +38,6 @@ import org.slf4j.LoggerFactory;
 public class HttpSamlAuthRequestInterceptor extends HttpRequestInterceptorBase {
 
   private final IJdbcBrowserClient browserClient;
-  private static final String BEARER = "Bearer ";
   private static final Logger LOG = LoggerFactory
       .getLogger(HttpSamlAuthRequestInterceptor.class);
 
@@ -56,7 +56,7 @@ public class HttpSamlAuthRequestInterceptor extends HttpRequestInterceptorBase {
         : browserClient.getServerResponse().getToken();
     String clientIdentifier = browserClient.getClientIdentifier();
     if (token != null && !token.isEmpty()) {
-      httpRequest.addHeader(HttpHeaders.AUTHORIZATION, BEARER + token);
+      httpRequest.addHeader(HttpHeaders.AUTHORIZATION, HttpAuthUtils.BEARER + " " + token);
       httpRequest.addHeader(HiveSamlUtils.SSO_CLIENT_IDENTIFIER, clientIdentifier);
       httpRequest.removeHeaders(HiveSamlUtils.SSO_TOKEN_RESPONSE_PORT);
     } else {
diff --git a/service/pom.xml b/service/pom.xml
index 6aca7e2..c2f2ccb 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -25,6 +25,7 @@
   <name>Hive Service</name>
   <properties>
     <hive.path.to.root>..</hive.path.to.root>
+    <nimbus-jose-jwt.version>9.20</nimbus-jose-jwt.version>
   </properties>
   <dependencies>
     <!-- dependencies are always listed in sorted order by groupId, artifactId -->
@@ -78,6 +79,11 @@
     </dependency>
     <!-- inter-project -->
     <dependency>
+      <groupId>com.nimbusds</groupId>
+      <artifactId>nimbus-jose-jwt</artifactId>
+      <version>${nimbus-jose-jwt.version}</version>
+    </dependency>
+    <dependency>
       <groupId>commons-codec</groupId>
       <artifactId>commons-codec</artifactId>
     </dependency>
diff --git a/service/src/java/org/apache/hive/service/auth/HiveAuthConstants.java b/service/src/java/org/apache/hive/service/auth/HiveAuthConstants.java
index 0d71df0..f1a5114 100644
--- a/service/src/java/org/apache/hive/service/auth/HiveAuthConstants.java
+++ b/service/src/java/org/apache/hive/service/auth/HiveAuthConstants.java
@@ -26,7 +26,8 @@ public class HiveAuthConstants {
     KERBEROS("KERBEROS"),
     CUSTOM("CUSTOM"),
     PAM("PAM"),
-    SAML("SAML");
+    SAML("SAML"),
+    JWT("JWT");
 
     private final String authType;
 
diff --git a/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java b/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java
index 31985d9..efaec8a 100644
--- a/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java
+++ b/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java
@@ -48,6 +48,7 @@ public final class HttpAuthUtils {
   public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
   public static final String AUTHORIZATION = "Authorization";
   public static final String BASIC = "Basic";
+  public static final String BEARER = "Bearer";
   public static final String NEGOTIATE = "Negotiate";
   private static final Logger LOG = LoggerFactory.getLogger(HttpAuthUtils.class);
   private static final String COOKIE_ATTR_SEPARATOR = "&";
diff --git a/service/src/java/org/apache/hive/service/auth/jwt/JWTValidator.java b/service/src/java/org/apache/hive/service/auth/jwt/JWTValidator.java
new file mode 100644
index 0000000..a1b934f
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/jwt/JWTValidator.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hive.service.auth.jwt;
+
+import com.google.common.base.Preconditions;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
+import com.nimbusds.jose.jwk.AsymmetricJWK;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.sasl.AuthenticationException;
+import java.io.IOException;
+import java.security.Key;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * This class is used to validate JWT. JWKS is fetched during instantiation and kept in the memory.
+ * We disallow JWT signature verification with symmetric key, because that means anyone can get the same key
+ * and use it to sign a JWT.
+ */
+public class JWTValidator {
+
+  private static final Logger LOG = LoggerFactory.getLogger(JWTValidator.class.getName());
+  private static final DefaultJWSVerifierFactory JWS_VERIFIER_FACTORY = new DefaultJWSVerifierFactory();
+
+  private final URLBasedJWKSProvider jwksProvider;
+
+  public JWTValidator(HiveConf conf) throws IOException, ParseException {
+    this.jwksProvider = new URLBasedJWKSProvider(conf);
+  }
+
+  public String validateJWTAndExtractUser(String signedJwt) throws ParseException, AuthenticationException {
+    Preconditions.checkNotNull(jwksProvider);
+    Preconditions.checkNotNull(signedJwt, "No token found");
+    final SignedJWT parsedJwt = SignedJWT.parse(signedJwt);
+    List<JWK> matchedJWKS = jwksProvider.getJWKs(parsedJwt.getHeader());
+    if (matchedJWKS.isEmpty()) {
+      throw new AuthenticationException("Failed to find matched JWKs with the JWT header: " + parsedJwt.getHeader());
+    }
+
+    // verify signature
+    Exception lastException = null;
+    for (JWK matchedJWK : matchedJWKS) {
+      String keyID = matchedJWK.getKeyID() == null ? "null" : matchedJWK.getKeyID();
+      try {
+        JWSVerifier verifier = getVerifier(parsedJwt.getHeader(), matchedJWK);
+        if (parsedJwt.verify(verifier)) {
+          LOG.debug("Verified JWT {} by JWK {}", parsedJwt.getPayload(), keyID);
+          break;
+        }
+      } catch (Exception e) {
+        lastException = e;
+        LOG.warn("Failed to verify JWT {} by JWK {}", parsedJwt.getPayload(), keyID, e);
+      }
+    }
+    // We use only the last seven characters to let a user can differentiate exceptions for different JWT
+    int startIndex = Math.max(0, signedJwt.length() - 7);
+    String lastSevenChars = signedJwt.substring(startIndex);
+    if (parsedJwt.getState() != JWSObject.State.VERIFIED) {
+      throw new AuthenticationException("Failed to verify the JWT signature (ends with " + lastSevenChars + ")",
+          lastException);
+    }
+
+    // verify claims
+    JWTClaimsSet claimsSet = parsedJwt.getJWTClaimsSet();
+    Date expirationTime = claimsSet.getExpirationTime();
+    if (expirationTime != null) {
+      Date now = new Date();
+      if (now.after(expirationTime)) {
+        LOG.warn("Rejecting an expired JWT: {}", parsedJwt.getPayload());
+        throw new AuthenticationException("JWT (ends with " + lastSevenChars + ") has been expired");
+      }
+    }
+
+    // We assume the subject of claims is the query user
+    return claimsSet.getSubject();
+  }
+
+  private static JWSVerifier getVerifier(JWSHeader header, JWK jwk) throws JOSEException {
+    Preconditions.checkArgument(jwk instanceof AsymmetricJWK,
+        "JWT signature verification with symmetric key is not allowed.");
+    Key key = ((AsymmetricJWK) jwk).toPublicKey();
+    return JWS_VERIFIER_FACTORY.createJWSVerifier(header, key);
+  }
+}
diff --git a/service/src/java/org/apache/hive/service/auth/jwt/URLBasedJWKSProvider.java b/service/src/java/org/apache/hive/service/auth/jwt/URLBasedJWKSProvider.java
new file mode 100644
index 0000000..ebf99e3
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/jwt/URLBasedJWKSProvider.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hive.service.auth.jwt;
+
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKMatcher;
+import com.nimbusds.jose.jwk.JWKSelector;
+import com.nimbusds.jose.jwk.JWKSet;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.sasl.AuthenticationException;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides a way to get JWKS json. Hive will use this to verify the incoming JWTs.
+ */
+public class URLBasedJWKSProvider {
+
+  private static final Logger LOG = LoggerFactory.getLogger(URLBasedJWKSProvider.class.getName());
+  private final HiveConf conf;
+  private List<JWKSet> jwkSets = new ArrayList<>();
+
+  public URLBasedJWKSProvider(HiveConf conf) throws IOException, ParseException {
+    this.conf = conf;
+    loadJWKSets();
+  }
+
+  /**
+   * Fetches the JWKS and stores into memory. The JWKS are expected to be in the standard form as defined here -
+   * https://datatracker.ietf.org/doc/html/rfc7517#appendix-A.
+   */
+  private void loadJWKSets() throws IOException, ParseException {
+    String jwksURL = HiveConf.getVar(conf, HiveConf.ConfVars.HIVE_SERVER2_AUTHENTICATION_JWT_JWKS_URL);
+    String[] jwksURLs = jwksURL.split(",");
+    for (String urlString : jwksURLs) {
+      URL url = new URL(urlString);
+      jwkSets.add(JWKSet.load(url));
+      LOG.info("Loaded JWKS from " + urlString);
+    }
+  }
+
+  /**
+   * Returns filtered JWKS by one or more criteria, such as kid, typ, alg.
+   */
+  public List<JWK> getJWKs(JWSHeader header) throws AuthenticationException {
+    JWKMatcher matcher = JWKMatcher.forJWSHeader(header);
+    if (matcher == null) {
+      throw new AuthenticationException("Unsupported algorithm: " + header.getAlgorithm());
+    }
+
+    List<JWK> jwks = new ArrayList<>();
+    JWKSelector selector = new JWKSelector(matcher);
+    for (JWKSet jwkSet : jwkSets) {
+      jwks.addAll(selector.select(jwkSet));
+    }
+    return jwks;
+  }
+}
diff --git a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
index 244bd3a..bbb74e0 100644
--- a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
+++ b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
@@ -24,6 +24,7 @@ import java.net.InetAddress;
 import java.nio.charset.StandardCharsets;
 import java.security.PrivilegedExceptionAction;
 import java.security.SecureRandom;
+import java.text.ParseException;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collections;
@@ -41,6 +42,7 @@ import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.core.NewCookie;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
 import com.google.common.io.ByteStreams;
 
 import org.apache.hadoop.hive.conf.HiveConf;
@@ -59,6 +61,7 @@ import org.apache.hive.service.auth.HttpAuthUtils;
 import org.apache.hive.service.auth.HttpAuthenticationException;
 import org.apache.hive.service.auth.PasswdAuthenticationProvider;
 import org.apache.hive.service.auth.PlainSaslHelper;
+import org.apache.hive.service.auth.jwt.JWTValidator;
 import org.apache.hive.service.auth.ldap.HttpEmptyAuthenticationException;
 import org.apache.hive.service.auth.saml.HiveSaml2Client;
 import org.apache.hive.service.auth.saml.HiveSamlRelayStateStore;
@@ -110,6 +113,8 @@ public class ThriftHttpServlet extends TServlet {
   private static final String HIVE_DELEGATION_TOKEN_HEADER =  "X-Hive-Delegation-Token";
   private static final String X_FORWARDED_FOR = "X-Forwarded-For";
 
+  private JWTValidator jwtValidator;
+
   public ThriftHttpServlet(TProcessor processor, TProtocolFactory protocolFactory,
       String authType, UserGroupInformation serviceUGI, UserGroupInformation httpUGI,
       HiveAuthFactory hiveAuthFactory, HiveConf hiveConf) throws Exception {
@@ -136,6 +141,9 @@ public class ThriftHttpServlet extends TServlet {
       this.isHttpOnlyCookie = hiveConf.getBoolVar(
         ConfVars.HIVE_SERVER2_THRIFT_HTTP_COOKIE_IS_HTTPONLY);
     }
+    if (this.authType.isEnabled(HiveAuthConstants.AuthTypes.JWT)) {
+      this.jwtValidator = new JWTValidator(hiveConf);
+    }
   }
 
   @Override
@@ -213,6 +221,8 @@ public class ThriftHttpServlet extends TServlet {
             } else {
               clientUserName = doKerberosAuth(request);
             }
+          } else if (authType.isEnabled(HiveAuthConstants.AuthTypes.JWT) && hasJWT(request)) {
+            clientUserName = validateJWT(request, response);
           } else if (authType.isEnabled(HiveAuthConstants.AuthTypes.SAML)) {
             // check if this request needs a SAML redirect
             String authHeader = request.getHeader(HttpAuthUtils.AUTHORIZATION);
@@ -264,8 +274,7 @@ public class ThriftHttpServlet extends TServlet {
         LOG.info("Cookie added for clientUserName " + clientUserName);
       }
       super.doPost(request, response);
-    }
-    catch (HttpAuthenticationException e) {
+    } catch (HttpAuthenticationException e) {
       // Ignore HttpEmptyAuthenticationException, it is normal for knox
       // to send a request with empty header
       if (!(e instanceof HttpEmptyAuthenticationException)) {
@@ -292,8 +301,7 @@ public class ThriftHttpServlet extends TServlet {
         }
       }
       response.getWriter().println("Authentication Error: " + e.getMessage());
-    }
-    finally {
+    } finally {
       // Clear the thread locals
       SessionManager.clearUserName();
       SessionManager.clearIpAddress();
@@ -302,6 +310,23 @@ public class ThriftHttpServlet extends TServlet {
     }
   }
 
+  private String validateJWT(HttpServletRequest request, HttpServletResponse response)
+      throws HttpAuthenticationException {
+    Preconditions.checkState(jwtValidator != null, "JWT validator should have been set");
+    String signedJwt = extractBearerToken(request, response);
+    String user = null;
+    try {
+      user = jwtValidator.validateJWTAndExtractUser(signedJwt);
+      Preconditions.checkNotNull(user, "JWT needs to contain the user name as subject");
+      Preconditions.checkState(!user.isEmpty(), "User name should not be empty");
+      LOG.info("JWT verification successful for user {}", user);
+    } catch (Exception e) {
+      LOG.error("JWT verification failed", e);
+      throw new HttpAuthenticationException(e);
+    }
+    return user;
+  }
+
   /**
    * A request needs redirect if it does not have a bearer token and it contains a valid
    * response port in its header.
@@ -672,7 +697,8 @@ public class ThriftHttpServlet extends TServlet {
 
   private String getUsername(HttpServletRequest request)
       throws HttpAuthenticationException {
-    String creds[] = getAuthHeaderTokens(request);
+    String authHeaderDecodedString = getAuthHeaderDecodedString(request);
+    String[] creds = authHeaderDecodedString.split(":", 2);
     // Username must be present
     if (creds[0] == null || creds[0].isEmpty()) {
       throw new HttpAuthenticationException("Authorization header received " +
@@ -683,23 +709,23 @@ public class ThriftHttpServlet extends TServlet {
 
   private String getPassword(HttpServletRequest request)
       throws HttpAuthenticationException {
-    String[] creds = getAuthHeaderTokens(request);
+    String authHeaderDecodedString = getAuthHeaderDecodedString(request);
+    String[] creds = authHeaderDecodedString.split(":", 2);
     // Password must be present
-    if (creds[1] == null || creds[1].isEmpty()) {
+    if (creds.length < 2 || creds[1] == null || creds[1].isEmpty()) {
       throw new HttpAuthenticationException("Authorization header received " +
-          "from the client does not contain username.");
+          "from the client does not contain password.");
     }
     return creds[1];
   }
 
-  private String[] getAuthHeaderTokens(HttpServletRequest request) throws HttpAuthenticationException {
+  private String getAuthHeaderDecodedString(HttpServletRequest request) throws HttpAuthenticationException {
     String authHeaderBase64Str = getAuthHeader(request);
-    String authHeaderString = new String(Base64.getDecoder().decode(authHeaderBase64Str), StandardCharsets.UTF_8);
-    return authHeaderString.split(":");
+    return new String(Base64.getDecoder().decode(authHeaderBase64Str), StandardCharsets.UTF_8);
   }
 
   /**
-   * Returns the base64 encoded auth header payload
+   * Returns the base64 encoded auth header payload.
    * @param request request to interrogate
    * @return base64 encoded auth header payload
    * @throws HttpAuthenticationException exception if header is missing or empty
@@ -732,6 +758,18 @@ public class ThriftHttpServlet extends TServlet {
     return authType.isEnabled(HiveAuthConstants.AuthTypes.KERBEROS);
   }
 
+  private boolean hasJWT(HttpServletRequest request) {
+    String authHeaderString;
+    try {
+      authHeaderString = getAuthHeader(request);
+    } catch (HttpAuthenticationException e) {
+      return false;
+    }
+    // Assume JWT consists of three parts separated by dots
+    String[] jwt = authHeaderString.split("\\.");
+    return jwt.length == 3;
+  }
+
   private static String getDoAsQueryParam(String queryString) {
     if (LOG.isDebugEnabled()) {
       LOG.debug("URL query string:" + queryString);