LENS-1509 : Lens Server SPNEGO authentication
diff --git a/contrib/clients/python/lens/client/auth.py b/contrib/clients/python/lens/client/auth.py
new file mode 100644
index 0000000..fccc75c
--- /dev/null
+++ b/contrib/clients/python/lens/client/auth.py
@@ -0,0 +1,83 @@
+#
+# 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.
+#
+import kerberos
+from requests.auth import AuthBase
+import subprocess
+import threading
+from urlparse import urlparse
+
+
+class SpnegoAuth(AuthBase):
+    def __init__(self, keytab=None, user=None):
+        self._thread_local = threading.local()
+        self.keytab = keytab
+        self.user = user
+
+    def __call__(self, request):
+        self.init_per_thread_state()
+        request.register_hook('response', self.handle_response)
+        self._thread_local.num_401_calls = 1
+        return request
+
+    def has_tgt(self):
+        # if tgt is available return
+        return subprocess.call(['klist', '-s']) == 0
+
+    def acquire_tgt(self):
+        # try to kinit
+        exit_code = subprocess.call(['kinit', '-k', '-t', self.keytab, self.user])
+        if exit_code != 0:
+            raise Exception("Couldn't acquire TGT")
+
+    def init_per_thread_state(self):
+        # Ensure state is initialized just once per-thread
+        if not hasattr(self._thread_local, 'init'):
+            self._thread_local.init = True
+            self._thread_local.num_401_calls = None
+
+    def handle_response(self, response, **kwargs):
+        if response.status_code == 401 and self._thread_local.num_401_calls < 2:
+            self._thread_local.num_401_calls += 1
+            return self.handle_401(response, **kwargs)
+
+        self._thread_local.num_401_calls += 1
+        return response
+
+    def handle_401(self, response, **kwargs):
+        s_auth = response.headers.get('www-authenticate', '')
+        if "negotiate" in s_auth.lower():
+            # try to acquire tgt
+            if not self.has_tgt() and self.keytab is not None and self.user is not None:
+                self.acquire_tgt()
+            host = urlparse(response.url).hostname
+            spn = 'HTTP/' + host
+            code, krb_context = kerberos.authGSSClientInit(spn)
+            kerberos.authGSSClientStep(krb_context, "")
+            negotiate_details = kerberos.authGSSClientResponse(krb_context)
+            auth_header = "Negotiate " + negotiate_details
+
+            # Consume content and release the original connection
+            # to allow our new request to reuse the same one.
+            response.content
+            response.close()
+
+            response.request.headers['Authorization'] = auth_header
+            _resp = response.connection.send(response.request, **kwargs)
+            return _resp
+
+        return response
+
diff --git a/lens-api/src/main/java/org/apache/lens/api/auth/AuthScheme.java b/lens-api/src/main/java/org/apache/lens/api/auth/AuthScheme.java
new file mode 100644
index 0000000..d8434fd
--- /dev/null
+++ b/lens-api/src/main/java/org/apache/lens/api/auth/AuthScheme.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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.lens.api.auth;
+
+import java.util.Optional;
+
+import org.apache.commons.lang.StringUtils;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public enum AuthScheme {
+  BASIC("Basic"),
+  DIGEST("Digest"),
+  NEGOTIATE("Negotiate"),
+  ;
+
+  @Getter
+  private final String name;
+
+  public static Optional<AuthScheme> getFromString(String value) {
+    if (StringUtils.isBlank(value)) {
+      return Optional.empty();
+    }
+    try {
+      return Optional.of(AuthScheme.valueOf(value));
+    } catch (IllegalArgumentException e) {
+      return Optional.empty();
+    }
+  }
+}
diff --git a/lens-client/src/main/java/org/apache/lens/client/SpnegoClientFilter.java b/lens-client/src/main/java/org/apache/lens/client/SpnegoClientFilter.java
new file mode 100644
index 0000000..87696aa
--- /dev/null
+++ b/lens-client/src/main/java/org/apache/lens/client/SpnegoClientFilter.java
@@ -0,0 +1,316 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.lens.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.apache.commons.lang.StringUtils;
+
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * A client filter for Jersey client which supports SPNEGO authentication.
+ *
+ * Currently only "Negotiate" scheme is supported which will do auth using Kerberos.
+ *
+ * A user can use his/her keytab and userprincipal in lens-client-site.xml
+ * using config "lens.client.authentication.kerberos.keytab" and "lens.client.authentication.kerberos.principal"
+ * respectively. If these config is not provided then kerberos credential cache is used by default.
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class SpnegoClientFilter implements ClientRequestFilter, ClientResponseFilter{
+  private static final String REQUEST_PROPERTY_FILTER_REUSED =
+          "org.glassfish.jersey.client.authentication.HttpAuthenticationFilter.reused";
+  private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
+  private static final String NEGOTIATE_SCHEME = "Negotiate";
+
+  private static final LensClientConfig CONF = new LensClientConfig();
+  private final String keyTabLocation = CONF.get(LensClientConfig.KERBEROS_KEYTAB);
+  private final String userPrincipal = CONF.get(LensClientConfig.KERBEROS_PRINCIPAL);
+  private final String realm = CONF.get(LensClientConfig.KERBEROS_REALM);
+
+  private String servicePrincipalName;
+  private boolean useCanonicalHostname;
+
+  @Override
+  public void filter(ClientRequestContext requestContext) throws IOException {
+
+  }
+
+  @Override
+  public void filter(ClientRequestContext request, ClientResponseContext response) throws IOException {
+    if ("true".equals(request.getProperty(REQUEST_PROPERTY_FILTER_REUSED))) {
+      return;
+    }
+    boolean authenticate;
+
+    if (response.getStatus() == Response.Status.UNAUTHORIZED.getStatusCode()) {
+      String authString = response.getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE);
+      if (authString != null) {
+        if (authString.trim().startsWith(NEGOTIATE_SCHEME)) {
+          authenticate = true;
+        } else {
+          return;
+        }
+      } else {
+        authenticate = false;
+      }
+
+      if (authenticate) {
+        String authorization = getAuthorization(request.getUri());
+        repeatRequest(request, response, authorization);
+      }
+    }
+  }
+
+
+
+  private String getAuthorization(URI currentURI) {
+    try {
+      String spn = getCompleteServicePrincipalName(currentURI);
+
+      Oid oid = new Oid(SPNEGO_OID);
+
+      byte[] token = getToken(spn, oid);
+      String encodedToken = new String(Base64.getEncoder().encode(token), StandardCharsets.UTF_8);
+      return NEGOTIATE_SCHEME + " " + encodedToken;
+    } catch (LoginException e) {
+      throw new RuntimeException(e.getMessage(), e);
+    } catch (GSSException e) {
+      throw new RuntimeException(e.getMessage(), e);
+    }
+  }
+
+
+  private byte[] getToken(String spn, Oid oid) throws GSSException, LoginException {
+    LoginContext lc = buildLoginContext();
+    lc.login();
+    Subject subject = lc.getSubject();
+
+    GSSManager manager = GSSManager.getInstance();
+    GSSName serverName = manager.createName(spn, null); // 2nd oid
+
+    GSSContext context = manager
+            .createContext(serverName.canonicalize(oid), oid, null, GSSContext.DEFAULT_LIFETIME);
+
+    final byte[] token = new byte[0];
+
+    try {
+      return Subject.doAs(subject, new CreateServiceTicketAction(context, token));
+    } catch (PrivilegedActionException e) {
+      if (e.getCause() instanceof GSSException) {
+        throw (GSSException) e.getCause();
+      }
+      log.error("initSecContext", e);
+      return null;
+    }
+  }
+
+
+  private String getCompleteServicePrincipalName(URI currentURI) {
+    String name;
+
+    if (servicePrincipalName == null) {
+      String host = currentURI.getHost();
+      if (useCanonicalHostname) {
+        host = getCanonicalHostname(host);
+      }
+      name = "HTTP/" + host;
+    } else {
+      name = servicePrincipalName;
+    }
+    if (realm != null) {
+      name += "@" + realm;
+    }
+
+    return name;
+  }
+
+  private String getCanonicalHostname(String hostname) {
+    String canonicalHostname = hostname;
+    try {
+      InetAddress in = InetAddress.getByName(hostname);
+      canonicalHostname = in.getCanonicalHostName();
+      log.debug("resolved hostname=" + hostname + " to canonicalHostname=" + canonicalHostname);
+    } catch (Exception e) {
+      log.warn("unable to resolve canonical hostname", e);
+    }
+    return canonicalHostname;
+  }
+
+
+  private static final class CreateServiceTicketAction implements PrivilegedExceptionAction<byte[]> {
+    private final GSSContext context;
+    private final byte[] token;
+
+    private CreateServiceTicketAction(GSSContext context, byte[] token) {
+      this.context = context;
+      this.token = token;
+    }
+
+    public byte[] run() throws GSSException {
+      byte[] data =  context.initSecContext(token, 0, token.length);
+      return data;
+    }
+  }
+
+  @RequiredArgsConstructor
+  static class ClientLoginConfig extends Configuration {
+    private final String keyTabLocation;
+    private final String userPrincipal;
+
+    @Override
+    public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+      Map<String, Object> options = new HashMap<String, Object>();
+
+      // if we don't have keytab or principal only option is to rely on
+      // credentials cache.
+      if (StringUtils.isEmpty(keyTabLocation) || StringUtils.isEmpty(userPrincipal)) {
+        // cache
+        options.put("useTicketCache", "true");
+      } else {
+        // keytab
+        options.put("useKeyTab", "true");
+        options.put("keyTab", keyTabLocation);
+        options.put("principal", userPrincipal);
+        options.put("storeKey", "true");
+      }
+
+      options.put("doNotPrompt", "true");
+      options.put("isInitiator", "true");
+
+      return new AppConfigurationEntry[] { new AppConfigurationEntry(
+              "com.sun.security.auth.module.Krb5LoginModule",
+              AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), };
+    }
+
+  }
+
+  private LoginContext buildLoginContext() throws LoginException {
+    ClientLoginConfig loginConfig = new ClientLoginConfig(keyTabLocation, userPrincipal);
+
+    Subject subject =  null;
+    if (StringUtils.isNotBlank(keyTabLocation) && StringUtils.isNotBlank(userPrincipal)) {
+      Set<Principal> princ = new HashSet<>(1);
+      princ.add(new KerberosPrincipal(userPrincipal));
+      subject = new Subject(false, princ, new HashSet<>(), new HashSet<>());
+    }
+    LoginContext lc = new LoginContext("", subject, null, loginConfig);
+    return lc;
+  }
+
+
+  /**
+   * Repeat the {@code request} with provided {@code newAuthorizationHeader}
+   * and update the {@code response} with newest response data.
+   *
+   * @param request                Request context.
+   * @param response               Response context (will be updated with the new response data).
+   * @param newAuthorizationHeader {@code Authorization} header that should be added to the new request.
+   * @return {@code true} is the authentication was successful ({@code true} if 401 response code was not returned;
+   * {@code false} otherwise).
+   */
+  private boolean repeatRequest(ClientRequestContext request,
+                               ClientResponseContext response,
+                               String newAuthorizationHeader) {
+    Client client = ClientBuilder.newClient(request.getConfiguration());
+    String method = request.getMethod();
+    MediaType mediaType = request.getMediaType();
+    URI lUri = request.getUri();
+
+    WebTarget resourceTarget = client.target(lUri);
+
+    Invocation.Builder builder = resourceTarget.request(mediaType);
+
+    MultivaluedMap<String, Object> newHeaders = new MultivaluedHashMap<String, Object>();
+
+    for (Map.Entry<String, List<Object>> entry : request.getHeaders().entrySet()) {
+      if (HttpHeaders.AUTHORIZATION.equals(entry.getKey())) {
+        continue;
+      }
+      newHeaders.put(entry.getKey(), entry.getValue());
+    }
+
+    newHeaders.add(HttpHeaders.AUTHORIZATION, newAuthorizationHeader);
+    builder.headers(newHeaders);
+
+    builder.property(REQUEST_PROPERTY_FILTER_REUSED, "true");
+
+    Invocation invocation;
+    if (request.getEntity() == null) {
+      invocation = builder.build(method);
+    } else {
+      invocation = builder.build(method,
+              Entity.entity(request.getEntity(), request.getMediaType()));
+    }
+    Response nextResponse = invocation.invoke();
+
+    if (nextResponse.hasEntity()) {
+      response.setEntityStream(nextResponse.readEntity(InputStream.class));
+    }
+    MultivaluedMap<String, String> headers = response.getHeaders();
+    headers.clear();
+    headers.putAll(nextResponse.getStringHeaders());
+    response.setStatus(nextResponse.getStatus());
+
+    return response.getStatus() != Response.Status.UNAUTHORIZED.getStatusCode();
+  }
+}
diff --git a/lens-server/src/main/java/org/apache/lens/server/auth/Authenticate.java b/lens-server/src/main/java/org/apache/lens/server/auth/Authenticate.java
new file mode 100644
index 0000000..44b0040
--- /dev/null
+++ b/lens-server/src/main/java/org/apache/lens/server/auth/Authenticate.java
@@ -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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.lens.server.auth;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that the JAX-RS resource class/method will be authenticated by a filter.
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Authenticate {
+}
diff --git a/lens-server/src/main/java/org/apache/lens/server/auth/LensSecurityContext.java b/lens-server/src/main/java/org/apache/lens/server/auth/LensSecurityContext.java
new file mode 100644
index 0000000..c7f73a8
--- /dev/null
+++ b/lens-server/src/main/java/org/apache/lens/server/auth/LensSecurityContext.java
@@ -0,0 +1,69 @@
+/**
+ * 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.lens.server.auth;
+
+import java.security.Principal;
+
+import javax.ws.rs.core.SecurityContext;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Implementation of {@link SecurityContext} which you can inject in a resource class
+ * authenticated by lens auth filter.
+ */
+public class LensSecurityContext implements SecurityContext {
+  private final Principal principal;
+  private final String authScheme;
+
+  public LensSecurityContext(String username, String authScheme) {
+    principal = new SimplePrincipal(username);
+    this.authScheme = authScheme;
+  }
+
+  @Override
+  public Principal getUserPrincipal() {
+    return principal;
+  }
+
+  @Override
+  public boolean isUserInRole(String role) {
+    return false;
+  }
+
+  @Override
+  public boolean isSecure() {
+    return false;
+  }
+
+  @Override
+  public String getAuthenticationScheme() {
+    return authScheme;
+  }
+
+  @RequiredArgsConstructor
+  private static class SimplePrincipal implements Principal {
+    private final String name;
+
+    @Override
+    public String getName() {
+      return name;
+    }
+  }
+}
diff --git a/lens-server/src/main/java/org/apache/lens/server/auth/SpnegoAuthenticationFilter.java b/lens-server/src/main/java/org/apache/lens/server/auth/SpnegoAuthenticationFilter.java
new file mode 100644
index 0000000..a6a0abf
--- /dev/null
+++ b/lens-server/src/main/java/org/apache/lens/server/auth/SpnegoAuthenticationFilter.java
@@ -0,0 +1,277 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.lens.server.auth;
+
+import java.io.File;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Priority;
+import javax.security.auth.Subject;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.ws.rs.NotAuthorizedException;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.lens.api.auth.AuthScheme;
+import org.apache.lens.server.LensServerConf;
+import org.apache.lens.server.api.LensConfConstants;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * A JAX-RS filter for SPNEGO authentication.
+ *
+ * <p>Currently only "Negotiate" scheme is supported which will do auth using Kerberos.</p>
+ *
+ * <p>This filter can be enabled by adding an entry in {@code lens.server.ws.filternames} property and providing
+ * the impl class.</p>
+ *
+ * <pre>The following configuration is needed for the filter to function
+ * {@code lens.server.authentication.scheme} : NEGOTIATE (other values which are not supported are listed
+ * in {@link AuthScheme})
+ * {@code lens.server.authentication.kerberos.principal} : The SPN (in format HTTP/fqdn)
+ * {@code lens.server.authentication.kerberos.keytab} : Keytab of lens SPN
+ * </pre>
+ */
+
+@Slf4j
+@Priority(Priorities.AUTHENTICATION)
+public class SpnegoAuthenticationFilter implements ContainerRequestFilter {
+  private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
+  private static final String KERBEROS_LOGIN_MODULE_NAME =
+          "com.sun.security.auth.module.Krb5LoginModule";
+
+  private static final org.apache.hadoop.conf.Configuration CONF = LensServerConf.getHiveConf();
+  private static final AuthScheme AUTH_SCHEME = AuthScheme.valueOf(CONF.get(LensConfConstants.AUTH_SCHEME));
+
+  static {
+    if (AUTH_SCHEME != AuthScheme.NEGOTIATE) {
+      log.error("Lens server currently only supports NEGOTIATE auth scheme");
+      throw new RuntimeException("Lens server currently only supports NEGOTIATE auth scheme");
+    }
+  }
+
+  private String servicePrincipalName = CONF.get(LensConfConstants.KERBEROS_PRINCIPAL);
+  private String realm = CONF.get(LensConfConstants.KERBEROS_REALM);
+  private Configuration loginConfig = getJaasKrb5TicketConfig(servicePrincipalName,
+          new File(CONF.get(LensConfConstants.KERBEROS_KEYTAB)));
+
+  private HttpHeaders headers;
+
+  private UriInfo uriInfo;
+
+  private ResourceInfo resourceInfo;
+
+  @Context
+  public void setHeaders(HttpHeaders headers) {
+    this.headers = headers;
+  }
+
+  @Context
+  public void setUriInfo(UriInfo uriInfo) {
+    this.uriInfo = uriInfo;
+  }
+
+  @Context
+  public void setResourceInfo(ResourceInfo resourceInfo) {
+    this.resourceInfo = resourceInfo;
+  }
+
+  @Override
+  public void filter(ContainerRequestContext context) {
+    // only authenticate when @Authenticate is present on resource
+    if (resourceInfo.getResourceClass() == null || resourceInfo.getResourceMethod() == null) {
+      return;
+    }
+    if (!(resourceInfo.getResourceClass().isAnnotationPresent(Authenticate.class)
+            || resourceInfo.getResourceMethod().isAnnotationPresent(Authenticate.class))) {
+      return;
+    }
+    List<String> authHeaders = headers
+            .getRequestHeader(HttpHeaders.AUTHORIZATION);
+    if (authHeaders == null || authHeaders.size() != 1) {
+      log.info("No Authorization header is available");
+      throw toNotAuthorizedException(null, getFaultResponse());
+    }
+    String[] authPair = StringUtils.split(authHeaders.get(0), " ");
+    if (authPair.length != 2 || !AuthScheme.NEGOTIATE.getName().equalsIgnoreCase(authPair[0])) {
+      log.info("Negotiate Authorization scheme is expected");
+      throw toNotAuthorizedException(null, getFaultResponse());
+    }
+
+    byte[] serviceTicket = getServiceTicket(authPair[1]);
+
+    try {
+      Subject serviceSubject = loginAndGetSubject();
+
+      GSSContext gssContext = createGSSContext();
+
+      Subject.doAs(serviceSubject, new ValidateServiceTicketAction(gssContext, serviceTicket));
+
+      final GSSName srcName = gssContext.getSrcName();
+      if (srcName == null) {
+        throw toNotAuthorizedException(null, getFaultResponse());
+      }
+
+      String complexUserName = srcName.toString();
+
+      String simpleUserName = complexUserName;
+      int index = simpleUserName.lastIndexOf('@');
+      if (index > 0) {
+        simpleUserName = simpleUserName.substring(0, index);
+      }
+      context.setSecurityContext(createSecurityContext(simpleUserName, AUTH_SCHEME.getName()));
+      if (!gssContext.getCredDelegState()) {
+        gssContext.dispose();
+        gssContext = null;
+      }
+
+    } catch (LoginException e) {
+      log.info("Unsuccessful JAAS login for the service principal: " + e.getMessage());
+      throw toNotAuthorizedException(e, getFaultResponse());
+    } catch (GSSException e) {
+      log.info("GSS API exception: " + e.getMessage());
+      throw toNotAuthorizedException(e, getFaultResponse());
+    } catch (PrivilegedActionException e) {
+      log.info("PrivilegedActionException: " + e.getMessage());
+      throw toNotAuthorizedException(e, getFaultResponse());
+    }
+  }
+
+  private SecurityContext createSecurityContext(String simpleUserName, String authScheme) {
+    return new LensSecurityContext(simpleUserName, authScheme);
+  }
+
+  private GSSContext createGSSContext() throws GSSException {
+    Oid oid = new Oid(SPNEGO_OID);
+    GSSManager gssManager = GSSManager.getInstance();
+
+    String spn = getCompleteServicePrincipalName();
+    GSSName gssService = gssManager.createName(spn, null);
+
+    return gssManager.createContext(gssService.canonicalize(oid),
+            oid, null, GSSContext.DEFAULT_LIFETIME);
+  }
+
+  private Subject loginAndGetSubject() throws LoginException {
+
+    // The login without a callback can work if
+    // - Kerberos keytabs are used with a principal name set in the JAAS config
+    // - Kerberos is integrated into the OS logon process
+    //   meaning that a process which runs this code has the
+    //   user identity
+
+    LoginContext lc = null;
+    if (loginConfig != null) {
+      lc = new LoginContext("", null, null, loginConfig);
+    } else {
+      log.info("LoginContext can not be initialized");
+      throw new LoginException();
+    }
+    lc.login();
+    return lc.getSubject();
+  }
+
+  private byte[] getServiceTicket(String encodedServiceTicket) {
+    try {
+      return java.util.Base64.getDecoder().decode(encodedServiceTicket);
+    } catch (IllegalArgumentException ex) {
+      throw toNotAuthorizedException(null, getFaultResponse());
+    }
+  }
+
+  private static Response getFaultResponse() {
+    return Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, AuthScheme.NEGOTIATE.getName()).build();
+  }
+
+  private String getCompleteServicePrincipalName() {
+    String name = servicePrincipalName == null
+            ? "HTTP/" + uriInfo.getBaseUri().getHost() : servicePrincipalName;
+    if (realm != null) {
+      name += "@" + realm;
+    }
+    return name;
+  }
+
+  private static final class ValidateServiceTicketAction implements PrivilegedExceptionAction<byte[]> {
+    private final GSSContext context;
+    private final byte[] token;
+
+    private ValidateServiceTicketAction(GSSContext context, byte[] token) {
+      this.context = context;
+      this.token = token;
+    }
+
+    public byte[] run() throws GSSException {
+      byte[] data = context.acceptSecContext(token, 0, token.length);
+      return data;
+    }
+  }
+
+  private static Configuration getJaasKrb5TicketConfig(
+          final String principal, final File keytab) {
+    return new Configuration() {
+      @Override
+      public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+        Map<String, String> options = new HashMap<>();
+        options.put("principal", principal);
+        options.put("keyTab", keytab.getAbsolutePath());
+        options.put("doNotPrompt", "true");
+        options.put("useKeyTab", "true");
+        options.put("storeKey", "true");
+        options.put("isInitiator", "false");
+
+        return new AppConfigurationEntry[] {
+          new AppConfigurationEntry(KERBEROS_LOGIN_MODULE_NAME,
+                        AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options),
+        };
+      }
+    };
+  }
+
+  private WebApplicationException toNotAuthorizedException(Throwable cause, Response resp) {
+    return new NotAuthorizedException(resp, cause);
+  }
+
+}
+
diff --git a/lens-server/src/main/java/org/apache/lens/server/error/NotAuthorizedExceptionMapper.java b/lens-server/src/main/java/org/apache/lens/server/error/NotAuthorizedExceptionMapper.java
new file mode 100644
index 0000000..4cf0185
--- /dev/null
+++ b/lens-server/src/main/java/org/apache/lens/server/error/NotAuthorizedExceptionMapper.java
@@ -0,0 +1,32 @@
+/**
+ * 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.lens.server.error;
+
+import javax.ws.rs.NotAuthorizedException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+@Provider
+public class NotAuthorizedExceptionMapper implements ExceptionMapper<NotAuthorizedException> {
+  @Override
+  public Response toResponse(NotAuthorizedException exception) {
+    return exception.getResponse();
+  }
+}