You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2016/01/15 23:06:48 UTC

knox git commit: KNOX-641 - Support CAS / OAuth / OpenID C / SAML protocols using pac4j - (Jérôme Leleu via lmccay)

Repository: knox
Updated Branches:
  refs/heads/master 3158bc842 -> 447b8fd7a


KNOX-641 - Support CAS / OAuth / OpenID C / SAML protocols using pac4j - (Jérôme Leleu via lmccay)

Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/447b8fd7
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/447b8fd7
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/447b8fd7

Branch: refs/heads/master
Commit: 447b8fd7a328304c6660391ba8941d1d0e8f03d4
Parents: 3158bc8
Author: Larry McCay <lm...@hortonworks.com>
Authored: Fri Jan 15 17:06:34 2016 -0500
Committer: Larry McCay <lm...@hortonworks.com>
Committed: Fri Jan 15 17:06:34 2016 -0500

----------------------------------------------------------------------
 .../federation/SSOCookieProviderTest.java       |  17 +-
 gateway-provider-security-pac4j/pom.xml         | 100 +++++++++++
 .../hadoop/gateway/pac4j/Pac4jMessages.java     |  43 +++++
 .../Pac4jFederationProviderContributor.java     |  77 +++++++++
 .../pac4j/filter/Pac4jDispatcherFilter.java     | 172 +++++++++++++++++++
 .../pac4j/filter/Pac4jIdentityAdapter.java      | 132 ++++++++++++++
 .../gateway/pac4j/session/KnoxSessionStore.java | 120 +++++++++++++
 ...gateway.deploy.ProviderDeploymentContributor |  19 ++
 .../gateway/pac4j/MockHttpServletRequest.java   |  88 ++++++++++
 .../gateway/pac4j/MockHttpServletResponse.java  |  82 +++++++++
 .../hadoop/gateway/pac4j/Pac4jProviderTest.java | 149 ++++++++++++++++
 gateway-release/pom.xml                         |   4 +
 .../gateway/service/knoxsso/WebSSOResource.java |  54 ++----
 .../service/knoxsso/WebSSOResourceTest.java     |  33 ----
 .../services/security/token/impl/JWTToken.java  |  13 +-
 .../org/apache/hadoop/gateway/util/Urls.java    |  42 +++++
 .../apache/hadoop/gateway/util/UrlsTest.java    |  57 ++++++
 pom.xml                                         |  10 +-
 18 files changed, 1120 insertions(+), 92 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
