You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ch...@apache.org on 2022/06/16 02:33:10 UTC

[incubator-kyuubi] branch master updated: [KYUUBI #886] Add HTTP transport mode support to KYUUBI - no Kerberos support

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

chengpan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new 1ea245d25 [KYUUBI #886] Add HTTP transport mode support to KYUUBI - no Kerberos support
1ea245d25 is described below

commit 1ea245d254f2b6c8ef58bdfff9d457bf9307e567
Author: Mahmoud Bahaa <ma...@incorta.com>
AuthorDate: Thu Jun 16 10:33:00 2022 +0800

    [KYUUBI #886] Add HTTP transport mode support to KYUUBI - no Kerberos support
    
    Same as https://github.com/apache/incubator-kyuubi/pull/2815 but with Kerberos auth removed as was not complete nor tested. so thought to make a separated PR with no Kerberos support then we can add Kerberos support later on
    
    ### _Why are the changes needed?_
    
    Add http transport protocol support to Kyuubi
    main code was taken from:
    
    1. https://github.com/SteNicholas/incubator-kyuubi/commit/25ac0731d7331616988026996ca918793af151e9
    2. https://github.com/apache/hive/blob/branch-3.1/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java
    2. https://github.com/apache/hive/blob/branch-3.1/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
    
    ### _How was this patch tested?_
    - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible
    
    - [ ] Add screenshots for manual tests if appropriate
    
    - [ ] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request
    
    Closes #2833 from mahmoudbahaa/transport-mode-http-no-kerberos.
    
    Closes #886
    
    3a24abec [Mahmoud Bahaa] fix test cases
    ce982af7 [Mahmoud Bahaa] [KYUUBI #886] Add HTTP transport mode support to KYUUBI - no kerberos support
    b6371859 [SteNicholas] [KYUUBI #886] Add HTTP transport mode support to KYUUBI
    36fb0760 [Mahmoud Bahaa] update .gitignore to include conf non template files
    
    Lead-authored-by: Mahmoud Bahaa <ma...@incorta.com>
    Co-authored-by: SteNicholas <pr...@163.com>
    Signed-off-by: Cheng Pan <ch...@apache.org>
---
 .gitignore                                         |   3 +
 docs/deployment/settings.md                        |  20 +-
 .../org/apache/kyuubi/config/KyuubiConf.scala      | 135 ++++++-
 .../kyuubi/service/TBinaryFrontendService.scala    |   2 +
 .../apache/kyuubi/service/TFrontendService.scala   |  16 +-
 .../KyuubiAuthenticationFactory.scala              |  26 ++
 .../org/apache/kyuubi/config/KyuubiConfSuite.scala |   4 +
 .../apache/kyuubi/operation/SparkQueryTests.scala  |   4 +
 .../apache/kyuubi/metrics/MetricsConstants.scala   |   5 +
 .../org/apache/kyuubi/server/KyuubiServer.scala    |   1 +
 .../kyuubi/server/KyuubiTHttpFrontendService.scala | 283 +++++++++++++++
 .../kyuubi/server/http/ThriftHttpServlet.scala     | 390 +++++++++++++++++++++
 .../kyuubi/server/http/util/CookieSigner.scala     |  89 +++++
 .../kyuubi/server/http/util/HttpAuthUtils.scala    |  97 +++++
 .../kyuubi/server/http/util/SessionManager.scala   |  89 +++++
 ...rationThriftHttpKerberosAndPlainAuthSuite.scala |  42 +++
 .../KyuubiOperationThriftHttpPerUserSuite.scala    |  33 ++
 17 files changed, 1231 insertions(+), 8 deletions(-)

diff --git a/.gitignore b/.gitignore
index b36d791d1..d5b95c21e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,3 +74,6 @@ embedded_zookeeper/
 **/operation_logs/
 **/server_operation_logs/
 **/engine_operation_logs/
+conf/log4j2.properties
+conf/kyuubi-defaults.conf
+conf/kyuubi-env.sh
diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md
index abeecd1bb..7acd94c18 100644
--- a/docs/deployment/settings.md
+++ b/docs/deployment/settings.md
@@ -269,12 +269,30 @@ kyuubi.frontend.mysql.max.worker.threads|999|Maximum number of threads in the co
 kyuubi.frontend.mysql.min.worker.threads|9|Minimum number of threads in the command execution thread pool for the MySQL frontend service|int|1.4.0
 kyuubi.frontend.mysql.netty.worker.threads|&lt;undefined&gt;|Number of thread in the netty worker event loop of MySQL frontend service. Use min(cpu_cores, 8) in default.|int|1.4.0
 kyuubi.frontend.mysql.worker.keepalive.time|PT1M|Time(ms) that an idle async thread of the command execution thread pool will wait for a new task to arrive before terminating in MySQL frontend service|duration|1.4.0
-kyuubi.frontend.protocols|THRIFT_BINARY|A comma separated list for all frontend protocols <ul> <li>THRIFT_BINARY - HiveServer2 compatible thrift binary protocol.</li> <li>REST - Kyuubi defined REST API(experimental).</li>  <li>MYSQL - MySQL compatible text protocol(experimental).</li> </ul>|seq|1.4.0
+kyuubi.frontend.protocols|THRIFT_BINARY|A comma separated list for all frontend protocols <ul> <li>THRIFT_BINARY - HiveServer2 compatible thrift binary protocol.</li> <li>THRIFT_HTTP - HiveServer2 compatible thrift http protocol.</li> <li>REST - Kyuubi defined REST API(experimental).</li>  <li>MYSQL - MySQL compatible text protocol(experimental).</li> </ul>|seq|1.4.0
 kyuubi.frontend.rest.bind.host|&lt;undefined&gt;|Hostname or IP of the machine on which to run the REST frontend service.|string|1.4.0
 kyuubi.frontend.rest.bind.port|10099|Port of the machine on which to run the REST frontend service.|int|1.4.0
 kyuubi.frontend.thrift.backoff.slot.length|PT0.1S|Time to back off during login to the thrift frontend service.|duration|1.4.0
 kyuubi.frontend.thrift.binary.bind.host|&lt;undefined&gt;|Hostname or IP of the machine on which to run the thrift frontend service via binary protocol.|string|1.4.0
 kyuubi.frontend.thrift.binary.bind.port|10009|Port of the machine on which to run the thrift frontend service via binary protocol.|int|1.4.0
+kyuubi.frontend.thrift.http.allow.user.substitution|true|Allow alternate user to be specified as part of open connection request when using HTTP transport mode.|boolean|1.6.0
+kyuubi.frontend.thrift.http.bind.host|&lt;undefined&gt;|Hostname or IP of the machine on which to run the thrift frontend service via http protocol.|string|1.6.0
+kyuubi.frontend.thrift.http.bind.port|10010|Port of the machine on which to run the thrift frontend service via http protocol.|int|1.6.0
+kyuubi.frontend.thrift.http.compression.enabled|true|Enable thrift http compression via Jetty compression support|boolean|1.6.0
+kyuubi.frontend.thrift.http.cookie.auth.enabled|true|When true, Kyuubi in HTTP transport mode, will use cookie based authentication mechanism|boolean|1.6.0
+kyuubi.frontend.thrift.http.cookie.domain|&lt;undefined&gt;|Domain for the Kyuubi generated cookies|string|1.6.0
+kyuubi.frontend.thrift.http.cookie.is.httponly|true|HttpOnly attribute of the Kyuubi generated cookie.|boolean|1.6.0
+kyuubi.frontend.thrift.http.cookie.max.age|86400|Maximum age in seconds for server side cookie used by Kyuubi in HTTP mode.|int|1.6.0
+kyuubi.frontend.thrift.http.cookie.path|&lt;undefined&gt;|Path for the Kyuubi generated cookies|string|1.6.0
+kyuubi.frontend.thrift.http.max.idle.time|PT30M|Maximum idle time for a connection on the server when in HTTP mode.|duration|1.6.0
+kyuubi.frontend.thrift.http.path|cliservice|Path component of URL endpoint when in HTTP mode.|string|1.6.0
+kyuubi.frontend.thrift.http.request.header.size|6144|Request header size in bytes, when using HTTP transport mode. Jetty defaults used.|int|1.6.0
+kyuubi.frontend.thrift.http.response.header.size|6144|Response header size in bytes, when using HTTP transport mode. Jetty defaults used.|int|1.6.0
+kyuubi.frontend.thrift.http.ssl.keystore.password|&lt;undefined&gt;|SSL certificate keystore password.|string|1.6.0
+kyuubi.frontend.thrift.http.ssl.keystore.path|&lt;undefined&gt;|SSL certificate keystore location.|string|1.6.0
+kyuubi.frontend.thrift.http.ssl.protocol.blacklist|SSLv2,SSLv3|SSL Versions to disable when using HTTP transport mode.|string|1.6.0
+kyuubi.frontend.thrift.http.use.SSL|false|Set this to true for using SSL encryption in http mode.|boolean|1.6.0
+kyuubi.frontend.thrift.http.xsrf.filter.enabled|false|If enabled, Kyuubi will block any requests made to it over http if an X-XSRF-HEADER header is not present|boolean|1.6.0
 kyuubi.frontend.thrift.login.timeout|PT20S|Timeout for Thrift clients during login to the thrift frontend service.|duration|1.4.0
 kyuubi.frontend.thrift.max.message.size|104857600|Maximum message size in bytes a Kyuubi server will accept.|int|1.4.0
 kyuubi.frontend.thrift.max.worker.threads|999|Maximum number of threads in the of frontend worker thread pool for the thrift frontend service|int|1.4.0
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
index 7d10dd8b4..8d4521e26 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
@@ -135,6 +135,8 @@ case class KyuubiConf(loadSysDefault: Boolean = true) extends Logging {
     FRONTEND_BIND_PORT,
     FRONTEND_THRIFT_BINARY_BIND_HOST,
     FRONTEND_THRIFT_BINARY_BIND_PORT,
+    FRONTEND_THRIFT_HTTP_BIND_HOST,
+    FRONTEND_THRIFT_HTTP_BIND_PORT,
     FRONTEND_REST_BIND_HOST,
     FRONTEND_REST_BIND_PORT,
     FRONTEND_MYSQL_BIND_HOST,
@@ -300,7 +302,7 @@ object KyuubiConf {
 
   object FrontendProtocols extends Enumeration {
     type FrontendProtocol = Value
-    val THRIFT_BINARY, REST, MYSQL = Value
+    val THRIFT_BINARY, THRIFT_HTTP, REST, MYSQL = Value
   }
 
   val FRONTEND_PROTOCOLS: ConfigEntry[Seq[String]] =
@@ -308,6 +310,7 @@ object KyuubiConf {
       .doc("A comma separated list for all frontend protocols " +
         "<ul>" +
         " <li>THRIFT_BINARY - HiveServer2 compatible thrift binary protocol.</li>" +
+        " <li>THRIFT_HTTP - HiveServer2 compatible thrift http protocol.</li>" +
         " <li>REST - Kyuubi defined REST API(experimental).</li> " +
         " <li>MYSQL - MySQL compatible text protocol(experimental).</li> " +
         "</ul>")
@@ -349,6 +352,21 @@ object KyuubiConf {
       .version("1.4.0")
       .fallbackConf(FRONTEND_BIND_PORT)
 
+  val FRONTEND_THRIFT_HTTP_BIND_HOST: ConfigEntry[Option[String]] =
+    buildConf("kyuubi.frontend.thrift.http.bind.host")
+      .doc("Hostname or IP of the machine on which to run the thrift frontend service " +
+        "via http protocol.")
+      .version("1.6.0")
+      .fallbackConf(FRONTEND_BIND_HOST)
+
+  val FRONTEND_THRIFT_HTTP_BIND_PORT: ConfigEntry[Int] =
+    buildConf("kyuubi.frontend.thrift.http.bind.port")
+      .doc("Port of the machine on which to run the thrift frontend service via http protocol.")
+      .version("1.6.0")
+      .intConf
+      .checkValue(p => p == 0 || (p > 1024 && p < 65535), "Invalid Port number")
+      .createWithDefault(10010)
+
   val FRONTEND_MIN_WORKER_THREADS: ConfigEntry[Int] =
     buildConf("kyuubi.frontend.min.worker.threads")
       .doc("(deprecated) Minimum number of threads in the of frontend worker thread pool for " +
@@ -434,6 +452,121 @@ object KyuubiConf {
       .version("1.4.0")
       .fallbackConf(FRONTEND_LOGIN_BACKOFF_SLOT_LENGTH)
 
+  val FRONTEND_THRIFT_HTTP_REQUEST_HEADER_SIZE: ConfigEntry[Int] =
+    buildConf("kyuubi.frontend.thrift.http.request.header.size")
+      .doc("Request header size in bytes, when using HTTP transport mode. Jetty defaults used.")
+      .version("1.6.0")
+      .intConf
+      .createWithDefault(6 * 1024)
+
+  val FRONTEND_THRIFT_HTTP_RESPONSE_HEADER_SIZE: ConfigEntry[Int] =
+    buildConf("kyuubi.frontend.thrift.http.response.header.size")
+      .doc("Response header size in bytes, when using HTTP transport mode. Jetty defaults used.")
+      .version("1.6.0")
+      .intConf
+      .createWithDefault(6 * 1024)
+
+  val FRONTEND_THRIFT_HTTP_MAX_IDLE_TIME: ConfigEntry[Long] =
+    buildConf("kyuubi.frontend.thrift.http.max.idle.time")
+      .doc("Maximum idle time for a connection on the server when in HTTP mode.")
+      .version("1.6.0")
+      .timeConf
+      .createWithDefault(Duration.ofSeconds(1800).toMillis)
+
+  val FRONTEND_THRIFT_HTTP_PATH: ConfigEntry[String] =
+    buildConf("kyuubi.frontend.thrift.http.path")
+      .doc("Path component of URL endpoint when in HTTP mode.")
+      .version("1.6.0")
+      .stringConf
+      .createWithDefault("cliservice")
+
+  val FRONTEND_THRIFT_HTTP_COMPRESSION_ENABLED: ConfigEntry[Boolean] =
+    buildConf("kyuubi.frontend.thrift.http.compression.enabled")
+      .doc("Enable thrift http compression via Jetty compression support")
+      .version("1.6.0")
+      .booleanConf
+      .createWithDefault(true)
+
+  val FRONTEND_THRIFT_HTTP_COOKIE_AUTH_ENABLED: ConfigEntry[Boolean] =
+    buildConf("kyuubi.frontend.thrift.http.cookie.auth.enabled")
+      .doc("When true, Kyuubi in HTTP transport mode, " +
+        "will use cookie based authentication mechanism")
+      .version("1.6.0")
+      .booleanConf
+      .createWithDefault(true)
+
+  val FRONTEND_THRIFT_HTTP_COOKIE_MAX_AGE: ConfigEntry[Int] =
+    buildConf("kyuubi.frontend.thrift.http.cookie.max.age")
+      .doc("Maximum age in seconds for server side cookie used by Kyuubi in HTTP mode.")
+      .version("1.6.0")
+      .intConf
+      .createWithDefault(86400)
+
+  val FRONTEND_THRIFT_HTTP_COOKIE_DOMAIN: OptionalConfigEntry[String] =
+    buildConf("kyuubi.frontend.thrift.http.cookie.domain")
+      .doc("Domain for the Kyuubi generated cookies")
+      .version("1.6.0")
+      .stringConf
+      .createOptional
+
+  val FRONTEND_THRIFT_HTTP_COOKIE_PATH: OptionalConfigEntry[String] =
+    buildConf("kyuubi.frontend.thrift.http.cookie.path")
+      .doc("Path for the Kyuubi generated cookies")
+      .version("1.6.0")
+      .stringConf
+      .createOptional
+
+  val FRONTEND_THRIFT_HTTP_COOKIE_IS_HTTPONLY: ConfigEntry[Boolean] =
+    buildConf("kyuubi.frontend.thrift.http.cookie.is.httponly")
+      .doc("HttpOnly attribute of the Kyuubi generated cookie.")
+      .version("1.6.0")
+      .booleanConf
+      .createWithDefault(true)
+
+  val FRONTEND_THRIFT_HTTP_XSRF_FILTER_ENABLED: ConfigEntry[Boolean] =
+    buildConf("kyuubi.frontend.thrift.http.xsrf.filter.enabled")
+      .doc("If enabled, Kyuubi will block any requests made to it over http " +
+        "if an X-XSRF-HEADER header is not present")
+      .version("1.6.0")
+      .booleanConf
+      .createWithDefault(false)
+
+  val FRONTEND_THRIFT_HTTP_USE_SSL: ConfigEntry[Boolean] =
+    buildConf("kyuubi.frontend.thrift.http.use.SSL")
+      .doc("Set this to true for using SSL encryption in http mode.")
+      .version("1.6.0")
+      .booleanConf
+      .createWithDefault(false)
+
+  val FRONTEND_THRIFT_HTTP_SSL_KEYSTORE_PATH: OptionalConfigEntry[String] =
+    buildConf("kyuubi.frontend.thrift.http.ssl.keystore.path")
+      .doc("SSL certificate keystore location.")
+      .version("1.6.0")
+      .stringConf
+      .createOptional
+
+  val FRONTEND_THRIFT_HTTP_SSL_KEYSTORE_PASSWORD: OptionalConfigEntry[String] =
+    buildConf("kyuubi.frontend.thrift.http.ssl.keystore.password")
+      .doc("SSL certificate keystore password.")
+      .version("1.6.0")
+      .stringConf
+      .createOptional
+
+  val FRONTEND_THRIFT_HTTP_SSL_PROTOCOL_BLACKLIST: ConfigEntry[String] =
+    buildConf("kyuubi.frontend.thrift.http.ssl.protocol.blacklist")
+      .doc("SSL Versions to disable when using HTTP transport mode.")
+      .version("1.6.0")
+      .stringConf
+      .createWithDefault("SSLv2,SSLv3")
+
+  val FRONTEND_THRIFT_HTTP_ALLOW_USER_SUBSTITUTION: ConfigEntry[Boolean] =
+    buildConf("kyuubi.frontend.thrift.http.allow.user.substitution")
+      .doc("Allow alternate user to be specified as part of open connection" +
+        " request when using HTTP transport mode.")
+      .version("1.6.0")
+      .booleanConf
+      .createWithDefault(true)
+
   val AUTHENTICATION_METHOD: ConfigEntry[Seq[String]] = buildConf("kyuubi.authentication")
     .doc("A comma separated list of client authentication types.<ul>" +
       " <li>NOSASL: raw transport.</li>" +
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TBinaryFrontendService.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TBinaryFrontendService.scala
index 10373840a..6cf36a3c4 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TBinaryFrontendService.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TBinaryFrontendService.scala
@@ -42,6 +42,8 @@ abstract class TBinaryFrontendService(name: String)
    * @note this is final because we don't want new implementations for engine to override this.
    *       and we shall simply set it to zero for randomly picking an available port
    */
+  final override protected lazy val serverHost: Option[String] =
+    conf.get(FRONTEND_THRIFT_BINARY_BIND_HOST)
   final override protected lazy val portNum: Int = conf.get(FRONTEND_THRIFT_BINARY_BIND_PORT)
 
   private var server: Option[TServer] = None
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TFrontendService.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TFrontendService.scala
index 8ed1e4068..c9a9e1d78 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TFrontendService.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TFrontendService.scala
@@ -31,7 +31,7 @@ import org.apache.thrift.transport.TTransport
 
 import org.apache.kyuubi.{KyuubiSQLException, Logging, Utils}
 import org.apache.kyuubi.Utils.stringifyException
-import org.apache.kyuubi.config.KyuubiConf.{FRONTEND_CONNECTION_URL_USE_HOSTNAME, FRONTEND_THRIFT_BINARY_BIND_HOST}
+import org.apache.kyuubi.config.KyuubiConf.FRONTEND_CONNECTION_URL_USE_HOSTNAME
 import org.apache.kyuubi.operation.{FetchOrientation, OperationHandle}
 import org.apache.kyuubi.service.authentication.KyuubiAuthenticationFactory
 import org.apache.kyuubi.session.SessionHandle
@@ -48,12 +48,13 @@ abstract class TFrontendService(name: String)
   private val started = new AtomicBoolean(false)
   private lazy val _hadoopConf: Configuration = KyuubiHadoopUtils.newHadoopConf(conf)
   private lazy val serverThread = new NamedThreadFactory(getName, false).newThread(this)
-  private lazy val serverHost = conf.get(FRONTEND_THRIFT_BINARY_BIND_HOST)
 
+  protected def serverHost: Option[String]
   protected def portNum: Int
   protected lazy val serverAddr: InetAddress =
     serverHost.map(InetAddress.getByName).getOrElse(Utils.findLocalInetAddress)
   protected lazy val serverSocket = new ServerSocket(portNum, -1, serverAddr)
+  protected lazy val actualPort: Int = serverSocket.getLocalPort
   protected lazy val authFactory: KyuubiAuthenticationFactory =
     new KyuubiAuthenticationFactory(conf, isServer())
 
@@ -117,11 +118,11 @@ abstract class TFrontendService(name: String)
       case None =>
         serverAddr.getHostAddress
     }
-    val actualPort = serverSocket.getLocalPort
+
     host + ":" + actualPort
   }
 
-  private def getProxyUser(
+  protected def getProxyUser(
       sessionConf: java.util.Map[String, String],
       ipAddress: String,
       realUser: String): String = {
@@ -134,7 +135,7 @@ abstract class TFrontendService(name: String)
     }
   }
 
-  private def getUserName(req: TOpenSessionReq): String = {
+  protected def getUserName(req: TOpenSessionReq): String = {
     val realUser: String =
       ServiceUtils.getShortName(authFactory.getRemoteUser.getOrElse(req.getUsername))
     if (req.getConfiguration == null) {
@@ -143,6 +144,9 @@ abstract class TFrontendService(name: String)
       getProxyUser(req.getConfiguration, authFactory.getIpAddress.orNull, realUser)
     }
   }
+  protected def getIpAddress: String = {
+    authFactory.getIpAddress.orNull
+  }
 
   private def getMinVersion(versions: TProtocolVersion*): TProtocolVersion = {
     versions.minBy(_.getValue)
@@ -153,7 +157,7 @@ abstract class TFrontendService(name: String)
     val protocol = getMinVersion(SERVER_VERSION, req.getClient_protocol)
     res.setServerProtocolVersion(protocol)
     val userName = getUserName(req)
-    val ipAddress = authFactory.getIpAddress.orNull
+    val ipAddress = getIpAddress
     val configuration =
       Option(req.getConfiguration).map(_.asScala.toMap).getOrElse(Map.empty[String, String])
     val sessionHandle = be.openSession(
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/KyuubiAuthenticationFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/KyuubiAuthenticationFactory.scala
index f943def86..fa019ebde 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/KyuubiAuthenticationFactory.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/KyuubiAuthenticationFactory.scala
@@ -32,11 +32,13 @@ import org.apache.thrift.transport.{TSaslServerTransport, TTransportException, T
 import org.apache.kyuubi.{KyuubiSQLException, Logging}
 import org.apache.kyuubi.config.KyuubiConf
 import org.apache.kyuubi.config.KyuubiConf._
+import org.apache.kyuubi.service.authentication.AuthMethods.AuthMethod
 import org.apache.kyuubi.service.authentication.AuthTypes._
 
 class KyuubiAuthenticationFactory(conf: KyuubiConf, isServer: Boolean = true) extends Logging {
 
   private val authTypes = conf.get(AUTHENTICATION_METHOD).map(AuthTypes.withName)
+  private val none = authTypes.contains(NONE)
   private val noSasl = authTypes == Seq(NOSASL)
   private val kerberosEnabled = authTypes.contains(KERBEROS)
   private val plainAuthTypeOpt = authTypes.filterNot(_.equals(KERBEROS))
@@ -117,6 +119,30 @@ class KyuubiAuthenticationFactory(conf: KyuubiConf, isServer: Boolean = true) ex
     hadoopAuthServer.map(_.getRemoteAddress).map(_.getHostAddress)
       .orElse(Option(TSetIpAddressProcessor.getUserIpAddress))
   }
+
+  def isNoSaslEnabled: Boolean = {
+    noSasl
+  }
+
+  def isKerberosEnabled: Boolean = {
+    kerberosEnabled
+  }
+
+  def isPlainAuthEnabled: Boolean = {
+    plainAuthTypeOpt.isDefined
+  }
+
+  def isNoneEnabled: Boolean = {
+    none
+  }
+
+  def getValidPasswordAuthMethod: AuthMethod = {
+    debug(authTypes)
+    if (none) AuthMethods.NONE
+    else if (authTypes.contains(LDAP)) AuthMethods.LDAP
+    else if (authTypes.contains(CUSTOM)) AuthMethods.CUSTOM
+    else throw new IllegalArgumentException("No valid Password Auth detected")
+  }
 }
 object KyuubiAuthenticationFactory {
   val HS2_PROXY_USER = "hive.server2.proxy.user"
diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/config/KyuubiConfSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/config/KyuubiConfSuite.scala
index 05cf23396..215c5327e 100644
--- a/kyuubi-common/src/test/scala/org/apache/kyuubi/config/KyuubiConfSuite.scala
+++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/config/KyuubiConfSuite.scala
@@ -58,6 +58,9 @@ class KyuubiConfSuite extends KyuubiFunSuite {
     conf.set(FRONTEND_THRIFT_BINARY_BIND_HOST.key, "kentyao.org")
     assert(conf.get(FRONTEND_THRIFT_BINARY_BIND_HOST).get === "kentyao.org")
 
+    conf.set(FRONTEND_THRIFT_HTTP_BIND_HOST.key, "kentyao.org")
+    assert(conf.get(FRONTEND_THRIFT_HTTP_BIND_HOST).get === "kentyao.org")
+
     conf.setIfMissing(OPERATION_IDLE_TIMEOUT, 60L)
     assert(conf.get(OPERATION_IDLE_TIMEOUT) === 5)
 
@@ -72,6 +75,7 @@ class KyuubiConfSuite extends KyuubiFunSuite {
 
     val map = conf.getAllWithPrefix("kyuubi", "")
     assert(map(FRONTEND_THRIFT_BINARY_BIND_HOST.key.substring(7)) === "kentyao.org")
+    assert(map(FRONTEND_THRIFT_HTTP_BIND_HOST.key.substring(7)) === "kentyao.org")
     val map1 = conf.getAllWithPrefix("kyuubi", "operation")
     assert(map1(OPERATION_IDLE_TIMEOUT.key.substring(7)) === "PT0.005S")
     assert(map1.size === 1)
diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala
index ead80d98d..654d4afad 100644
--- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala
+++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala
@@ -32,6 +32,8 @@ trait SparkQueryTests extends HiveJDBCTestHelper {
 
   protected lazy val SPARK_ENGINE_MAJOR_MINOR_VERSION: (Int, Int) = sparkEngineMajorMinorVersion
 
+  protected lazy val httpMode = false;
+
   test("execute statement - select null") {
     withJdbcStatement() { statement =>
       val resultSet = statement.executeQuery("SELECT NULL AS col")
@@ -332,6 +334,8 @@ trait SparkQueryTests extends HiveJDBCTestHelper {
   }
 
   test("execute statement - select with variable substitution") {
+    assume(!httpMode)
+
     withThriftClient { client =>
       val req = new TOpenSessionReq()
       req.setUsername("chengpan")
diff --git a/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConstants.scala b/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConstants.scala
index 68116f4db..fcf7d56bc 100644
--- a/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConstants.scala
+++ b/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConstants.scala
@@ -31,11 +31,16 @@ object MetricsConstants {
   final val EXEC_POOL_ACTIVE: String = KYUUBI + "exec.pool.threads.active"
 
   final private val CONN = KYUUBI + "connection."
+  final private val THRIFT_HTTP_CONN = KYUUBI + "thrift.http.connection."
 
   final val CONN_OPEN: String = CONN + "opened"
   final val CONN_FAIL: String = CONN + "failed"
   final val CONN_TOTAL: String = CONN + "total"
 
+  final val THRIFT_HTTP_CONN_OPEN: String = THRIFT_HTTP_CONN + "opened"
+  final val THRIFT_HTTP_CONN_FAIL: String = THRIFT_HTTP_CONN + "failed"
+  final val THRIFT_HTTP_CONN_TOTAL: String = THRIFT_HTTP_CONN + "total"
+
   final private val ENGINE = KYUUBI + "engine."
   final val ENGINE_FAIL: String = ENGINE + "failed"
   final val ENGINE_TIMEOUT: String = ENGINE + "timeout"
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
index 59f1fa26b..1ccae0370 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
@@ -117,6 +117,7 @@ class KyuubiServer(name: String) extends Serverable(name) {
   override lazy val frontendServices: Seq[AbstractFrontendService] =
     conf.get(FRONTEND_PROTOCOLS).map(FrontendProtocols.withName).map {
       case THRIFT_BINARY => new KyuubiTBinaryFrontendService(this)
+      case THRIFT_HTTP => new KyuubiTHttpFrontendService(this)
       case REST =>
         warn("REST frontend protocol is experimental, API may change in the future.")
         new KyuubiRestFrontendService(this)
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTHttpFrontendService.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTHttpFrontendService.scala
new file mode 100644
index 000000000..565589010
--- /dev/null
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTHttpFrontendService.scala
@@ -0,0 +1,283 @@
+/*
+ * 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.kyuubi.server
+
+import java.net.ServerSocket
+import java.util.concurrent.{SynchronousQueue, ThreadPoolExecutor, TimeUnit}
+import javax.security.sasl.AuthenticationException
+import javax.servlet.{ServletContextEvent, ServletContextListener}
+
+import org.apache.commons.lang3.SystemUtils
+import org.apache.hive.service.rpc.thrift.{TCLIService, TOpenSessionReq}
+import org.apache.thrift.protocol.TBinaryProtocol
+import org.eclipse.jetty.http.HttpMethod
+import org.eclipse.jetty.security.{ConstraintMapping, ConstraintSecurityHandler}
+import org.eclipse.jetty.server._
+import org.eclipse.jetty.server.handler.gzip.GzipHandler
+import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
+import org.eclipse.jetty.util.security.Constraint
+import org.eclipse.jetty.util.ssl.SslContextFactory
+import org.eclipse.jetty.util.thread.ExecutorThreadPool
+
+import org.apache.kyuubi.KyuubiException
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.config.KyuubiConf._
+import org.apache.kyuubi.metrics.MetricsConstants.{THRIFT_HTTP_CONN_FAIL, THRIFT_HTTP_CONN_OPEN, THRIFT_HTTP_CONN_TOTAL}
+import org.apache.kyuubi.metrics.MetricsSystem
+import org.apache.kyuubi.server.http.ThriftHttpServlet
+import org.apache.kyuubi.server.http.util.SessionManager
+import org.apache.kyuubi.service.{Serverable, Service, ServiceUtils, TFrontendService}
+import org.apache.kyuubi.util.NamedThreadFactory
+
+/**
+ * Apache Thrift based hive service rpc
+ *  1. the server side implementation serves client-server rpc calls
+ *  2. the engine side implementations serve server-engine rpc calls
+ */
+final class KyuubiTHttpFrontendService(
+    override val serverable: Serverable)
+  extends TFrontendService("KyuubiTHttpFrontendService") {
+
+  override protected lazy val serverHost: Option[String] =
+    conf.get(FRONTEND_THRIFT_HTTP_BIND_HOST)
+  override protected lazy val portNum: Int = conf.get(FRONTEND_THRIFT_HTTP_BIND_PORT)
+  override protected lazy val actualPort: Int = portNum
+  override protected lazy val serverSocket: ServerSocket = null
+
+  private var server: Option[Server] = None
+  private val APPLICATION_THRIFT = "application/x-thrift"
+
+  /**
+   * Configure Jetty to serve http requests. Example of a client connection URL:
+   * http://localhost:10000/servlets/thrifths2/ A gateway may cause actual target
+   * URL to differ, e.g. http://gateway:port/hive2/servlets/thrifths2/.
+   *
+   * @param conf the configuration of the service
+   */
+  override def initialize(conf: KyuubiConf): Unit = synchronized {
+    this.conf = conf
+    if (authFactory.isKerberosEnabled) {
+      try {
+        authFactory.getValidPasswordAuthMethod
+      } catch {
+        case _: IllegalArgumentException =>
+          throw new AuthenticationException("Kerberos is not supported for thrift http mode")
+      }
+    }
+
+    try {
+      // Server thread pool
+      // Start with minWorkerThreads, expand till maxWorkerThreads and reject
+      // subsequent requests
+      val minThreads = conf.get(FRONTEND_THRIFT_MIN_WORKER_THREADS)
+      val maxThreads = conf.get(FRONTEND_THRIFT_MAX_WORKER_THREADS)
+      val keepAliveTime = conf.get(FRONTEND_THRIFT_WORKER_KEEPALIVE_TIME)
+      val executor = new ThreadPoolExecutor(
+        minThreads,
+        maxThreads,
+        keepAliveTime,
+        TimeUnit.MILLISECONDS,
+        new SynchronousQueue[Runnable](),
+        new NamedThreadFactory(getName + "HttpHandler-Pool", false))
+      val threadPool = new ExecutorThreadPool(executor)
+
+      // HTTP Server
+      server = Some(new Server(threadPool))
+
+      val httpConf = new HttpConfiguration
+      // Configure header size
+      val requestHeaderSize = conf.get(FRONTEND_THRIFT_HTTP_REQUEST_HEADER_SIZE)
+      val responseHeaderSize = conf.get(FRONTEND_THRIFT_HTTP_RESPONSE_HEADER_SIZE)
+      httpConf.setRequestHeaderSize(requestHeaderSize)
+      httpConf.setResponseHeaderSize(responseHeaderSize)
+      val connectionFactory = new HttpConnectionFactory(httpConf)
+
+      val useSsl = conf.get(FRONTEND_THRIFT_HTTP_USE_SSL)
+      val schemeName = if (useSsl) "https" else "http"
+
+      // Change connector if SSL is used
+      val connector =
+        if (useSsl) {
+          val keyStorePath = conf.get(FRONTEND_THRIFT_HTTP_SSL_KEYSTORE_PATH)
+
+          if (keyStorePath.isEmpty) {
+            throw new IllegalArgumentException(FRONTEND_THRIFT_HTTP_SSL_KEYSTORE_PATH.key +
+              " Not configured for SSL connection, please set the key with: " +
+              FRONTEND_THRIFT_HTTP_SSL_KEYSTORE_PATH.doc)
+          }
+
+          val keyStorePassword = conf.get(FRONTEND_THRIFT_HTTP_SSL_KEYSTORE_PASSWORD)
+          if (keyStorePassword.isEmpty) {
+            throw new IllegalArgumentException(FRONTEND_THRIFT_HTTP_SSL_KEYSTORE_PASSWORD.key +
+              " Not configured for SSL connection. please set the key with: " +
+              FRONTEND_THRIFT_HTTP_SSL_KEYSTORE_PASSWORD.doc)
+          }
+
+          val sslContextFactory = new SslContextFactory.Server
+          val excludedProtocols = conf.get(FRONTEND_THRIFT_HTTP_SSL_PROTOCOL_BLACKLIST).split(",")
+          info("Thrift HTTP Server SSL: adding excluded protocols: " +
+            String.join(",", excludedProtocols: _*))
+          sslContextFactory.addExcludeProtocols(excludedProtocols: _*)
+          info("Thrift HTTP Server SSL: SslContextFactory.getExcludeProtocols = " +
+            String.join(",", sslContextFactory.getExcludeProtocols: _*))
+          sslContextFactory.setKeyStorePath(keyStorePath.get)
+          sslContextFactory.setKeyStorePassword(keyStorePassword.get)
+          new ServerConnector(
+            server.get,
+            sslContextFactory,
+            connectionFactory)
+        } else {
+          new ServerConnector(server.get, connectionFactory)
+        }
+
+      connector.setPort(portNum)
+      // Linux:yes, Windows:no
+      // result of setting the SO_REUSEADDR flag is different on Windows
+      // http://msdn.microsoft.com/en-us/library/ms740621(v=vs.85).aspx
+      // without this 2 NN's can start on the same machine and listen on
+      // the same port with indeterminate routing of incoming requests to them
+      connector.setReuseAddress(!SystemUtils.IS_OS_WINDOWS)
+      val maxIdleTime = conf.get(FRONTEND_THRIFT_HTTP_MAX_IDLE_TIME)
+      connector.setIdleTimeout(maxIdleTime)
+      connector.setAcceptQueueSize(maxThreads)
+      server.foreach(_.addConnector(connector))
+
+      val processor = new TCLIService.Processor[TCLIService.Iface](this)
+      val protocolFactory = new TBinaryProtocol.Factory
+      val servlet = new ThriftHttpServlet(processor, protocolFactory, authFactory, conf)
+      servlet.init()
+
+      // Context handler
+      val context = new ServletContextHandler(ServletContextHandler.SESSIONS)
+      context.setContextPath("/")
+
+      context.addEventListener(new ServletContextListener() {
+        override def contextInitialized(servletContextEvent: ServletContextEvent): Unit = {
+          MetricsSystem.tracing { ms =>
+            ms.incCount(THRIFT_HTTP_CONN_TOTAL)
+            ms.incCount(THRIFT_HTTP_CONN_OPEN)
+          }
+        }
+
+        override def contextDestroyed(servletContextEvent: ServletContextEvent): Unit = {
+          MetricsSystem.tracing { ms =>
+            ms.decCount(THRIFT_HTTP_CONN_OPEN)
+          }
+        }
+      })
+
+      val httpPath = getHttpPath(conf.get(FRONTEND_THRIFT_HTTP_PATH))
+
+      if (conf.get(FRONTEND_THRIFT_HTTP_COMPRESSION_ENABLED)) {
+        val gzipHandler = new GzipHandler
+        gzipHandler.setHandler(context)
+        gzipHandler.addIncludedMethods(HttpMethod.POST.asString())
+        gzipHandler.addIncludedMimeTypes(APPLICATION_THRIFT)
+        server.foreach(_.setHandler(gzipHandler))
+      } else {
+        server.foreach(_.setHandler(context))
+      }
+
+      context.addServlet(new ServletHolder(servlet), httpPath)
+      constrainHttpMethods(context)
+
+      info(s"Started ${getClass.getSimpleName} in $schemeName mode on port $portNum " +
+        s"path=$httpPath with $minThreads ... $maxThreads threads")
+    } catch {
+      case e: Throwable =>
+        MetricsSystem.tracing(_.incCount(THRIFT_HTTP_CONN_FAIL))
+        error(e)
+        throw new KyuubiException(
+          s"Failed to initialize frontend service on $serverAddr:$portNum.",
+          e)
+    }
+    super.initialize(conf)
+  }
+
+  override def run(): Unit =
+    try {
+      if (isServer()) {
+        info(s"Starting and exposing JDBC connection at: jdbc:hive2://$connectionUrl/")
+      }
+      server.foreach(_.start())
+    } catch {
+      case _: InterruptedException => error(s"$getName is interrupted")
+      case t: Throwable =>
+        error(s"Error starting $getName", t)
+        System.exit(-1)
+    }
+
+  override protected def stopServer(): Unit = {
+    server.foreach(_.stop())
+    server = None
+  }
+
+  override protected def isServer(): Boolean = true
+
+  override val discoveryService: Option[Service] = None
+
+  private def getHttpPath(httpPath: String): String = {
+    if (httpPath == null || httpPath == "") return "/*"
+    else {
+      if (!httpPath.startsWith("/")) return "/" + httpPath
+      if (httpPath.endsWith("/")) return httpPath + "*"
+      if (!httpPath.endsWith("/*")) return httpPath + "/*"
+    }
+    httpPath
+  }
+
+  def constrainHttpMethods(ctxHandler: ServletContextHandler): Unit = {
+    val constraint = new Constraint
+    constraint.setAuthenticate(true)
+    val cmt = new ConstraintMapping
+    cmt.setConstraint(constraint)
+    cmt.setMethod("TRACE")
+    cmt.setPathSpec("/*")
+    val securityHandler = new ConstraintSecurityHandler
+    val cmo = new ConstraintMapping
+    cmo.setConstraint(constraint)
+    cmo.setMethod("OPTIONS")
+    cmo.setPathSpec("/*")
+    securityHandler.setConstraintMappings(Array[ConstraintMapping](cmt, cmo))
+    ctxHandler.setSecurityHandler(securityHandler)
+  }
+
+  override protected def getIpAddress: String = {
+    SessionManager.getIpAddress
+  }
+
+  override protected def getUserName(req: TOpenSessionReq): String = {
+    var userName: String = SessionManager.getUserName
+    if (userName == null) userName = req.getUsername
+    userName = getShortName(userName)
+    val effectiveClientUser: String = getProxyUser(req.getConfiguration, getIpAddress, userName)
+    debug("Client's username: " + effectiveClientUser)
+    effectiveClientUser
+  }
+
+  private def getShortName(userName: String): String = {
+    var ret: String = null
+
+    if (userName != null) {
+      val indexOfDomainMatch = ServiceUtils.indexOfDomainMatch(userName)
+      ret = if (indexOfDomainMatch <= 0) userName else userName.substring(0, indexOfDomainMatch)
+    }
+
+    ret
+  }
+}
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/ThriftHttpServlet.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/ThriftHttpServlet.scala
new file mode 100644
index 000000000..fc9fe563f
--- /dev/null
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/ThriftHttpServlet.scala
@@ -0,0 +1,390 @@
+/*
+ * 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.kyuubi.server.http
+
+import java.io.IOException
+import java.security.SecureRandom
+import javax.security.sasl.AuthenticationException
+import javax.servlet.ServletException
+import javax.servlet.http.Cookie
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+import javax.ws.rs.core.NewCookie
+
+import scala.collection.mutable
+
+import org.apache.commons.codec.binary.Base64
+import org.apache.commons.codec.binary.StringUtils
+import org.apache.hadoop.hive.shims.Utils
+import org.apache.thrift.TProcessor
+import org.apache.thrift.protocol.TProtocolFactory
+import org.apache.thrift.server.TServlet
+
+import org.apache.kyuubi.Logging
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.server.http.util.{CookieSigner, HttpAuthUtils, SessionManager}
+import org.apache.kyuubi.service.authentication.{AuthenticationProviderFactory, KyuubiAuthenticationFactory}
+
+class ThriftHttpServlet(
+    processor: TProcessor,
+    protocolFactory: TProtocolFactory,
+    authFactory: KyuubiAuthenticationFactory,
+    conf: KyuubiConf)
+  extends TServlet(processor, protocolFactory) with Logging {
+  // Class members for cookie based authentication.
+  private var signer: CookieSigner = _
+  val AUTH_COOKIE = "hive.server2.auth"
+  private val RAN = new SecureRandom()
+  private var isCookieAuthEnabled: Boolean = false
+  private var cookieDomain: String = _
+  private var cookiePath: String = _
+  private var cookieMaxAge: Int = 0
+  private var isCookieSecure = false
+  private var isHttpOnlyCookie = false
+  private val X_FORWARDED_FOR_HEADER = "X-Forwarded-For"
+
+  override def init(): Unit = {
+    isCookieAuthEnabled = conf.get(KyuubiConf.FRONTEND_THRIFT_HTTP_COOKIE_AUTH_ENABLED)
+    // Initialize the cookie based authentication related variables.
+    if (isCookieAuthEnabled) { // Generate the signer with secret.
+      val secret = RAN.nextLong().toString
+      debug("Using the random number as the secret for cookie generation " + secret)
+      signer = new CookieSigner(secret.getBytes)
+      cookieMaxAge = conf.get(KyuubiConf.FRONTEND_THRIFT_HTTP_COOKIE_MAX_AGE)
+      cookieDomain = conf.get(KyuubiConf.FRONTEND_THRIFT_HTTP_COOKIE_DOMAIN).orNull
+      cookiePath = conf.get(KyuubiConf.FRONTEND_THRIFT_HTTP_COOKIE_PATH).orNull
+      // always send secure cookies for SSL mode
+      isCookieSecure = conf.get(KyuubiConf.FRONTEND_THRIFT_HTTP_USE_SSL)
+      isHttpOnlyCookie = conf.get(KyuubiConf.FRONTEND_THRIFT_HTTP_COOKIE_IS_HTTPONLY)
+    }
+  }
+
+  @throws[ServletException]
+  @throws[IOException]
+  override def doPost(request: HttpServletRequest, response: HttpServletResponse): Unit = {
+    var clientUserName: String = null
+    var clientIpAddress: String = null
+    var requireNewCookie: Boolean = false
+    try {
+      if (conf.get(KyuubiConf.FRONTEND_THRIFT_HTTP_XSRF_FILTER_ENABLED)) {
+        val continueProcessing = Utils.doXsrfFilter(request, response, null, null)
+        if (!continueProcessing) {
+          warn("Request did not have valid XSRF header, rejecting.")
+          return
+        }
+      }
+
+      // If the cookie based authentication is already enabled, parse the
+      // request and validate the request cookies.
+      if (isCookieAuthEnabled) {
+        debug("Cookie Auth Enabled")
+        clientUserName = validateCookie(request)
+        requireNewCookie = clientUserName == null
+        if (requireNewCookie) {
+          info("Could not validate cookie sent, will try to generate a new cookie")
+        } else {
+          debug("Got userName From Cookie: " + clientUserName)
+        }
+      }
+
+      // If the cookie based authentication is not enabled or the request does not have a valid
+      // cookie, use authentication depending on the server setup.
+      if (clientUserName == null) {
+        clientUserName = doPasswdAuth(request, authFactory)
+      }
+
+      assert(clientUserName != null)
+      debug("Client username: " + clientUserName)
+
+      // Set the thread local username to be used for doAs if true
+      SessionManager.setUserName(clientUserName)
+
+      // find proxy user if any from query param
+      val doAsQueryParam = getDoAsQueryParam(request.getQueryString)
+      if (doAsQueryParam != null) SessionManager.setProxyUserName(doAsQueryParam)
+
+      clientIpAddress = request.getRemoteAddr
+      debug("Client IP Address: " + clientIpAddress)
+      // Set the thread local ip address
+      SessionManager.setIpAddress(clientIpAddress)
+
+      val forwarded_for = request.getHeader(X_FORWARDED_FOR_HEADER)
+      if (forwarded_for != null) {
+        debug(X_FORWARDED_FOR_HEADER + ":" + forwarded_for)
+        val forwardedAddresses = forwarded_for.split(",").toList
+        SessionManager.setForwardedAddresses(forwardedAddresses)
+      } else SessionManager.setForwardedAddresses(List.empty[String])
+
+      // Generate new cookie and add it to the response
+      if (requireNewCookie && !authFactory.isNoSaslEnabled) {
+        val cookieToken = HttpAuthUtils.createCookieToken(clientUserName)
+        val hs2Cookie = createCookie(signer.signCookie(cookieToken))
+        if (isHttpOnlyCookie) response.setHeader("SET-COOKIE", getHttpOnlyCookieHeader(hs2Cookie))
+        else response.addCookie(hs2Cookie)
+        info("Cookie added for clientUserName " + clientUserName)
+      }
+      super.doPost(request, response)
+    } catch {
+      case e: AuthenticationException =>
+        error("Error: ", e)
+        // Send a 401 to the client
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
+
+        // scalastyle:off println
+        response.getWriter.println("Authentication Error: " + e.getMessage)
+      // scalastyle:on println
+      case e: Throwable =>
+        error("Error: ", e)
+        throw e
+    } finally {
+      // Clear the thread locals
+      SessionManager.clearUserName()
+      SessionManager.clearIpAddress()
+      SessionManager.clearProxyUserName()
+      SessionManager.clearForwardedAddresses()
+    }
+  }
+
+  /**
+   * Retrieves the client name from cookieString. If the cookie does not
+   * correspond to a valid client, the function returns null.
+   *
+   * @param cookies HTTP Request cookies.
+   * @return Client Username if cookieString has a HS2 Generated cookie that is currently valid.
+   *         Else, returns null.
+   */
+  private def getClientNameFromCookie(cookies: Array[Cookie]): String = {
+    // Current Cookie Name, Current Cookie Value
+    var currName: String = null
+    var currValue: String = null
+    // Following is the main loop which iterates through all the cookies send by the client.
+    // The HS2 generated cookies are of the format hive.server2.auth=<value>
+    // A cookie which is identified as a hiveserver2 generated cookie is validated
+    // by calling signer.verifyAndExtract(). If the validation passes, send the
+    // username for which the cookie is validated to the caller. If no client side
+    // cookie passes the validation, return null to the caller.
+    for (currCookie <- cookies) { // Get the cookie name
+      currName = currCookie.getName
+      if (currName == AUTH_COOKIE) {
+        // If we reached here, we have match for HS2 generated cookie
+        currValue = currCookie.getValue
+        // Validate the value.
+        try currValue = signer.verifyAndExtract(currValue)
+        catch {
+          case e: IllegalArgumentException =>
+            debug("Invalid cookie" + e.getMessage)
+            currValue = null
+        }
+        // Retrieve the user name, do the final validation step.
+        if (currValue != null) {
+          val userName = HttpAuthUtils.getUserNameFromCookieToken(currValue)
+          if (userName == null) {
+            warn("Invalid cookie token " + currValue)
+          } else {
+            // We have found a valid cookie in the client request.
+            debug("Validated the cookie for user " + userName)
+            return userName
+          }
+        }
+      }
+    }
+    // No valid HS2 generated cookies found, return null
+    null
+  }
+
+  /**
+   * Convert cookie array to human readable cookie string
+   *
+   * @param cookies Cookie Array
+   * @return String containing all the cookies separated by a newline character.
+   *         Each cookie is of the format [key]=[value]
+   */
+  private def toCookieStr(cookies: Array[Cookie]) = {
+    val cookieStr = new mutable.StringBuilder
+    for (c <- cookies) {
+      cookieStr.append(c.getName).append('=').append(c.getValue).append(" ;\n")
+    }
+    cookieStr.toString
+  }
+
+  /**
+   * Validate the request cookie. This function iterates over the request cookie headers
+   * and finds a cookie that represents a valid client/server session. If it finds one, it
+   * returns the client name associated with the session. Else, it returns null.
+   *
+   * @param request The HTTP Servlet Request send by the client
+   * @return Client Username if the request has valid HS2 cookie, else returns null
+   */
+  private def validateCookie(request: HttpServletRequest): String = {
+    // Find all the valid cookies associated with the request.
+    val cookies = request.getCookies
+    if (cookies == null) {
+      debug("No valid cookies associated with the request " + request)
+      return null
+    }
+    debug("Received cookies: " + toCookieStr(cookies))
+    getClientNameFromCookie(cookies)
+  }
+
+  import java.io.UnsupportedEncodingException
+
+  /**
+   * Generate a server side cookie given the cookie value as the input.
+   *
+   * @param str Input string token.
+   * @return The generated cookie.
+   * @throws UnsupportedEncodingException
+   */
+  @throws[UnsupportedEncodingException]
+  private def createCookie(str: String): Cookie = {
+    debug("Cookie name = " + AUTH_COOKIE + " value = " + str)
+    val cookie = new Cookie(AUTH_COOKIE, str)
+    cookie.setMaxAge(cookieMaxAge)
+    if (cookieDomain != null) cookie.setDomain(cookieDomain)
+    if (cookiePath != null) cookie.setPath(cookiePath)
+    cookie.setSecure(isCookieSecure)
+    cookie
+  }
+
+  /**
+   * Generate httponly cookie from HS2 cookie
+   *
+   * @param cookie HS2 generated cookie
+   * @return The httponly cookie
+   */
+  private def getHttpOnlyCookieHeader(cookie: Cookie): String = {
+    val newCookie = new NewCookie(
+      cookie.getName,
+      cookie.getValue,
+      cookie.getPath,
+      cookie.getDomain,
+      cookie.getVersion,
+      cookie.getComment,
+      cookie.getMaxAge,
+      cookie.getSecure)
+    newCookie + "; HttpOnly"
+  }
+
+  /**
+   * Do the LDAP/PAM authentication
+   *
+   * @param request
+   * @param authFactory
+   * @throws AuthenticationException
+   */
+  @throws[AuthenticationException]
+  private def doPasswdAuth(
+      request: HttpServletRequest,
+      authFactory: KyuubiAuthenticationFactory): String = {
+    val userName = getUsername(request, authFactory: KyuubiAuthenticationFactory)
+    debug("Is No SASL Enabled : " + authFactory.isNoSaslEnabled)
+    // No-op when authType is NOSASL
+    if (authFactory.isNoSaslEnabled) return userName
+    try {
+      debug("Initiating Password Authentication")
+      val password = getPassword(request, authFactory)
+      val authMethod = authFactory.getValidPasswordAuthMethod
+      debug("Password Method: " + authMethod)
+      val provider = AuthenticationProviderFactory.getAuthenticationProvider(authMethod, conf)
+      debug("Password Provider obtained")
+      provider.authenticate(userName, password)
+      debug("Password Provider authenticated username successfully")
+    } catch {
+      case e: Exception =>
+        throw new AuthenticationException(e.getMessage, e)
+    }
+    userName
+  }
+
+  @throws[AuthenticationException]
+  private def getUsername(
+      request: HttpServletRequest,
+      authFactory: KyuubiAuthenticationFactory): String = {
+    val creds = getAuthHeaderTokens(request, authFactory)
+    // Username must be present
+    if (creds(0) == null || creds(0).isEmpty) {
+      throw new AuthenticationException("Authorization header received " +
+        "from the client does not contain username.")
+    }
+    creds(0)
+  }
+
+  @throws[AuthenticationException]
+  private def getPassword(
+      request: HttpServletRequest,
+      authFactory: KyuubiAuthenticationFactory): String = {
+    val creds = getAuthHeaderTokens(request, authFactory)
+    // Password must be present
+    if (creds(1) == null || creds(1).isEmpty) {
+      throw new AuthenticationException("Authorization header received " +
+        "from the client does not contain username.")
+    }
+    creds(1)
+  }
+
+  @throws[AuthenticationException]
+  private def getAuthHeaderTokens(
+      request: HttpServletRequest,
+      authFactory: KyuubiAuthenticationFactory): Array[String] = {
+    val authHeaderBase64 = getAuthHeader(request, authFactory)
+    val authHeaderString = StringUtils.newStringUtf8(Base64.decodeBase64(authHeaderBase64.getBytes))
+    authHeaderString.split(":")
+  }
+
+  /**
+   * Returns the base64 encoded auth header payload
+   *
+   * @param request
+   * @param authFactory
+   * @return
+   * @throws AuthenticationException
+   */
+  @throws[AuthenticationException]
+  private def getAuthHeader(
+      request: HttpServletRequest,
+      authFactory: KyuubiAuthenticationFactory): String = {
+    val authHeader = request.getHeader(HttpAuthUtils.AUTHORIZATION)
+    // Each http request must have an Authorization header
+    if (authHeader == null || authHeader.isEmpty) {
+      throw new AuthenticationException("Authorization header received " +
+        "from the client is empty.")
+    }
+
+    var authHeaderBase64String: String = null
+    val beginIndex = (HttpAuthUtils.BASIC + " ").length
+    authHeaderBase64String = authHeader.substring(beginIndex)
+    // Authorization header must have a payload
+    if (authHeaderBase64String == null || authHeaderBase64String.isEmpty) {
+      throw new AuthenticationException("Authorization header received " +
+        "from the client does not contain any data.")
+    }
+    authHeaderBase64String
+  }
+
+  private def getDoAsQueryParam(queryString: String): String = {
+    debug("URL query string:" + queryString)
+    if (queryString == null) return null
+    val params = javax.servlet.http.HttpUtils.parseQueryString(queryString)
+    val keySet = params.keySet
+    keySet.forEach(key => {
+      if (key.equalsIgnoreCase("doAs")) return params.get(key)(0)
+    })
+
+    null
+  }
+}
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/CookieSigner.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/CookieSigner.scala
new file mode 100644
index 000000000..0c4bca1a7
--- /dev/null
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/CookieSigner.scala
@@ -0,0 +1,89 @@
+/*
+ * 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.kyuubi.server.http.util
+
+import java.security.{MessageDigest, NoSuchAlgorithmException}
+
+import org.apache.commons.codec.binary.Base64
+
+import org.apache.kyuubi.Logging
+
+object CookieSigner {
+  private val SIGNATURE = "&s="
+  private val SHA_STRING = "SHA-256"
+}
+
+class CookieSigner(secret: Array[Byte]) extends Logging {
+  private val secretBytes = secret.clone()
+
+  /**
+   * Sign the cookie given the string token as input.
+   *
+   * @param str Input token
+   * @return Signed token that can be used to create a cookie
+   */
+  def signCookie(str: String): String = {
+    if (str == null || str.isEmpty) {
+      throw new IllegalArgumentException("NULL or empty string to sign")
+    }
+    val signature = getSignature(str)
+    debug("Signature generated for " + str + " is " + signature)
+    str + CookieSigner.SIGNATURE + signature
+  }
+
+  /**
+   * Verify a signed string and extracts the original string.
+   *
+   * @param signedStr The already signed string
+   * @return Raw Value of the string without the signature
+   */
+  def verifyAndExtract(signedStr: String): String = {
+    val index = signedStr.lastIndexOf(CookieSigner.SIGNATURE)
+    if (index == -1) throw new IllegalArgumentException("Invalid input sign: " + signedStr)
+    val originalSignature = signedStr.substring(index + CookieSigner.SIGNATURE.length)
+    val rawValue = signedStr.substring(0, index)
+    val currentSignature = getSignature(rawValue)
+    debug("Signature generated for " + rawValue + " inside verify is " + currentSignature)
+    if (!MessageDigest.isEqual(originalSignature.getBytes, currentSignature.getBytes)) {
+      throw new IllegalArgumentException("Invalid sign, original = " + originalSignature +
+        " current = " + currentSignature)
+    }
+    rawValue
+  }
+
+  /**
+   * Get the signature of the input string based on SHA digest algorithm.
+   *
+   * @param str Input token
+   * @return Signed String
+   */
+  private def getSignature(str: String) =
+    try {
+      val md = MessageDigest.getInstance(CookieSigner.SHA_STRING)
+      md.update(str.getBytes)
+      md.update(secretBytes)
+      val digest = md.digest
+      new Base64(0).encodeToString(digest)
+    } catch {
+      case ex: NoSuchAlgorithmException =>
+        throw new RuntimeException(
+          "Invalid SHA digest String: " +
+            CookieSigner.SHA_STRING + " " + ex.getMessage,
+          ex)
+    }
+}
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/HttpAuthUtils.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/HttpAuthUtils.scala
new file mode 100644
index 000000000..7bb117476
--- /dev/null
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/HttpAuthUtils.scala
@@ -0,0 +1,97 @@
+/*
+ * 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.kyuubi.server.http.util
+
+import java.security.SecureRandom
+import java.util
+import java.util.StringTokenizer
+
+import scala.collection.mutable
+
+import org.apache.kyuubi.Logging
+
+object HttpAuthUtils extends Logging {
+  val WWW_AUTHENTICATE = "WWW-Authenticate"
+  val AUTHORIZATION = "Authorization"
+  val BASIC = "Basic"
+  val NEGOTIATE = "Negotiate"
+
+  private val COOKIE_ATTR_SEPARATOR = "&"
+  private val COOKIE_CLIENT_USER_NAME = "cu"
+  private val COOKIE_CLIENT_RAND_NUMBER = "rn"
+  private val COOKIE_KEY_VALUE_SEPARATOR = "="
+  private val COOKIE_ATTRIBUTES = new util.HashSet[String](
+    util.Arrays.asList(COOKIE_CLIENT_USER_NAME, COOKIE_CLIENT_RAND_NUMBER))
+
+  /**
+   * Creates and returns a HS2 cookie token.
+   *
+   * @param clientUserName Client User name.
+   * @return An unsigned cookie token generated from input parameters.
+   *         The final cookie generated is of the following format :
+   *         cu=<username>&rn=<randomNumber>&s=<cookieSignature>
+   */
+  def createCookieToken(clientUserName: String): String = {
+    val sb = new mutable.StringBuilder
+    sb.append(COOKIE_CLIENT_USER_NAME).append(COOKIE_KEY_VALUE_SEPARATOR)
+      .append(clientUserName).append(COOKIE_ATTR_SEPARATOR).append(COOKIE_CLIENT_RAND_NUMBER)
+      .append(COOKIE_KEY_VALUE_SEPARATOR).append((new SecureRandom).nextLong)
+    sb.toString
+  }
+
+  /**
+   * Parses a cookie token to retrieve client user name.
+   *
+   * @param tokenStr Token String.
+   * @return A valid user name if input is of valid format, else returns null.
+   */
+  def getUserNameFromCookieToken(tokenStr: String): String = {
+    val map = splitCookieToken(tokenStr)
+    if (!(map.keySet == COOKIE_ATTRIBUTES)) {
+      error("Invalid token with missing attributes " + tokenStr)
+      return null
+    }
+    map.get(COOKIE_CLIENT_USER_NAME)
+  }
+
+  /**
+   * Splits the cookie token into attributes pairs.
+   *
+   * @param tokenStr input token.
+   * @return a map with the attribute pairs of the token if the input is valid.
+   *         Else, returns null.
+   */
+  private def splitCookieToken(tokenStr: String): util.Map[String, String] = {
+    val map = new util.HashMap[String, String]
+    val st = new StringTokenizer(tokenStr, COOKIE_ATTR_SEPARATOR)
+    while ({
+      st.hasMoreTokens
+    }) {
+      val part = st.nextToken
+      val separator = part.indexOf(COOKIE_KEY_VALUE_SEPARATOR)
+      if (separator == -1) {
+        error("Invalid token string " + tokenStr)
+        return null
+      }
+      val key = part.substring(0, separator)
+      val value = part.substring(separator + 1)
+      map.put(key, value)
+    }
+    map
+  }
+}
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/SessionManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/SessionManager.scala
new file mode 100644
index 000000000..f5c44411c
--- /dev/null
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/util/SessionManager.scala
@@ -0,0 +1,89 @@
+/*
+ * 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.kyuubi.server.http.util
+
+import org.apache.kyuubi.Logging
+
+object SessionManager extends Logging {
+
+  private val threadLocalIpAddress: ThreadLocal[String] = new ThreadLocal[String]
+
+  def setIpAddress(ipAddress: String): Unit = {
+    threadLocalIpAddress.set(ipAddress)
+  }
+
+  def clearIpAddress(): Unit = {
+    threadLocalIpAddress.remove()
+  }
+
+  def getIpAddress: String = {
+    threadLocalIpAddress.get
+  }
+
+  private val threadLocalForwardedAddresses: ThreadLocal[List[String]] =
+    new ThreadLocal[List[String]]
+
+  def setForwardedAddresses(ipAddress: List[String]): Unit = {
+    threadLocalForwardedAddresses.set(ipAddress)
+  }
+
+  def clearForwardedAddresses(): Unit = {
+    threadLocalForwardedAddresses.remove()
+  }
+
+  def getForwardedAddresses: List[String] = {
+    threadLocalForwardedAddresses.get
+  }
+
+  private val threadLocalUserName: ThreadLocal[String] = new ThreadLocal[String]() {
+    override protected def initialValue: String = {
+      null
+    }
+  }
+
+  def setUserName(userName: String): Unit = {
+    threadLocalUserName.set(userName)
+  }
+
+  def clearUserName(): Unit = {
+    threadLocalUserName.remove()
+  }
+
+  def getUserName: String = {
+    threadLocalUserName.get
+  }
+
+  private val threadLocalProxyUserName: ThreadLocal[String] = new ThreadLocal[String]() {
+    override protected def initialValue: String = {
+      null
+    }
+  }
+
+  def setProxyUserName(userName: String): Unit = {
+    debug("setting proxy user name based on query param to: " + userName)
+    threadLocalProxyUserName.set(userName)
+  }
+
+  def getProxyUserName: String = {
+    threadLocalProxyUserName.get
+  }
+
+  def clearProxyUserName(): Unit = {
+    threadLocalProxyUserName.remove()
+  }
+}
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala
new file mode 100644
index 000000000..9d189b565
--- /dev/null
+++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala
@@ -0,0 +1,42 @@
+/*
+ * 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.kyuubi.operation.thrift.http
+
+import org.scalactic.source.Position
+import org.scalatest.Tag
+
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.config.KyuubiConf.FrontendProtocols
+import org.apache.kyuubi.operation.KyuubiOperationKerberosAndPlainAuthSuite
+
+class KyuubiOperationThriftHttpKerberosAndPlainAuthSuite
+  extends KyuubiOperationKerberosAndPlainAuthSuite {
+  override protected val frontendProtocols: Seq[KyuubiConf.FrontendProtocols.Value] =
+    FrontendProtocols.THRIFT_HTTP :: Nil
+
+  override protected def getJdbcUrl: String =
+    s"jdbc:hive2://${server.frontendServices.head.connectionUrl}/default;transportMode=http;" +
+      s"httpPath=cliservice"
+
+  override protected def test(testName: String, testTags: Tag*)(testFun: => Any)(implicit
+      pos: Position): Unit = {
+    if (!testName.equals("test with KERBEROS authentication")) {
+      super.test(testName, testTags: _*)(testFun)(pos)
+    }
+  }
+}
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpPerUserSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpPerUserSuite.scala
new file mode 100644
index 000000000..8a09d8c43
--- /dev/null
+++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpPerUserSuite.scala
@@ -0,0 +1,33 @@
+/*
+ * 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.kyuubi.operation.thrift.http
+
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.config.KyuubiConf.FrontendProtocols
+import org.apache.kyuubi.operation.KyuubiOperationPerUserSuite
+
+class KyuubiOperationThriftHttpPerUserSuite extends KyuubiOperationPerUserSuite {
+  override protected val frontendProtocols: Seq[KyuubiConf.FrontendProtocols.Value] =
+    FrontendProtocols.THRIFT_HTTP :: Nil
+
+  override protected def getJdbcUrl: String =
+    s"jdbc:hive2://${server.frontendServices.head.connectionUrl}/;transportMode=http;" +
+      s"httpPath=cliservice;"
+
+  override protected lazy val httpMode = true;
+}