diff --git a/itests/hive-unit/src/test/java/org/apache/hive/service/cli/thrift/TestThriftHttpCLIService.java b/itests/hive-unit/src/test/java/org/apache/hive/service/cli/thrift/TestThriftHttpCLIService.java index 970e904..fd0452d 100644 --- a/itests/hive-unit/src/test/java/org/apache/hive/service/cli/thrift/TestThriftHttpCLIService.java +++ b/itests/hive-unit/src/test/java/org/apache/hive/service/cli/thrift/TestThriftHttpCLIService.java @@ -160,7 +160,7 @@ private static TTransport getHttpTransport() throws Exception { String httpUrl = transportMode + "://" + host + ":" + port + "/" + thriftHttpPath + "/"; httpClient.addRequestInterceptor( - new HttpBasicAuthInterceptor(USERNAME, PASSWORD, null, null)); + new HttpBasicAuthInterceptor(USERNAME, PASSWORD, null, null, null)); return new THttpClient(httpUrl, httpClient); } diff --git a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java index ae5d972..3017a9d 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java +++ b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java @@ -280,7 +280,17 @@ private CloseableHttpClient getHttpClient(Boolean useSsl) throws SQLException { HttpClientBuilder httpClientBuilder; // Request interceptor for any request pre-processing logic HttpRequestInterceptor requestInterceptor; + Map additionalHttpHeaders = new HashMap(); + // Retrieve the additional HttpHeaders + for (Map.Entry entry : sessConfMap.entrySet()) { + String key = entry.getKey(); + + if (key.startsWith(JdbcConnectionParams.HTTP_HEADER_PREFIX)) { + additionalHttpHeaders.put(key.substring(JdbcConnectionParams.HTTP_HEADER_PREFIX.length()), + entry.getValue()); + } + } // Configure http client for kerberos/password based authentication if (isKerberosAuthMode()) { /** @@ -291,7 +301,8 @@ private CloseableHttpClient getHttpClient(Boolean useSsl) throws SQLException { */ requestInterceptor = new HttpKerberosRequestInterceptor(sessConfMap.get(JdbcConnectionParams.AUTH_PRINCIPAL), - host, getServerHttpUrl(useSsl), assumeSubject, cookieStore, cookieName); + host, getServerHttpUrl(useSsl), assumeSubject, cookieStore, cookieName, + additionalHttpHeaders); } else { /** @@ -299,7 +310,7 @@ private CloseableHttpClient getHttpClient(Boolean useSsl) throws SQLException { * In https mode, the entire information is encrypted */ requestInterceptor = new HttpBasicAuthInterceptor(getUserName(), getPassword(), - cookieStore, cookieName); + cookieStore, cookieName, additionalHttpHeaders); } // Configure http client for cookie based authentication if (isCookieEnabled) { diff --git a/jdbc/src/java/org/apache/hive/jdbc/HttpBasicAuthInterceptor.java b/jdbc/src/java/org/apache/hive/jdbc/HttpBasicAuthInterceptor.java index 9676651..9f0bf20 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/HttpBasicAuthInterceptor.java +++ b/jdbc/src/java/org/apache/hive/jdbc/HttpBasicAuthInterceptor.java @@ -18,15 +18,12 @@ package org.apache.hive.jdbc; -import java.io.IOException; +import java.util.Map; import org.apache.http.Header; -import org.apache.http.HttpException; import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CookieStore; -import org.apache.http.client.protocol.ClientContext; import org.apache.http.impl.auth.AuthSchemeBase; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.protocol.HttpContext; @@ -36,45 +33,23 @@ * used to add header with these credentials to HTTP requests * */ -public class HttpBasicAuthInterceptor implements HttpRequestInterceptor { +public class HttpBasicAuthInterceptor extends HttpRequestInterceptorBase { UsernamePasswordCredentials credentials; AuthSchemeBase authScheme; - CookieStore cookieStore; - boolean isCookieEnabled; - String cookieName; public HttpBasicAuthInterceptor(String username, String password, CookieStore cookieStore, - String cn) { - if(username != null){ + String cn, Map additionalHeaders) { + super(cookieStore, cn, additionalHeaders); + authScheme = new BasicScheme(); + if (username != null){ credentials = new UsernamePasswordCredentials(username, password); } - authScheme = new BasicScheme(); - this.cookieStore = cookieStore; - isCookieEnabled = (cookieStore != null); - cookieName = cn; } @Override - public void process(HttpRequest httpRequest, HttpContext httpContext) - throws HttpException, IOException { - if (isCookieEnabled) { - httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); - } - // Add the authentication details under the following scenarios: - // 1. Cookie Authentication is disabled OR - // 2. The first time when the request is sent OR - // 3. The server returns a 401, which sometimes means the cookie has expired - if (!isCookieEnabled || ((httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) == null && - (cookieStore == null || (cookieStore != null && - Utils.needToSendCredentials(cookieStore, cookieName)))) || - (httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) != null && - httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY). - equals(Utils.HIVE_SERVER2_RETRY_TRUE)))) { - Header basicAuthHeader = authScheme.authenticate(credentials, httpRequest, httpContext); - httpRequest.addHeader(basicAuthHeader); - } - if (isCookieEnabled) { - httpContext.setAttribute(Utils.HIVE_SERVER2_RETRY_KEY, Utils.HIVE_SERVER2_RETRY_FALSE); - } + protected void addHttpAuthHeader(HttpRequest httpRequest, HttpContext httpContext) + throws Exception { + Header basicAuthHeader = authScheme.authenticate(credentials, httpRequest, httpContext); + httpRequest.addHeader(basicAuthHeader); } } diff --git a/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java b/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java index 8bb4bf6..7a0cc7d 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java +++ b/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java @@ -18,88 +18,41 @@ package org.apache.hive.jdbc; -import java.io.IOException; -import java.util.concurrent.locks.ReentrantLock; - +import java.util.Map; import org.apache.hive.service.auth.HttpAuthUtils; -import org.apache.http.HttpException; import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; import org.apache.http.client.CookieStore; -import org.apache.http.client.protocol.ClientContext; import org.apache.http.protocol.HttpContext; /** - * * Authentication interceptor which adds Base64 encoded payload, * containing the username and kerberos service ticket, * to the outgoing http request header. - * */ -public class HttpKerberosRequestInterceptor implements HttpRequestInterceptor { +public class HttpKerberosRequestInterceptor extends HttpRequestInterceptorBase { String principal; String host; String serverHttpUrl; boolean assumeSubject; - CookieStore cookieStore; - boolean isCookieEnabled; - String cookieName; - - // A fair reentrant lock - private static ReentrantLock kerberosLock = new ReentrantLock(true); public HttpKerberosRequestInterceptor(String principal, String host, - String serverHttpUrl, boolean assumeSubject, CookieStore cs, String cn) { + String serverHttpUrl, boolean assumeSubject, CookieStore cs, String cn, + Map additionalHeaders) { + super(cs, cn, additionalHeaders); this.principal = principal; this.host = host; this.serverHttpUrl = serverHttpUrl; this.assumeSubject = assumeSubject; - this.cookieStore = cs; - isCookieEnabled = (cs != null); - cookieName = cn; } @Override - public void process(HttpRequest httpRequest, HttpContext httpContext) - throws HttpException, IOException { - String kerberosAuthHeader; - - try { - // Generate the service ticket for sending to the server. - // Locking ensures the tokens are unique in case of concurrent requests - kerberosLock.lock(); - // If cookie based authentication is allowed, generate ticket only when necessary. - // The necessary condition is either when there are no server side cookies in the - // cookiestore which can be send back or when the server returns a 401 error code - // indicating that the previous cookie has expired. - if (isCookieEnabled) { - httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); - } - // Generate the kerberos ticket under the following scenarios: - // 1. Cookie Authentication is disabled OR - // 2. The first time when the request is sent OR - // 3. The server returns a 401, which sometimes means the cookie has expired - if (!isCookieEnabled || ((httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) == null && - (cookieStore == null || (cookieStore != null && - Utils.needToSendCredentials(cookieStore, cookieName)))) || - (httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) != null && - httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY). - equals(Utils.HIVE_SERVER2_RETRY_TRUE)))) { - kerberosAuthHeader = HttpAuthUtils.getKerberosServiceTicket( - principal, host, serverHttpUrl, assumeSubject); - // Set the session key token (Base64 encoded) in the headers - httpRequest.addHeader(HttpAuthUtils.AUTHORIZATION + ": " + - HttpAuthUtils.NEGOTIATE + " ", kerberosAuthHeader); - } - if (isCookieEnabled) { - httpContext.setAttribute(Utils.HIVE_SERVER2_RETRY_KEY, Utils.HIVE_SERVER2_RETRY_FALSE); - } - } catch (Exception e) { - throw new HttpException(e.getMessage(), e); - } - finally { - kerberosLock.unlock(); - } + protected void addHttpAuthHeader(HttpRequest httpRequest, + HttpContext httpContext) throws Exception { + String kerberosAuthHeader = HttpAuthUtils.getKerberosServiceTicket( + principal, host, serverHttpUrl, assumeSubject); + // Set the session key token (Base64 encoded) in the headers + httpRequest.addHeader(HttpAuthUtils.AUTHORIZATION + ": " + + HttpAuthUtils.NEGOTIATE + " ", kerberosAuthHeader); } } diff --git a/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java b/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java new file mode 100644 index 0000000..af0e020 --- /dev/null +++ b/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java @@ -0,0 +1,76 @@ +package org.apache.hive.jdbc; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.client.CookieStore; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.protocol.HttpContext; + +public abstract class HttpRequestInterceptorBase implements HttpRequestInterceptor { + CookieStore cookieStore; + boolean isCookieEnabled; + String cookieName; + Map additionalHeaders; + + // Abstract function to add HttpAuth Header + protected abstract void addHttpAuthHeader(HttpRequest httpRequest, HttpContext httpContext) + throws Exception; + + // A fair reentrant lock + private static ReentrantLock requestLock = new ReentrantLock(true); + + public HttpRequestInterceptorBase(CookieStore cs, String cn, + Map additionalHeaders) { + this.cookieStore = cs; + isCookieEnabled = (cs != null); + cookieName = cn; + this.additionalHeaders = additionalHeaders; + } + + @Override + public void process(HttpRequest httpRequest, HttpContext httpContext) + throws HttpException, IOException { + try { + // Locking ensures the tokens are unique in case of concurrent requests + requestLock.lock(); + // If cookie based authentication is allowed, generate auth header only when necessary. + // The necessary condition is either when there are no server side cookies in the + // cookiestore which can be send back or when the server returns a 401 error code + // indicating that the previous cookie has expired. + if (isCookieEnabled) { + httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); + } + // Generate the auth header under the following scenarios: + // 1. Cookie Authentication is disabled OR + // 2. The first time when the request is sent OR + // 3. The server returns a 401, which sometimes means the cookie has expired + if (!isCookieEnabled || ((httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) == null && + (cookieStore == null || (cookieStore != null && + Utils.needToSendCredentials(cookieStore, cookieName)))) || + (httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY) != null && + httpContext.getAttribute(Utils.HIVE_SERVER2_RETRY_KEY). + equals(Utils.HIVE_SERVER2_RETRY_TRUE)))) { + addHttpAuthHeader(httpRequest, httpContext); + } + if (isCookieEnabled) { + httpContext.setAttribute(Utils.HIVE_SERVER2_RETRY_KEY, Utils.HIVE_SERVER2_RETRY_FALSE); + } + // Insert the additional http headers + if (additionalHeaders != null) { + for (Map.Entry entry : additionalHeaders.entrySet()) { + httpRequest.addHeader(entry.getKey(), entry.getValue()); + } + } + } catch (Exception e) { + throw new HttpException(e.getMessage(), e); + } + finally { + requestLock.unlock(); + } + } +} diff --git a/jdbc/src/java/org/apache/hive/jdbc/Utils.java b/jdbc/src/java/org/apache/hive/jdbc/Utils.java index 791ecc7..874a370 100644 --- a/jdbc/src/java/org/apache/hive/jdbc/Utils.java +++ b/jdbc/src/java/org/apache/hive/jdbc/Utils.java @@ -110,6 +110,8 @@ static final String COOKIE_NAME = "cookieName"; // The default value of the cookie name when CookieAuth=true static final String DEFAULT_COOKIE_NAMES_HS2 = "hive.server2.auth"; + // The http header prefix for additional headers which have to be appended to the request + static final String HTTP_HEADER_PREFIX = "http.header."; // Non-configurable params: // Currently supports JKS keystore format