index cfa1b37..c613869 100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
@@ -394,19 +394,20 @@ public class SSOCookieProviderTest  {
 
   protected SignedJWT getJWT(String sub, Date expires, RSAPrivateKey privateKey)
       throws Exception {
-    JWTClaimsSet claimsSet = new JWTClaimsSet();
-    claimsSet.setSubject(sub);
-    claimsSet.setIssueTime(new Date(new Date().getTime()));
-    claimsSet.setIssuer("https://c2id.com");
-    claimsSet.setCustomClaim("scope", "openid");
-    claimsSet.setExpirationTime(expires);
     List<String> aud = new ArrayList<String>();
     aud.add("bar");
-    claimsSet.setAudience("bar");
+
+    JWTClaimsSet claims = new JWTClaimsSet.Builder()
+    .issuer("https://c2id.com")
+    .subject(sub)
+    .audience(aud)
+    .expirationTime(expires)
+    .claim("scope", "openid")
+    .build();
 
     JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
 
-    SignedJWT signedJWT = new SignedJWT(header, claimsSet);
+    SignedJWT signedJWT = new SignedJWT(header, claims);
     Base64URL sigInput = Base64URL.encode(signedJWT.getSigningInput());
     JWSSigner signer = new RSASSASigner(privateKey);
 

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/pom.xml b/gateway-provider-security-pac4j/pom.xml
new file mode 100644
index 0000000..08d3751
--- /dev/null
+++ b/gateway-provider-security-pac4j/pom.xml
@@ -0,0 +1,100 @@
+<!--
+   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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.knox</groupId>
+        <artifactId>gateway</artifactId>
+        <version>0.8.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>gateway-provider-security-pac4j</artifactId>
+
+    <name>gateway-provider-security-pac4j</name>
+    <description>An extension of the gateway integrating pac4j as an authentication provider.</description>
+
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <properties>
+        <j2e-pac4j.version>1.2.1</j2e-pac4j.version>
+        <pac4j.version>1.8.3</pac4j.version>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>${gateway-group}</groupId>
+            <artifactId>gateway-server</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.pac4j</groupId>
+            <artifactId>j2e-pac4j</artifactId>
+            <version>${j2e-pac4j.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.pac4j</groupId>
+            <artifactId>pac4j-http</artifactId>
+            <version>${pac4j.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.pac4j</groupId>
+            <artifactId>pac4j-config</artifactId>
+            <version>${pac4j.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>xalan</groupId>
+                    <artifactId>xalan</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>1.10.19</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-test-utils</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/Pac4jMessages.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/Pac4jMessages.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/Pac4jMessages.java
new file mode 100644
index 0000000..b3526ae
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/Pac4jMessages.java
@@ -0,0 +1,43 @@
+/**
+ * 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.hadoop.gateway.pac4j;
+
+import org.apache.hadoop.gateway.i18n.messages.Message;
+import org.apache.hadoop.gateway.i18n.messages.MessageLevel;
+import org.apache.hadoop.gateway.i18n.messages.Messages;
+
+/**
+ * Logging messages for the pac4j provider.
+ *
+ * @since 0.8.0
+ */
+@Messages(logger="org.apache.hadoop.gateway.pac4j")
+public interface Pac4jMessages {
+
+  @Message( level = MessageLevel.ERROR, text = "pac4j callback URL required")
+  public void ssoAuthenticationProviderUrlRequired();
+
+  @Message( level = MessageLevel.ERROR, text = "At least one pac4j client must be defined")
+  public void atLeastOnePac4jClientMustBeDefined();
+
+  @Message( level = MessageLevel.ERROR, text = "Crypto service, alias service and cluster name required")
+  public void cryptoServiceAndAliasServiceAndClusterNameRequired();
+
+  @Message( level = MessageLevel.ERROR, text = "Unable to generate a password for encryption")
+  public void unableToGenerateAPasswordForEncryption(Exception e);
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/deploy/Pac4jFederationProviderContributor.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/deploy/Pac4jFederationProviderContributor.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/deploy/Pac4jFederationProviderContributor.java
new file mode 100644
index 0000000..c673ed5
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/deploy/Pac4jFederationProviderContributor.java
@@ -0,0 +1,77 @@
+/**
+ * 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.hadoop.gateway.pac4j.deploy;
+
+import org.apache.hadoop.gateway.deploy.DeploymentContext;
+import org.apache.hadoop.gateway.deploy.ProviderDeploymentContributorBase;
+import org.apache.hadoop.gateway.descriptor.FilterParamDescriptor;
+import org.apache.hadoop.gateway.descriptor.ResourceDescriptor;
+import org.apache.hadoop.gateway.topology.Provider;
+import org.apache.hadoop.gateway.topology.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Descriptor of the pac4j provider. This module must be deployed for the KnoxSSO service.
+ *
+ * @since 0.8.0
+ */
+public class Pac4jFederationProviderContributor extends ProviderDeploymentContributorBase {
+
+  private static final String ROLE = "federation";
+  private static final String NAME = "pac4j";
+  private static final String DISPATCHER_FILTER_CLASSNAME = "org.apache.hadoop.gateway.pac4j.filter.Pac4jDispatcherFilter";
+  private static final String IDENTITY_ADAPTER_CLASSNAME = "org.apache.hadoop.gateway.pac4j.filter.Pac4jIdentityAdapter";
+
+  @Override
+  public String getRole() {
+    return ROLE;
+  }
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+  
+  @Override
+  public void initializeContribution(DeploymentContext context) {
+    super.initializeContribution(context);
+  }
+
+  @Override
+  public void contributeProvider(DeploymentContext context, Provider provider) {
+  }
+
+  @Override
+  public void contributeFilter(DeploymentContext context, Provider provider, Service service, 
+      ResourceDescriptor resource, List<FilterParamDescriptor> params) {
+    // blindly add all the provider params as filter init params
+    if (params == null) {
+      params = new ArrayList<FilterParamDescriptor>();
+    }
+    Map<String, String> providerParams = provider.getParams();
+    for(Entry<String, String> entry : providerParams.entrySet()) {
+      params.add( resource.createFilterParam().name( entry.getKey() ).value( entry.getValue() ) );
+    }
+    resource.addFilter().name( getName() ).role( getRole() ).impl( DISPATCHER_FILTER_CLASSNAME ).params( params );
+    resource.addFilter().name( getName() ).role( getRole() ).impl( IDENTITY_ADAPTER_CLASSNAME ).params( params );
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jDispatcherFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jDispatcherFilter.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jDispatcherFilter.java
new file mode 100644
index 0000000..a9506cf
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jDispatcherFilter.java
@@ -0,0 +1,172 @@
+/**
+ * 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.hadoop.gateway.pac4j.filter;
+
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.pac4j.Pac4jMessages;
+import org.apache.hadoop.gateway.pac4j.session.KnoxSessionStore;
+import org.apache.hadoop.gateway.services.GatewayServices;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.hadoop.gateway.services.security.AliasServiceException;
+import org.apache.hadoop.gateway.services.security.CryptoService;
+import org.pac4j.config.client.PropertiesConfigFactory;
+import org.pac4j.core.client.Client;
+import org.pac4j.core.client.Clients;
+import org.pac4j.core.config.Config;
+import org.pac4j.core.config.ConfigSingleton;
+import org.pac4j.core.context.J2EContext;
+import org.pac4j.core.context.Pac4jConstants;
+import org.pac4j.core.util.CommonHelper;
+import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
+import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
+import org.pac4j.j2e.filter.CallbackFilter;
+import org.pac4j.j2e.filter.RequiresAuthenticationFilter;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>This is the main filter for the pac4j provider. The pac4j provider module heavily relies on the j2e-pac4j library (https://github.com/pac4j/j2e-pac4j).</p>
+ * <p>This filter dispatches the HTTP calls between the j2e-pac4j filters:</p>
+ * <ul>
+ *     <li>to the {@link CallbackFilter} if the <code>client_name</code> parameter exists: it finishes the authentication process</li>
+ *     <li>to the {@link RequiresAuthenticationFilter} otherwise: it starts the authentication process (redirection to the identity provider) if the user is not authenticated</li>
+ * </ul>
+ * <p>It uses the {@link KnoxSessionStore} to manage session data. The generated cookies are defined on a domain name
+ * which can be configured via the domain suffix parameter: <code>pac4j.cookie.domain.suffix</code>.</p>
+ * <p>The callback url must be defined to the current protected url (KnoxSSO service for example) via the parameter: <code>pac4j.callbackUrl</code>.</p>
+ *
+ * @since 0.8.0
+ */
+public class Pac4jDispatcherFilter implements Filter {
+
+  private static Pac4jMessages log = MessagesFactory.get(Pac4jMessages.class);
+
+  public static final String TEST_BASIC_AUTH = "testBasicAuth";
+
+  public static final String PAC4J_CALLBACK_URL = "pac4j.callbackUrl";
+
+  private static final String PAC4J_COOKIE_DOMAIN_SUFFIX_PARAM = "pac4j.cookie.domain.suffix";
+
+  private CallbackFilter callbackFilter;
+
+  private RequiresAuthenticationFilter requiresAuthenticationFilter;
+
+  @Override
+  public void init( FilterConfig filterConfig ) throws ServletException {
+    // JWT service
+    final ServletContext context = filterConfig.getServletContext();
+    CryptoService cryptoService = null;
+    AliasService aliasService = null;
+    String clusterName = null;
+    if (context != null) {
+      GatewayServices services = (GatewayServices) context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+      clusterName = (String) context.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+      if (services != null) {
+        cryptoService = (CryptoService) services.getService(GatewayServices.CRYPTO_SERVICE);
+        aliasService = (AliasService) services.getService(GatewayServices.ALIAS_SERVICE);
+      }
+    }
+    // crypto service, alias service and cluster name are mandatory
+    if (cryptoService == null || aliasService == null || clusterName == null) {
+      log.cryptoServiceAndAliasServiceAndClusterNameRequired();
+      throw new ServletException("The crypto service, alias service and cluster name are required.");
+    }
+    try {
+      aliasService.getPasswordFromAliasForCluster(clusterName, KnoxSessionStore.PAC4J_PASSWORD, true);
+    } catch (AliasServiceException e) {
+      log.unableToGenerateAPasswordForEncryption(e);
+      throw new ServletException("Unable to generate a password for encryption.");
+    }
+
+    // url to SSO authentication provider
+    final String pac4jCallbackUrl = filterConfig.getInitParameter(PAC4J_CALLBACK_URL);
+    if (pac4jCallbackUrl == null) {
+      log.ssoAuthenticationProviderUrlRequired();
+      throw new ServletException("Required pac4j callback URL is missing.");
+    }
+
+    final Config config;
+    final String clientName;
+    // client name from servlet parameter (if defined)
+    final String clientNameParameter = filterConfig.getInitParameter(Pac4jConstants.CLIENT_NAME);
+    if (TEST_BASIC_AUTH.equalsIgnoreCase(clientNameParameter)) {
+      // test configuration
+      final IndirectBasicAuthClient indirectBasicAuthClient = new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator());
+      indirectBasicAuthClient.setRealmName("Knox TEST");
+      config = new Config(pac4jCallbackUrl, indirectBasicAuthClient);
+      clientName = "IndirectBasicAuthClient";
+    } else {
+      // get clients from the init parameters
+      final Map<String, String> properties = new HashMap<>();
+      final Enumeration<String> names = filterConfig.getInitParameterNames();
+      while (names.hasMoreElements()) {
+        final String key = names.nextElement();
+        properties.put(key, filterConfig.getInitParameter(key));
+      }
+      final PropertiesConfigFactory propertiesConfigFactory = new PropertiesConfigFactory(pac4jCallbackUrl, properties);
+      config = propertiesConfigFactory.build();
+      final List<Client> clients = config.getClients().getClients();
+      if (clients == null || clients.size() ==0) {
+        log.atLeastOnePac4jClientMustBeDefined();
+        throw new ServletException("At least one pac4j client must be defined.");
+      }
+      if (CommonHelper.isBlank(clientNameParameter)) {
+        clientName = clients.get(0).getName();
+      } else {
+        clientName = clientNameParameter;
+      }
+    }
+
+    callbackFilter = new CallbackFilter();
+    requiresAuthenticationFilter = new RequiresAuthenticationFilter();
+    requiresAuthenticationFilter.setClientName(clientName);
+    requiresAuthenticationFilter.setConfig(config);
+
+    final String domainSuffix = context.getInitParameter(PAC4J_COOKIE_DOMAIN_SUFFIX_PARAM);
+    config.setSessionStore(new KnoxSessionStore(cryptoService, clusterName, domainSuffix));
+    ConfigSingleton.setConfig(config);
+  }
+
+  @Override
+  public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+
+    final HttpServletRequest request = (HttpServletRequest) servletRequest;
+    final HttpServletResponse response = (HttpServletResponse) servletResponse;
+    final J2EContext context = new J2EContext(request, response, ConfigSingleton.getConfig().getSessionStore());
+
+    // it's a callback from an identity provider
+    if (request.getParameter(Clients.DEFAULT_CLIENT_NAME_PARAMETER) != null) {
+      // apply CallbackFilter
+      callbackFilter.doFilter(servletRequest, servletResponse, filterChain);
+    } else {
+      // otherwise just apply security and requires authentication
+      // apply RequiresAuthenticationFilter
+      requiresAuthenticationFilter.doFilter(servletRequest, servletResponse, filterChain);
+    }
+  }
+
+  @Override
+  public void destroy() { }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jIdentityAdapter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jIdentityAdapter.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jIdentityAdapter.java
new file mode 100644
index 0000000..52fad88
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jIdentityAdapter.java
@@ -0,0 +1,132 @@
+/**
+ * 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.hadoop.gateway.pac4j.filter;
+
+import org.apache.hadoop.gateway.audit.api.*;
+import org.apache.hadoop.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.hadoop.gateway.filter.AbstractGatewayFilter;
+import org.apache.hadoop.gateway.security.PrimaryPrincipal;
+import org.pac4j.core.config.ConfigSingleton;
+import org.pac4j.core.context.J2EContext;
+import org.pac4j.core.profile.ProfileManager;
+import org.pac4j.core.profile.UserProfile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.Subject;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+/**
+ * <p>This filter retrieves the authenticated user saved by the pac4j provider and injects it into the J2E HTTP request.</p>
+ *
+ * @since 0.8.0
+ */
+public class Pac4jIdentityAdapter implements Filter {
+
+  private final static Logger logger = LoggerFactory.getLogger(Pac4jIdentityAdapter.class);
+
+  private static AuditService auditService = AuditServiceFactory.getAuditService();
+  private static Auditor auditor = auditService.getAuditor(
+      AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME,
+      AuditConstants.KNOX_COMPONENT_NAME );
+
+  private String testIdentifier;
+
+  @Override
+  public void init( FilterConfig filterConfig ) throws ServletException {
+  }
+
+  public void destroy() {
+  }
+
+  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
+      throws IOException, ServletException {
+
+    final HttpServletRequest request = (HttpServletRequest) servletRequest;
+    final HttpServletResponse response = (HttpServletResponse) servletResponse;
+    final J2EContext context = new J2EContext(request, response, ConfigSingleton.getConfig().getSessionStore());
+    final ProfileManager manager = new ProfileManager(context);
+    final UserProfile profile = manager.get(true);
+    logger.debug("User authenticated as: {}", profile);
+    manager.remove(true);
+    final String id = profile.getId();
+    testIdentifier = id;
+    PrimaryPrincipal pp = new PrimaryPrincipal(id);
+    Subject subject = new Subject();
+    subject.getPrincipals().add(pp);
+    auditService.getContext().setUsername(id);
+    String sourceUri = (String)request.getAttribute( AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME );
+    auditor.audit(Action.AUTHENTICATION, sourceUri, ResourceType.URI, ActionOutcome.SUCCESS);
+    
+    doAs(request, response, chain, subject);
+  }
+  
+  private void doAs(final ServletRequest request,
+      final ServletResponse response, final FilterChain chain, Subject subject)
+      throws IOException, ServletException {
+    try {
+      Subject.doAs(
+          subject,
+          new PrivilegedExceptionAction<Object>() {
+            public Object run() throws Exception {
+              chain.doFilter(request, response);
+              return null;
+            }
+          }
+          );
+    }
+    catch (PrivilegedActionException e) {
+      Throwable t = e.getCause();
+      if (t instanceof IOException) {
+        throw (IOException) t;
+      }
+      else if (t instanceof ServletException) {
+        throw (ServletException) t;
+      }
+      else {
+        throw new ServletException(t);
+      }
+    }
+  }
+
+  /**
+   * For tests only.
+   */
+  public static void setAuditService(AuditService auditService) {
+    Pac4jIdentityAdapter.auditService = auditService;
+  }
+
+  /**
+   * For tests only.
+   */
+  public static void setAuditor(Auditor auditor) {
+    Pac4jIdentityAdapter.auditor = auditor;
+  }
+
+  /**
+   * For tests only.
+     */
+  public String getTestIdentifier() {
+    return testIdentifier;
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/session/KnoxSessionStore.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/session/KnoxSessionStore.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/session/KnoxSessionStore.java
new file mode 100644
index 0000000..24b6b6b
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/session/KnoxSessionStore.java
@@ -0,0 +1,120 @@
+/**
+ * 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.hadoop.gateway.pac4j.session;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.hadoop.gateway.services.security.CryptoService;
+import org.apache.hadoop.gateway.services.security.EncryptionResult;
+import org.apache.hadoop.gateway.util.Urls;
+import org.pac4j.core.context.ContextHelper;
+import org.pac4j.core.context.Cookie;
+import org.pac4j.core.context.WebContext;
+import org.pac4j.core.context.session.SessionStore;
+import org.pac4j.core.exception.TechnicalException;
+import org.pac4j.core.util.JavaSerializationHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+
+/**
+ * Specific session store where data are saved into cookies (and not in memory).
+ * Each data is encrypted and base64 encoded before being saved as a cookie (for security reasons).
+ *
+ * @since 0.8.0
+ */
+public class KnoxSessionStore implements SessionStore {
+
+    private final static Logger logger = LoggerFactory.getLogger(KnoxSessionStore.class);
+
+    public final static String PAC4J_PASSWORD = "pac4j.password";
+
+    public final static String PAC4J_SESSION_PREFIX = "pac4j.session.";
+
+    private final JavaSerializationHelper javaSerializationHelper;
+
+    private final CryptoService cryptoService;
+
+    private final String clusterName;
+
+    private final String domainSuffix;
+
+    public KnoxSessionStore(final CryptoService cryptoService, final String clusterName, final String domainSuffix) {
+        javaSerializationHelper = new JavaSerializationHelper();
+        this.cryptoService = cryptoService;
+        this.clusterName = clusterName;
+        this.domainSuffix = domainSuffix;
+    }
+
+    public String getOrCreateSessionId(WebContext context) {
+        return null;
+    }
+
+    private Serializable decryptBase64(final String v) {
+        if (v != null && v.length() > 0) {
+            byte[] bytes = Base64.decodeBase64(v);
+            EncryptionResult result = EncryptionResult.fromByteArray(bytes);
+            byte[] clear = cryptoService.decryptForCluster(this.clusterName,
+                    PAC4J_PASSWORD,
+                    result.cipher,
+                    result.iv,
+                    result.salt);
+            if (clear != null) {
+                return javaSerializationHelper.unserializeFromBytes(clear);
+            }
+        }
+        return null;
+    }
+
+    public Object get(WebContext context, String key) {
+        final Cookie cookie = ContextHelper.getCookie(context, PAC4J_SESSION_PREFIX + key);
+        Object value = null;
+        if (cookie != null) {
+            value = decryptBase64(cookie.getValue());
+        }
+        logger.debug("Get from session: {} = {}", key, value);
+        return value;
+    }
+
+    private String encryptBase64(final Object o) {
+        if (o == null) {
+            return null;
+        } else {
+            final byte[] bytes = javaSerializationHelper.serializeToBytes((Serializable) o);
+            EncryptionResult result = cryptoService.encryptForCluster(this.clusterName, PAC4J_PASSWORD, bytes);
+            return Base64.encodeBase64String(result.toByteAray());
+        }
+    }
+
+    public void set(WebContext context, String key, Object value) {
+        logger.debug("Save in session: {} = {}", key, value);
+        final Cookie cookie = new Cookie(PAC4J_SESSION_PREFIX + key, encryptBase64(value));
+        try {
+            String domain = Urls.getDomainName(context.getFullRequestURL(), this.domainSuffix);
+            if (domain == null) {
+                domain = context.getServerName();
+            }
+            cookie.setDomain(domain);
+        } catch (final Exception e) {
+            throw new TechnicalException(e);
+        }
+        cookie.setHttpOnly(true);
+        cookie.setSecure(ContextHelper.isHttpsOrSecure(context));
+        context.addResponseCookie(cookie);
+    }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor b/gateway-provider-security-pac4j/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
new file mode 100644
index 0000000..8cde74f
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
@@ -0,0 +1,19 @@
+##########################################################################
+# 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.
+##########################################################################
+
+org.apache.hadoop.gateway.pac4j.deploy.Pac4jFederationProviderContributor

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletRequest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletRequest.java b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletRequest.java
new file mode 100644
index 0000000..b09adc9
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletRequest.java
@@ -0,0 +1,88 @@
+/**
+ * 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.hadoop.gateway.pac4j;
+
+import javax.servlet.http.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Mockito.*;
+
+public class MockHttpServletRequest extends HttpServletRequestWrapper {
+
+    private String requestUrl;
+    private Cookie[] cookies;
+    private String serverName;
+    private Map<String, String> parameters = new HashMap<>();
+    private Map<String, String> headers = new HashMap<>();
+
+    public MockHttpServletRequest() {
+        super(mock(HttpServletRequest.class));
+    }
+
+    @Override
+    public Cookie[] getCookies() {
+        return cookies;
+    }
+
+    public void setCookies(final Cookie[] cookies) {
+        this.cookies = cookies;
+    }
+
+    @Override
+    public StringBuffer getRequestURL() {
+        return new StringBuffer(requestUrl);
+    }
+
+    public void setRequestURL(final String requestUrl) {
+        this.requestUrl = requestUrl;
+    }
+
+    @Override
+    public String getServerName() {
+        return serverName;
+    }
+
+    public void setServerName(final String serverName) {
+        this.serverName = serverName;
+    }
+
+    @Override
+    public String getParameter(String name) {
+        return parameters.get(name);
+    }
+
+    public void addParameter(String key, String value) {
+        parameters.put(key, value);
+    }
+
+    @Override
+    public String getHeader(String name) {
+        return headers.get(name);
+    }
+
+    public void addHeader(String key, String value) {
+        headers.put(key, value);
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletResponse.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletResponse.java b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletResponse.java
new file mode 100644
index 0000000..11d104c
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletResponse.java
@@ -0,0 +1,82 @@
+/**
+ * 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.hadoop.gateway.pac4j;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.*;
+
+public class MockHttpServletResponse extends HttpServletResponseWrapper {
+
+    private List<Cookie> cookies = new ArrayList<>();
+    private String location;
+    private int status = 0;
+    private Map<String, String> headers = new HashMap<>();
+
+    public MockHttpServletResponse() {
+        super(mock(HttpServletResponse.class));
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+        headers.put(name, value);
+    }
+
+    @Override
+    public void addHeader(String name, String value) {
+        headers.put(name, value);
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    @Override
+    public void addCookie(Cookie cookie) {
+        cookies.add(cookie);
+    }
+
+    public List<Cookie> getCookies() {
+        return cookies;
+    }
+
+    @Override
+    public void sendRedirect(String location) throws IOException {
+        setStatus(302);
+        setHeader("Location", location);
+    }
+
+    @Override
+    public void setStatus(int sc) {
+        status = sc;
+    }
+
+    @Override
+    public int getStatus() {
+        return status;
+    }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/Pac4jProviderTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/Pac4jProviderTest.java b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/Pac4jProviderTest.java
new file mode 100644
index 0000000..e2cee83
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/Pac4jProviderTest.java
@@ -0,0 +1,149 @@
+/**
+ * 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.hadoop.gateway.pac4j;
+
+import org.apache.hadoop.gateway.audit.api.AuditContext;
+import org.apache.hadoop.gateway.audit.api.AuditService;
+import org.apache.hadoop.gateway.audit.api.Auditor;
+import org.apache.hadoop.gateway.pac4j.filter.Pac4jDispatcherFilter;
+import org.apache.hadoop.gateway.pac4j.filter.Pac4jIdentityAdapter;
+import org.apache.hadoop.gateway.pac4j.session.KnoxSessionStore;
+import org.apache.hadoop.gateway.services.GatewayServices;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.hadoop.gateway.services.security.impl.DefaultCryptoService;
+import org.junit.Test;
+import org.pac4j.core.client.Clients;
+import org.pac4j.core.context.Pac4jConstants;
+import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.*;
+import static org.junit.Assert.*;
+
+/**
+ * This class simulates a full authentication process using pac4j.
+ */
+public class Pac4jProviderTest {
+
+    private final static String LOCALHOST = "127.0.0.1";
+    private final static String HADOOP_SERVICE_URL = "https://" + LOCALHOST + ":8443/gateway/sandox/webhdfs/v1/tmp?op=LISTSTATUS";
+    private final static String KNOXSSO_SERVICE_URL = "https://" + LOCALHOST + ":8443/gateway/idp/api/v1/websso";
+    private final static String PAC4J_CALLBACK_URL = KNOXSSO_SERVICE_URL;
+    private final static String ORIGINAL_URL = "originalUrl";
+    private final static String CLUSTER_NAME = "knox";
+    private final static String PAC4J_PASSWORD = "pwdfortest";
+    private final static String CLIENT_CLASS = IndirectBasicAuthClient.class.getSimpleName();
+    private final static String USERNAME = "jleleu";
+
+    @Test
+    public void test() throws Exception {
+        final AliasService aliasService = mock(AliasService.class);
+        when(aliasService.getPasswordFromAliasForCluster(CLUSTER_NAME, KnoxSessionStore.PAC4J_PASSWORD, true)).thenReturn(PAC4J_PASSWORD.toCharArray());
+        when(aliasService.getPasswordFromAliasForCluster(CLUSTER_NAME, KnoxSessionStore.PAC4J_PASSWORD)).thenReturn(PAC4J_PASSWORD.toCharArray());
+
+        final DefaultCryptoService cryptoService = new DefaultCryptoService();
+        cryptoService.setAliasService(aliasService);
+
+        final GatewayServices services = mock(GatewayServices.class);
+        when(services.getService(GatewayServices.CRYPTO_SERVICE)).thenReturn(cryptoService);
+        when(services.getService(GatewayServices.ALIAS_SERVICE)).thenReturn(aliasService);
+
+        final ServletContext context = mock(ServletContext.class);
+        when(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).thenReturn(services);
+        when(context.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE)).thenReturn(CLUSTER_NAME);
+
+        final FilterConfig config = mock(FilterConfig.class);
+        when(config.getServletContext()).thenReturn(context);
+        when(config.getInitParameter(Pac4jDispatcherFilter.PAC4J_CALLBACK_URL)).thenReturn(PAC4J_CALLBACK_URL);
+        when(config.getInitParameter(Pac4jConstants.CLIENT_NAME)).thenReturn(Pac4jDispatcherFilter.TEST_BASIC_AUTH);
+
+        final Pac4jDispatcherFilter dispatcher = new Pac4jDispatcherFilter();
+        dispatcher.init(config);
+        final Pac4jIdentityAdapter adapter = new Pac4jIdentityAdapter();
+        adapter.init(config);
+        adapter.setAuditor(mock(Auditor.class));
+        final AuditService auditService = mock(AuditService.class);
+        when(auditService.getContext()).thenReturn(mock(AuditContext.class));
+        adapter.setAuditService(auditService);
+
+        // step 1: call the KnoxSSO service with an original url pointing to an Hadoop service (redirected by the SSOCookieProvider)
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setRequestURL(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL);
+        request.setCookies(new Cookie[0]);
+        request.setServerName(LOCALHOST);
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        FilterChain filterChain = mock(FilterChain.class);
+        dispatcher.doFilter(request, response, filterChain);
+        // it should be a redirection to the identity provider
+        assertEquals(302, response.getStatus());
+        assertEquals(PAC4J_CALLBACK_URL + "?" + Clients.DEFAULT_CLIENT_NAME_PARAMETER + "=" + CLIENT_CLASS, response.getHeaders().get("Location"));
+        // we should have one cookie for the saved requested url
+        List<Cookie> cookies = response.getCookies();
+        assertEquals(1, cookies.size());
+        final Cookie requestedUrlCookie = cookies.get(0);
+        assertEquals(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.REQUESTED_URL, requestedUrlCookie.getName());
+
+        // step 2: send credentials to the callback url (callback from the identity provider)
+        request = new MockHttpServletRequest();
+        request.setCookies(new Cookie[]{requestedUrlCookie});
+        request.setRequestURL(PAC4J_CALLBACK_URL + "?" + Clients.DEFAULT_CLIENT_NAME_PARAMETER + "=" + CLIENT_CLASS);
+        request.addParameter(Clients.DEFAULT_CLIENT_NAME_PARAMETER, CLIENT_CLASS);
+        request.addHeader("Authorization", "Basic amxlbGV1OmpsZWxldQ==");
+        request.setServerName(LOCALHOST);
+        response = new MockHttpServletResponse();
+        filterChain = mock(FilterChain.class);
+        dispatcher.doFilter(request, response, filterChain);
+        // it should be a redirection to the original url
+        assertEquals(302, response.getStatus());
+        assertEquals(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL, response.getHeaders().get("Location"));
+        // we should have 3 cookies among with the user profile
+        cookies = response.getCookies();
+        Map<String, String> mapCookies = new HashMap<>();
+        assertEquals(3, cookies.size());
+        for (final Cookie cookie : cookies) {
+            mapCookies.put(cookie.getName(), cookie.getValue());
+        }
+        assertNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + CLIENT_CLASS + "$attemptedAuthentication"));
+        assertNotNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.USER_PROFILE));
+        assertNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.REQUESTED_URL));
+
+        // step 3: turn pac4j identity into KnoxSSO identity
+        request = new MockHttpServletRequest();
+        request.setCookies(cookies.toArray(new Cookie[cookies.size()]));
+        request.setRequestURL(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL);
+        request.setServerName(LOCALHOST);
+        response = new MockHttpServletResponse();
+        filterChain = mock(FilterChain.class);
+        dispatcher.doFilter(request, response, filterChain);
+        assertEquals(0, response.getStatus());
+        adapter.doFilter(request, response, filterChain);
+        cookies = response.getCookies();
+        assertEquals(1, cookies.size());
+        final Cookie userProfileCookie = cookies.get(0);
+        // the user profile has been cleaned
+        assertEquals(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.USER_PROFILE, userProfileCookie.getName());
+        assertNull(userProfileCookie.getValue());
+        assertEquals(USERNAME, adapter.getTestIdentifier());
+    }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-release/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-release/pom.xml b/gateway-release/pom.xml
index 1d2a60e..5a07d30 100644
--- a/gateway-release/pom.xml
+++ b/gateway-release/pom.xml
@@ -216,6 +216,10 @@
         </dependency>
         <dependency>
             <groupId>${gateway-group}</groupId>
+            <artifactId>gateway-provider-security-pac4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${gateway-group}</groupId>
             <artifactId>gateway-provider-security-webappsec</artifactId>
         </dependency>
         <dependency>

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
----------------------------------------------------------------------
diff --git a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
index 6c9bcfc..2b64456 100644
--- a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
+++ b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
@@ -18,8 +18,6 @@
 package org.apache.hadoop.gateway.service.knoxsso;
 
 import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.security.Principal;
 
 import javax.annotation.PostConstruct;
@@ -57,7 +55,7 @@ public class WebSSOResource {
   private static final String ORIGINAL_URL_COOKIE_NAME = "original-url";
   private static final String JWT_COOKIE_NAME = "hadoop-jwt";
   // default for the whitelist - open up for development - relative paths and localhost only
-  private static final String DEFAULT_WHITELIST = "^/.*$;^https?://localhost:\\d{0,9}/.*$";
+  private static final String DEFAULT_WHITELIST = "^/.*$;^https?://(localhost|127.0.0.1|0:0:0:0:0:0:0:1|::1):\\d{0,9}/.*$";
   static final String RESOURCE_PATH = "/api/v1/websso";
   private static KnoxSSOMessages log = MessagesFactory.get( KnoxSSOMessages.class );
   private boolean secureOnly = true;
@@ -66,10 +64,10 @@ public class WebSSOResource {
   private String whitelist = null;
   private String domainSuffix = null;
 
-  @Context 
+  @Context
   private HttpServletRequest request;
 
-  @Context 
+  @Context
   private HttpServletResponse response;
 
   @Context
@@ -129,7 +127,7 @@ public class WebSSOResource {
 
   private Response getAuthenticationToken(int statusCode) {
     GatewayServices services = (GatewayServices) request.getServletContext()
-        .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+            .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
     boolean removeOriginalUrlCookie = true;
     String original = getCookieValue((HttpServletRequest) request, ORIGINAL_URL_COOKIE_NAME);
     if (original == null) {
@@ -145,7 +143,7 @@ public class WebSSOResource {
       if (!validRedirect) {
         log.whiteListMatchFail(original, whitelist);
         throw new WebApplicationException("Original URL not valid according to the configured whitelist.",
-            Response.Status.BAD_REQUEST);
+                Response.Status.BAD_REQUEST);
       }
     }
 
@@ -154,13 +152,13 @@ public class WebSSOResource {
 
     try {
       JWT token = ts.issueToken(p, "RS256", System.currentTimeMillis() + tokenTTL);
-      
+
       addJWTHadoopCookie(original, token);
-      
+
       if (removeOriginalUrlCookie) {
         removeOriginalUrlCookie(response);
       }
-      
+
       log.aboutToRedirectToOriginal(original);
       response.setStatus(statusCode);
       response.setHeader("Location", original);
@@ -181,7 +179,7 @@ public class WebSSOResource {
     Cookie c = new Cookie(JWT_COOKIE_NAME,  token.toString());
     c.setPath("/");
     try {
-      String domain = getDomainName(original, domainSuffix);
+      String domain = Urls.getDomainName(original, domainSuffix);
       if (domain != null) {
         c.setDomain(domain);
       }
@@ -208,44 +206,12 @@ public class WebSSOResource {
     response.addCookie(c);
   }
 
-  String getDomainName(String url, String domainSuffix) throws URISyntaxException {
-    URI uri = new URI(url);
-    String domain = uri.getHost();
-
-    // if the hostname ends with the domainSuffix the use the domainSuffix as 
-    // the cookie domain
-    if (domainSuffix != null && domain.endsWith(domainSuffix)) {
-      return (domainSuffix.startsWith(".")) ? domainSuffix : "." + domainSuffix;
-    }
-
-    // if accessing via ip address do not wildcard the cookie domain
-    // let's use the default domain
-    if (Urls.isIp(domain)) {
-      return null;
-    }
-
-    // if there are fewer than 2 dots than this is likely a
-    // specific host and we should use the default domain
-    if (Urls.dotOccurrences(domain) < 2) {
-      return null;
-    }
-
-    // assume any non-ip address with more than
-    // 3 dots will need the first element removed and
-    // all subdmains accepted
-    int idx = domain.indexOf('.');
-    if (idx == -1) {
-      idx = 0;
-    }
-    return domain.substring(idx);
-  }
-
   private String getCookieValue(HttpServletRequest request, String name) {
     Cookie[] cookies = request.getCookies();
     String value = null;
     for(Cookie cookie : cookies){
       if(name.equals(cookie.getName())){
-          value = cookie.getValue();
+        value = cookie.getValue();
       }
     }
     if (value == null) {

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java
----------------------------------------------------------------------
diff --git a/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java
index 20c0566..73910dd 100644
--- a/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java
+++ b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java
@@ -25,39 +25,6 @@ import org.junit.Test;
  *
  */
 public class WebSSOResourceTest {
-  /**
-   * Domain name creation follows the following algorithm:
-   * 1. if the incoming request hostname endsWith a configured domain suffix return the suffix - with prefixed dot
-   * 2. if the request hostname is an ip address return null for default domain
-   * 3. if the request hostname has less than 3 dots return null for default domain
-   * 4. if request hostname has more than two dots strip the first element and return the remainder as domain 
-   * @throws Exception
-   */
-  @Test
-  public void testDomainNameCreation() throws Exception {
-    WebSSOResource resource = new WebSSOResource();
-    // determine parent domain and wildcard the cookie domain with a dot prefix
-    Assert.assertTrue(resource.getDomainName("http://www.local.com", null).equals(".local.com"));
-    Assert.assertTrue(resource.getDomainName("http://ljm.local.com", null).equals(".local.com"));
-
-    // test scenarios that will leverage the default cookie domain
-    Assert.assertEquals(resource.getDomainName("http://local.home", null), null);
-    Assert.assertEquals(resource.getDomainName("http://localhost", null), null); // chrome may not allow this
-
-    Assert.assertTrue(resource.getDomainName("http://local.home.test.com", null).equals(".home.test.com"));
-
-    // check the suffix config feature
-    Assert.assertTrue(resource.getDomainName("http://local.home.test.com", ".test.com").equals(".test.com"));
-    Assert.assertEquals(".novalocal", resource.getDomainName("http://34526yewt.novalocal", ".novalocal"));
-
-    // make sure that even if the suffix doesn't start with a dot that the domain does
-    // if we are setting a domain suffix then we want a specific domain for SSO and that
-    // will require all hosts in the domain in order for it to work
-    Assert.assertEquals(".novalocal", resource.getDomainName("http://34526yewt.novalocal", "novalocal"));
-
-    // ip addresses can not be wildcarded - may be a completely different domain
-    Assert.assertEquals(resource.getDomainName("http://127.0.0.1", null), null);
-  }
 
   @Test
   public void testWhitelistMatching() throws Exception {

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
index 7ab05ee..485fd89 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
@@ -57,11 +57,14 @@ public class JWTToken implements JWT {
 
   public JWTToken(String alg, String[] claimsArray) {
     JWSHeader header = new JWSHeader(new JWSAlgorithm(alg));
-    JWTClaimsSet claims = new JWTClaimsSet();
-    claims.setIssuer(claimsArray[0]);
-    claims.setSubject(claimsArray[1]);
-    claims.setAudience(claimsArray[2]);
-    claims.setExpirationTime(new Date(Long.parseLong(claimsArray[3])));
+
+    JWTClaimsSet claims = new JWTClaimsSet.Builder()
+    .issuer(claimsArray[0])
+    .subject(claimsArray[1])
+    .audience(claimsArray[2])
+    .expirationTime(new Date(Long.parseLong(claimsArray[3])))
+    .build();
+
     jwt = new SignedJWT(header, claims);
   }
 

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java
index 5255e3a..4d31150 100644
--- a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java
@@ -17,6 +17,8 @@
  */
 package org.apache.hadoop.gateway.util;
 
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -66,4 +68,44 @@ public class Urls {
   public static int dotOccurrences(String domain) {
     return domain.length() - domain.replace(".", "").length();
   }
+
+  /**
+   * Compute the domain name from an URL.
+   *
+   * @param url a given URL
+   * @param domainSuffix a domain suffix (can be <code>null</code>)
+   * @return the extracted domain name
+   * @throws URISyntaxException
+   */
+  public static String getDomainName(String url, String domainSuffix) throws URISyntaxException {
+    URI uri = new URI(url);
+    String domain = uri.getHost();
+
+    // if the hostname ends with the domainSuffix the use the domainSuffix as
+    // the cookie domain
+    if (domainSuffix != null && domain.endsWith(domainSuffix)) {
+      return (domainSuffix.startsWith(".")) ? domainSuffix : "." + domainSuffix;
+    }
+
+    // if accessing via ip address do not wildcard the cookie domain
+    // let's use the default domain
+    if (isIp(domain)) {
+      return null;
+    }
+
+    // if there are fewer than 2 dots than this is likely a
+    // specific host and we should use the default domain
+    if (dotOccurrences(domain) < 2) {
+      return null;
+    }
+
+    // assume any non-ip address with more than
+    // 3 dots will need the first element removed and
+    // all subdmains accepted
+    int idx = domain.indexOf('.');
+    if (idx == -1) {
+      idx = 0;
+    }
+    return domain.substring(idx);
+  }
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-util-common/src/test/java/org/apache/hadoop/gateway/util/UrlsTest.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/test/java/org/apache/hadoop/gateway/util/UrlsTest.java b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/util/UrlsTest.java
new file mode 100644
index 0000000..f55c79c
--- /dev/null
+++ b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/util/UrlsTest.java
@@ -0,0 +1,57 @@
+/**
+ * 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.hadoop.gateway.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UrlsTest {
+
+  /**
+   * Domain name creation follows the following algorithm:
+   * 1. if the incoming request hostname endsWith a configured domain suffix return the suffix - with prefixed dot
+   * 2. if the request hostname is an ip address return null for default domain
+   * 3. if the request hostname has less than 3 dots return null for default domain
+   * 4. if request hostname has more than two dots strip the first element and return the remainder as domain
+   * @throws Exception
+   */
+  @Test
+  public void testDomainNameCreation() throws Exception {
+    // determine parent domain and wildcard the cookie domain with a dot prefix
+    Assert.assertTrue(Urls.getDomainName("http://www.local.com", null).equals(".local.com"));
+    Assert.assertTrue(Urls.getDomainName("http://ljm.local.com", null).equals(".local.com"));
+
+    // test scenarios that will leverage the default cookie domain
+    Assert.assertEquals(Urls.getDomainName("http://local.home", null), null);
+    Assert.assertEquals(Urls.getDomainName("http://localhost", null), null); // chrome may not allow this
+
+    Assert.assertTrue(Urls.getDomainName("http://local.home.test.com", null).equals(".home.test.com"));
+
+    // check the suffix config feature
+    Assert.assertTrue(Urls.getDomainName("http://local.home.test.com", ".test.com").equals(".test.com"));
+    Assert.assertEquals(".novalocal", Urls.getDomainName("http://34526yewt.novalocal", ".novalocal"));
+
+    // make sure that even if the suffix doesn't start with a dot that the domain does
+    // if we are setting a domain suffix then we want a specific domain for SSO and that
+    // will require all hosts in the domain in order for it to work
+    Assert.assertEquals(".novalocal", Urls.getDomainName("http://34526yewt.novalocal", "novalocal"));
+
+    // ip addresses can not be wildcarded - may be a completely different domain
+    Assert.assertEquals(Urls.getDomainName("http://127.0.0.1", null), null);
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index c0ee6f9..4de7214 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,7 @@
         <module>gateway-provider-security-preauth</module>
         <module>gateway-provider-security-hadoopauth</module>
         <module>gateway-provider-security-shiro</module>
+        <module>gateway-provider-security-pac4j</module>
         <module>gateway-provider-security-authz-acls</module>
         <module>gateway-provider-identity-assertion-common</module>
         <module>gateway-provider-identity-assertion-concat</module>
@@ -395,6 +396,11 @@
             </dependency>
             <dependency>
                 <groupId>${gateway-group}</groupId>
+                <artifactId>gateway-provider-security-pac4j</artifactId>
+                <version>${gateway-version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${gateway-group}</groupId>
                 <artifactId>gateway-provider-security-webappsec</artifactId>
                 <version>${gateway-version}</version>
             </dependency>
@@ -602,7 +608,7 @@
             <dependency>
               <groupId>com.nimbusds</groupId>
               <artifactId>nimbus-jose-jwt</artifactId>
-              <version>3.9</version>
+              <version>4.11</version>
               <scope>compile</scope>
               <exclusions>
                 <exclusion>
@@ -1056,7 +1062,7 @@
                 <groupId>org.apache.velocity</groupId>
                 <artifactId>velocity</artifactId>
                 <version>1.7</version>
-                <scope>test</scope>
+                <!--scope>test</scope-->
             </dependency>
 
             <dependency>