You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2020/09/03 18:19:09 UTC

[nifi-registry] branch main updated: NIFIREG-411 Refactor nifi-registry-client to support other authN mechanisms

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

kdoran pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-registry.git


The following commit(s) were added to refs/heads/main by this push:
     new 2ec4f5e  NIFIREG-411 Refactor nifi-registry-client to support other authN mechanisms
2ec4f5e is described below

commit 2ec4f5e126c4217a0945c2494ebdab4f91f2a194
Author: Bryan Bende <bb...@apache.org>
AuthorDate: Thu Aug 6 16:56:23 2020 -0400

    NIFIREG-411 Refactor nifi-registry-client to support other authN mechanisms
    
    NIFIREG-411 Update CertificateUtils with changes from NiFi and add protocol to NiFiRegistryClientConfig
    
    This closes #293.
    
    Signed-off-by: Kevin Doran <kd...@apache.org>
---
 .../apache/nifi/registry/client/AccessClient.java  |  59 +++++
 .../nifi/registry/client/NiFiRegistryClient.java   |  84 ++++++
 .../registry/client/NiFiRegistryClientConfig.java  |  17 +-
 .../apache/nifi/registry/client/RequestConfig.java |  32 +++
 .../client/impl/AbstractCRUDJerseyClient.java      |   6 +-
 .../registry/client/impl/AbstractJerseyClient.java |  18 +-
 .../registry/client/impl/JerseyAccessClient.java   |  92 +++++++
 .../registry/client/impl/JerseyBucketClient.java   |   8 +-
 .../registry/client/impl/JerseyBundleClient.java   |   8 +-
 .../client/impl/JerseyBundleVersionClient.java     |  10 +-
 .../client/impl/JerseyExtensionClient.java         |   8 +-
 .../client/impl/JerseyExtensionRepoClient.java     |   8 +-
 .../registry/client/impl/JerseyFlowClient.java     |   8 +-
 .../client/impl/JerseyFlowSnapshotClient.java      |   8 +-
 .../registry/client/impl/JerseyItemsClient.java    |   8 +-
 .../client/impl/JerseyNiFiRegistryClient.java      | 138 ++++++----
 .../registry/client/impl/JerseyPoliciesClient.java |   9 +-
 .../registry/client/impl/JerseyTenantsClient.java  |   9 +-
 .../registry/client/impl/JerseyUserClient.java     |  11 +-
 .../impl/request/BasicAuthRequestConfig.java       |  55 ++++
 .../impl/request/BearerTokenRequestConfig.java     |  45 ++++
 .../impl/request/ProxiedEntityRequestConfig.java   |  62 +++++
 .../impl/request/TestBasicAuthRequestConfig.java   |  48 ++++
 .../impl/request/TestBearerTokenRequestConfig.java |  43 +++
 .../request/TestProxiedEntityRequestConfig.java    |  62 +++++
 .../registry/security/util/CertificateUtils.java   | 291 +++++++++++----------
 .../apache/nifi/registry/web/api/SecureLdapIT.java |  62 ++++-
 .../web/api/SecureNiFiRegistryClientIT.java        |  13 +-
 28 files changed, 967 insertions(+), 255 deletions(-)

diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/AccessClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/AccessClient.java
new file mode 100644
index 0000000..23cbcbc
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/AccessClient.java
@@ -0,0 +1,59 @@
+/*
+ * 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.nifi.registry.client;
+
+import java.io.IOException;
+
+/**
+ * Client for interacting with the AccessResource.
+ */
+public interface AccessClient {
+
+    /**
+     * Get an access token by authenticating with a username and password aginst the configured identity provider.
+     *
+     * @param username the username
+     * @param password the password
+     * @return the access token
+     *
+     * @throws IOException if an I/O error occurs
+     * @throws NiFiRegistryException if an non I/O error occurs
+     */
+    String getToken(String username, String password) throws NiFiRegistryException, IOException;
+
+    /**
+     * Gets an access token via spnego. It is expected that the caller of this method has wrapped the call
+     * in a {@code doAs()} using a {@link javax.security.auth.Subject}.
+     *
+     * @return the token
+     *
+     * @throws IOException if an I/O error occurs
+     * @throws NiFiRegistryException if an non I/O error occurs
+     */
+    String getTokenFromKerberosTicket() throws NiFiRegistryException, IOException;
+
+    /**
+     * Performs a logout for the user represented by the given token.
+     *
+     * @param token the toke to authenticate with
+     *
+     * @throws IOException if an I/O error occurs
+     * @throws NiFiRegistryException if an non I/O error occurs
+     */
+    void logout(String token) throws NiFiRegistryException, IOException;
+
+}
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClient.java
index a7497a1..2d2d8c8 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClient.java
@@ -29,10 +29,17 @@ public interface NiFiRegistryClient extends Closeable {
     BucketClient getBucketClient();
 
     /**
+     * @deprecated use getBucketClient(RequestConfig requestConfig)
+     *
      * @return the client for interacting with buckets on behalf of the given proxied entities
      */
     BucketClient getBucketClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with buckets using the given request config
+     */
+    BucketClient getBucketClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -41,10 +48,17 @@ public interface NiFiRegistryClient extends Closeable {
     FlowClient getFlowClient();
 
     /**
+     * @deprecated use getFlowClient(RequestConfig requestConfig)
+     *
      * @return the client for interacting with flows on behalf of the given proxied entities
      */
     FlowClient getFlowClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with flows using the given request config
+     */
+    FlowClient getFlowClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -53,10 +67,17 @@ public interface NiFiRegistryClient extends Closeable {
     FlowSnapshotClient getFlowSnapshotClient();
 
     /**
+     * @deprecated use getFlowSnapshotClient(RequestConfig requestConfig)
+     *
      * @return the client for interacting with flows/snapshots on behalf of the given proxied entities
      */
     FlowSnapshotClient getFlowSnapshotClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with flows/snapshots using the given request config
+     */
+    FlowSnapshotClient getFlowSnapshotClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -65,10 +86,17 @@ public interface NiFiRegistryClient extends Closeable {
     ItemsClient getItemsClient();
 
     /**
+     * @deprecated use getItemsClient(RequestConfig requestConfig)
+     *
      * @return the client for interacting with bucket items on behalf of the given proxied entities
      */
     ItemsClient getItemsClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with bucket items using the given request config
+     */
+    ItemsClient getItemsClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -77,10 +105,17 @@ public interface NiFiRegistryClient extends Closeable {
     UserClient getUserClient();
 
     /**
+     * @deprecated use getUserClient(RequestConfig requestConfig)
+     *
      * @return the client for obtaining information about the current user based on the given proxied entities
      */
     UserClient getUserClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for obtaining information about the current user based on the request config
+     */
+    UserClient getUserClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -89,10 +124,17 @@ public interface NiFiRegistryClient extends Closeable {
     BundleClient getBundleClient();
 
     /**
+     * @deprecated use getBundleClient(RequestConfig requestConfig)
+     *
      * @return the client for interacting with extension bundles on behalf of the given proxied entities
      */
     BundleClient getBundleClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with extension bundles using the given request config
+     */
+    BundleClient getBundleClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -101,10 +143,17 @@ public interface NiFiRegistryClient extends Closeable {
     BundleVersionClient getBundleVersionClient();
 
     /**
+     * @deprecated use getBundleVersionClient(RequestConfig requestConfig)
+     *
      * @return the client for interacting with extension bundle versions on behalf of the given proxied entities
      */
     BundleVersionClient getBundleVersionClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with extension bundle versions using the given request config
+     */
+    BundleVersionClient getBundleVersionClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -113,10 +162,17 @@ public interface NiFiRegistryClient extends Closeable {
     ExtensionRepoClient getExtensionRepoClient();
 
     /**
+     * @deprecated use getExtensionRepoClient(RequestConfig requestConfig)
+     *
      * @return the client for interacting with the extension repository on behalf of the given proxied entities
      */
     ExtensionRepoClient getExtensionRepoClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with the extension repository using the given request config
+     */
+    ExtensionRepoClient getExtensionRepoClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -125,10 +181,17 @@ public interface NiFiRegistryClient extends Closeable {
     ExtensionClient getExtensionClient();
 
     /**
+     * @deprecated use getExtensionClient(RequestConfig requestConfig)
+     *
      * @return the client for interacting with extensions on behalf of the given proxied entities
      */
     ExtensionClient getExtensionClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with extensions using the given request config
+     */
+    ExtensionClient getExtensionClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -141,12 +204,19 @@ public interface NiFiRegistryClient extends Closeable {
     /**
      * Returns client for interacting with tenants.
      *
+     * @deprecated use getTenantsClient(RequestConfig requestConfig)
+     *
      * @param proxiedEntity The given proxied entities.
      *
      * @return the client for interacting with tenants on behalf of the given proxied entities.
      */
     TenantsClient getTenantsClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with tenants using the given request config
+     */
+    TenantsClient getTenantsClient(RequestConfig requestConfig);
+
     //-------------------------------------------------------------------------------------------
 
     /**
@@ -159,12 +229,26 @@ public interface NiFiRegistryClient extends Closeable {
     /**
      * Returns client for interacting with access policies.
      *
+     * @deprecated use getPoliciesClient(RequestConfig requestConfig)
+     *
      * @param proxiedEntity The given proxied entities.
      *
      * @return the client for interacting with access policies on behalf of the given proxied entities.
      */
     PoliciesClient getPoliciesClient(String ... proxiedEntity);
 
+    /**
+     * @return the client for interacting with access policies using the given request config
+     */
+    PoliciesClient getPoliciesClient(RequestConfig requestConfig);
+
+    //-------------------------------------------------------------------------------------------
+
+    /**
+     * @return the client for obtaining access tokens
+     */
+    AccessClient getAccessClient();
+
     //-------------------------------------------------------------------------------------------
 
     /**
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClientConfig.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClientConfig.java
index de77b51..0c1b21b 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClientConfig.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClientConfig.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.registry.client;
 
+import org.apache.nifi.registry.security.util.CertificateUtils;
 import org.apache.nifi.registry.security.util.KeyStoreUtils;
 import org.apache.nifi.registry.security.util.KeystoreType;
 
@@ -36,6 +37,8 @@ import java.security.SecureRandom;
  */
 public class NiFiRegistryClientConfig {
 
+    public static final String DEFAULT_PROTOCOL = CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion();
+
     private final String baseUrl;
     private final SSLContext sslContext;
     private final String keystoreFilename;
@@ -45,6 +48,7 @@ public class NiFiRegistryClientConfig {
     private final String truststoreFilename;
     private final String truststorePass;
     private final KeystoreType truststoreType;
+    private final String protocol;
     private final HostnameVerifier hostnameVerifier;
     private final Integer readTimeout;
     private final Integer connectTimeout;
@@ -60,6 +64,7 @@ public class NiFiRegistryClientConfig {
         this.truststoreFilename = builder.truststoreFilename;
         this.truststorePass = builder.truststorePass;
         this.truststoreType = builder.truststoreType;
+        this.protocol = builder.protocol == null ? DEFAULT_PROTOCOL : builder.protocol;
         this.hostnameVerifier = builder.hostnameVerifier;
         this.readTimeout = builder.readTimeout;
         this.connectTimeout = builder.connectTimeout;
@@ -118,7 +123,7 @@ public class NiFiRegistryClientConfig {
                 // initialize the ssl context
                 KeyManager[] keyManagers = keyManagerFactory != null ? keyManagerFactory.getKeyManagers() : null;
                 TrustManager[] trustManagers = trustManagerFactory != null ? trustManagerFactory.getTrustManagers() : null;
-                final SSLContext sslContext = SSLContext.getInstance("TLS");
+                final SSLContext sslContext = SSLContext.getInstance(getProtocol());
                 sslContext.init(keyManagers, trustManagers, new SecureRandom());
                 sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
 
@@ -159,6 +164,10 @@ public class NiFiRegistryClientConfig {
         return truststoreType;
     }
 
+    public String getProtocol() {
+        return protocol;
+    }
+
     public HostnameVerifier getHostnameVerifier() {
         return hostnameVerifier;
     }
@@ -185,6 +194,7 @@ public class NiFiRegistryClientConfig {
         private String truststoreFilename;
         private String truststorePass;
         private KeystoreType truststoreType;
+        private String protocol;
         private HostnameVerifier hostnameVerifier;
         private Integer readTimeout;
         private Integer connectTimeout;
@@ -234,6 +244,11 @@ public class NiFiRegistryClientConfig {
             return this;
         }
 
+        public Builder protocol(final String protocol) {
+            this.protocol = protocol;
+            return this;
+        }
+
         public Builder hostnameVerifier(final HostnameVerifier hostnameVerifier) {
             this.hostnameVerifier = hostnameVerifier;
             return this;
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/RequestConfig.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/RequestConfig.java
new file mode 100644
index 0000000..fcb83e9
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/RequestConfig.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.nifi.registry.client;
+
+import java.util.Map;
+
+/**
+ * Configuration applied to each client request.
+ */
+public interface RequestConfig {
+
+    /**
+     * @return the headers to apply to each request
+     */
+    Map<String,String> getHeaders();
+
+
+}
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractCRUDJerseyClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractCRUDJerseyClient.java
index a6f9ac0..41005c1 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractCRUDJerseyClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractCRUDJerseyClient.java
@@ -18,20 +18,20 @@ package org.apache.nifi.registry.client.impl;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.revision.entity.RevisionInfo;
 
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.MediaType;
 import java.io.IOException;
-import java.util.Map;
 
 public class AbstractCRUDJerseyClient extends AbstractJerseyClient {
 
     protected final WebTarget baseTarget;
 
-    public AbstractCRUDJerseyClient(final WebTarget baseTarget, final Map<String, String> headers) {
-        super(headers);
+    public AbstractCRUDJerseyClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.baseTarget = baseTarget;
     }
 
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractJerseyClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractJerseyClient.java
index dd9792d..ad5ea41 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractJerseyClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractJerseyClient.java
@@ -18,6 +18,7 @@ package org.apache.nifi.registry.client.impl;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.revision.entity.RevisionInfo;
 
 import javax.ws.rs.WebApplicationException;
@@ -26,7 +27,6 @@ import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.Response;
 import java.io.IOException;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -36,16 +36,17 @@ import java.util.Map;
  */
 public class AbstractJerseyClient {
 
-    private final Map<String,String> headers;
+    private static final RequestConfig EMPTY_REQUEST_CONFIG = () ->  Collections.emptyMap();
 
-    public AbstractJerseyClient(final Map<String, String> headers) {
-        this.headers = headers == null ? Collections.emptyMap() : Collections.unmodifiableMap(new HashMap<>(headers));
-    }
+    private final RequestConfig requestConfig;
 
-    protected Map<String,String> getHeaders() {
-        return headers;
+    public AbstractJerseyClient(final RequestConfig requestConfig) {
+        this.requestConfig = (requestConfig == null ? EMPTY_REQUEST_CONFIG : requestConfig);
     }
 
+    protected RequestConfig getRequestConfig() {
+        return this.requestConfig;
+    }
     /**
      * Adds query parameters for the given RevisionInfo if populated.
      *
@@ -80,7 +81,10 @@ public class AbstractJerseyClient {
      */
     protected Invocation.Builder getRequestBuilder(final WebTarget webTarget) {
         final Invocation.Builder requestBuilder = webTarget.request();
+
+        final Map<String,String> headers = requestConfig.getHeaders();
         headers.entrySet().stream().forEach(e -> requestBuilder.header(e.getKey(), e.getValue()));
+
         return requestBuilder;
     }
 
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyAccessClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyAccessClient.java
new file mode 100644
index 0000000..4913f15
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyAccessClient.java
@@ -0,0 +1,92 @@
+/*
+ * 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.nifi.registry.client.impl;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.client.AccessClient;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
+import org.apache.nifi.registry.client.impl.request.BasicAuthRequestConfig;
+import org.apache.nifi.registry.client.impl.request.BearerTokenRequestConfig;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Jersey implementation of AccessClient.
+ */
+public class JerseyAccessClient extends AbstractJerseyClient implements AccessClient {
+
+    private final WebTarget accessTarget;
+
+    public JerseyAccessClient(final WebTarget baseTarget) {
+        super(null);
+        this.accessTarget = baseTarget.path("/access");
+    }
+
+    @Override
+    public String getToken(final String username, final String password) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(username)) {
+            throw new IllegalArgumentException("Username is required");
+        }
+
+        if (StringUtils.isBlank(password)) {
+            throw new IllegalArgumentException("Password is required");
+        }
+
+        return executeAction("Error performing login", () -> {
+            final WebTarget target = accessTarget.path("token/login");
+            final Invocation.Builder requestBuilder = getRequestBuilder(target);
+
+            final RequestConfig basicCredsConfig = new BasicAuthRequestConfig(username, password);
+            final Map<String,String> basicAuthHeaders = basicCredsConfig.getHeaders();
+            basicAuthHeaders.entrySet().stream().forEach(e -> requestBuilder.header(e.getKey(), e.getValue()));
+
+            return requestBuilder.post(Entity.json(null), String.class);
+        });
+    }
+
+    @Override
+    public String getTokenFromKerberosTicket() throws NiFiRegistryException, IOException {
+        return executeAction("Error performing kerberos login", () -> {
+            final WebTarget target = accessTarget.path("token/kerberos");
+            return getRequestBuilder(target).post(Entity.json(null), String.class);
+        });
+    }
+
+    @Override
+    public void logout(final String token) throws IOException, NiFiRegistryException {
+        if (StringUtils.isBlank(token)) {
+            throw new IllegalArgumentException("Token is required");
+        }
+
+        executeAction("Error performing logout", () -> {
+            final WebTarget target = accessTarget.path("logout");
+            final Invocation.Builder requestBuilder = getRequestBuilder(target);
+
+            final RequestConfig tokenConfig = new BearerTokenRequestConfig(token);
+            final Map<String,String> bearerHeaders = tokenConfig.getHeaders();
+            bearerHeaders.entrySet().stream().forEach(e -> requestBuilder.header(e.getKey(), e.getValue()));
+
+            requestBuilder.delete();
+            return null;
+        });
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBucketClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBucketClient.java
index 8c02e3a..6d35998 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBucketClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBucketClient.java
@@ -20,6 +20,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.bucket.Bucket;
 import org.apache.nifi.registry.client.BucketClient;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.field.Fields;
 import org.apache.nifi.registry.revision.entity.RevisionInfo;
 
@@ -30,7 +31,6 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Jersey implementation of BucketClient.
@@ -41,11 +41,11 @@ public class JerseyBucketClient extends AbstractJerseyClient implements BucketCl
 
 
     public JerseyBucketClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyBucketClient(final WebTarget baseTarget, final Map<String,String> headers) {
-        super(headers);
+    public JerseyBucketClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.bucketsTarget = baseTarget.path("/buckets");
     }
 
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBundleClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBundleClient.java
index 5425531..a3ce5aa 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBundleClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBundleClient.java
@@ -19,6 +19,7 @@ package org.apache.nifi.registry.client.impl;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.client.BundleClient;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.extension.bundle.Bundle;
 import org.apache.nifi.registry.extension.bundle.BundleFilterParams;
 
@@ -27,7 +28,6 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Jersey implementation of BundleClient.
@@ -38,11 +38,11 @@ public class JerseyBundleClient extends AbstractJerseyClient implements BundleCl
     private final WebTarget extensionBundlesTarget;
 
     public JerseyBundleClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyBundleClient(final WebTarget baseTarget, final Map<String, String> headers) {
-        super(headers);
+    public JerseyBundleClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.bucketExtensionBundlesTarget = baseTarget.path("buckets/{bucketId}/bundles");
         this.extensionBundlesTarget = baseTarget.path("bundles");
     }
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBundleVersionClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBundleVersionClient.java
index e9867ba..27775d0 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBundleVersionClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBundleVersionClient.java
@@ -19,12 +19,13 @@ package org.apache.nifi.registry.client.impl;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.client.BundleVersionClient;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.extension.bundle.BundleType;
 import org.apache.nifi.registry.extension.bundle.BundleVersion;
 import org.apache.nifi.registry.extension.bundle.BundleVersionFilterParams;
 import org.apache.nifi.registry.extension.bundle.BundleVersionMetadata;
-import org.apache.nifi.registry.extension.component.manifest.Extension;
 import org.apache.nifi.registry.extension.component.ExtensionMetadata;
+import org.apache.nifi.registry.extension.component.manifest.Extension;
 import org.glassfish.jersey.media.multipart.FormDataMultiPart;
 import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
 import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart;
@@ -39,7 +40,6 @@ import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Jersey implementation of BundleVersionClient.
@@ -50,11 +50,11 @@ public class JerseyBundleVersionClient extends AbstractJerseyClient implements B
     private final WebTarget extensionBundlesTarget;
 
     public JerseyBundleVersionClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyBundleVersionClient(final WebTarget baseTarget, final Map<String, String> headers) {
-        super(headers);
+    public JerseyBundleVersionClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.bucketExtensionBundlesTarget = baseTarget.path("buckets/{bucketId}/bundles");
         this.extensionBundlesTarget = baseTarget.path("bundles");
     }
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyExtensionClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyExtensionClient.java
index eb8082b..bbd440e 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyExtensionClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyExtensionClient.java
@@ -19,6 +19,7 @@ package org.apache.nifi.registry.client.impl;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.client.ExtensionClient;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.extension.bundle.BundleType;
 import org.apache.nifi.registry.extension.component.ExtensionFilterParams;
 import org.apache.nifi.registry.extension.component.ExtensionMetadataContainer;
@@ -31,7 +32,6 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 public class JerseyExtensionClient extends AbstractJerseyClient implements ExtensionClient {
@@ -39,11 +39,11 @@ public class JerseyExtensionClient extends AbstractJerseyClient implements Exten
     private final WebTarget extensionsTarget;
 
     public JerseyExtensionClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyExtensionClient(final WebTarget baseTarget, final Map<String, String> headers) {
-        super(headers);
+    public JerseyExtensionClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.extensionsTarget = baseTarget.path("extensions");
     }
 
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyExtensionRepoClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyExtensionRepoClient.java
index f4ad5d5..3a0daf5 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyExtensionRepoClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyExtensionRepoClient.java
@@ -19,6 +19,7 @@ package org.apache.nifi.registry.client.impl;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.client.ExtensionRepoClient;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.extension.component.manifest.Extension;
 import org.apache.nifi.registry.extension.repo.ExtensionRepoArtifact;
 import org.apache.nifi.registry.extension.repo.ExtensionRepoBucket;
@@ -37,7 +38,6 @@ import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 
 public class JerseyExtensionRepoClient extends AbstractJerseyClient implements ExtensionRepoClient {
@@ -45,11 +45,11 @@ public class JerseyExtensionRepoClient extends AbstractJerseyClient implements E
     private WebTarget extensionRepoTarget;
 
     public JerseyExtensionRepoClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyExtensionRepoClient(final WebTarget baseTarget, final Map<String, String> headers) {
-        super(headers);
+    public JerseyExtensionRepoClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.extensionRepoTarget = baseTarget.path("extension-repository");
     }
 
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
index d17e27a..4a61a30 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
@@ -19,6 +19,7 @@ package org.apache.nifi.registry.client.impl;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.client.FlowClient;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.diff.VersionedFlowDifference;
 import org.apache.nifi.registry.field.Fields;
 import org.apache.nifi.registry.flow.VersionedFlow;
@@ -31,7 +32,6 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Jersey implementation of FlowClient.
@@ -42,11 +42,11 @@ public class JerseyFlowClient extends AbstractJerseyClient  implements FlowClien
     private final WebTarget bucketFlowsTarget;
 
     public JerseyFlowClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyFlowClient(final WebTarget baseTarget, final Map<String,String> headers) {
-        super(headers);
+    public JerseyFlowClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.flowsTarget = baseTarget.path("/flows");
         this.bucketFlowsTarget = baseTarget.path("/buckets/{bucketId}/flows");
     }
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
index befe389..19890ca 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
@@ -19,6 +19,7 @@ package org.apache.nifi.registry.client.impl;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.client.FlowSnapshotClient;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
 
@@ -29,7 +30,6 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Jersey implementation of FlowSnapshotClient.
@@ -40,11 +40,11 @@ public class JerseyFlowSnapshotClient extends AbstractJerseyClient implements Fl
     final WebTarget flowsFlowSnapshotTarget;
 
     public JerseyFlowSnapshotClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyFlowSnapshotClient(final WebTarget baseTarget, final Map<String,String> headers) {
-        super(headers);
+    public JerseyFlowSnapshotClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.bucketFlowSnapshotTarget = baseTarget.path("/buckets/{bucketId}/flows/{flowId}/versions");
         this.flowsFlowSnapshotTarget = baseTarget.path("/flows/{flowId}/versions");
     }
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyItemsClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyItemsClient.java
index 6b01fc4..85c965f 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyItemsClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyItemsClient.java
@@ -20,6 +20,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.bucket.BucketItem;
 import org.apache.nifi.registry.client.ItemsClient;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.field.Fields;
 
 import javax.ws.rs.client.WebTarget;
@@ -27,7 +28,6 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Jersey implementation of ItemsClient.
@@ -37,11 +37,11 @@ public class JerseyItemsClient extends AbstractJerseyClient implements ItemsClie
     private final WebTarget itemsTarget;
 
     public JerseyItemsClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyItemsClient(final WebTarget baseTarget, final Map<String,String> headers) {
-        super(headers);
+    public JerseyItemsClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.itemsTarget = baseTarget.path("/items");
     }
 
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyNiFiRegistryClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyNiFiRegistryClient.java
index f876277..3e64bad 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyNiFiRegistryClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyNiFiRegistryClient.java
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.client.AccessClient;
 import org.apache.nifi.registry.client.BucketClient;
 import org.apache.nifi.registry.client.BundleClient;
 import org.apache.nifi.registry.client.BundleVersionClient;
@@ -34,9 +35,10 @@ import org.apache.nifi.registry.client.ItemsClient;
 import org.apache.nifi.registry.client.NiFiRegistryClient;
 import org.apache.nifi.registry.client.NiFiRegistryClientConfig;
 import org.apache.nifi.registry.client.PoliciesClient;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.client.TenantsClient;
 import org.apache.nifi.registry.client.UserClient;
-import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils;
+import org.apache.nifi.registry.client.impl.request.ProxiedEntityRequestConfig;
 import org.glassfish.jersey.client.ClientConfig;
 import org.glassfish.jersey.client.ClientProperties;
 import org.glassfish.jersey.client.RequestEntityProcessing;
@@ -50,11 +52,6 @@ import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.client.WebTarget;
 import java.io.IOException;
 import java.net.URI;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * A NiFiRegistryClient that uses Jersey Client.
@@ -136,42 +133,62 @@ public class JerseyNiFiRegistryClient implements NiFiRegistryClient {
     }
 
     @Override
+    public BucketClient getBucketClient(String... proxiedEntity) {
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyBucketClient(baseTarget, requestConfig);
+    }
+
+    @Override
+    public BucketClient getBucketClient(RequestConfig requestConfig) {
+        return new JerseyBucketClient(baseTarget, requestConfig);
+    }
+
+    @Override
     public FlowClient getFlowClient() {
         return this.flowClient;
     }
 
     @Override
+    public FlowClient getFlowClient(String... proxiedEntity) {
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyFlowClient(baseTarget, requestConfig);
+    }
+
+    @Override
+    public FlowClient getFlowClient(RequestConfig requestConfig) {
+        return new JerseyFlowClient(baseTarget, requestConfig);
+    }
+
+    @Override
     public FlowSnapshotClient getFlowSnapshotClient() {
         return this.flowSnapshotClient;
     }
 
     @Override
-    public ItemsClient getItemsClient() {
-        return this.itemsClient;
+    public FlowSnapshotClient getFlowSnapshotClient(String... proxiedEntity) {
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyFlowSnapshotClient(baseTarget, requestConfig);
     }
 
     @Override
-    public BucketClient getBucketClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyBucketClient(baseTarget, headers);
+    public FlowSnapshotClient getFlowSnapshotClient(RequestConfig requestConfig) {
+        return new JerseyFlowSnapshotClient(baseTarget, requestConfig);
     }
 
     @Override
-    public FlowClient getFlowClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyFlowClient(baseTarget, headers);
+    public ItemsClient getItemsClient() {
+        return this.itemsClient;
     }
 
     @Override
-    public FlowSnapshotClient getFlowSnapshotClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyFlowSnapshotClient(baseTarget, headers);
+    public ItemsClient getItemsClient(String... proxiedEntity) {
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyItemsClient(baseTarget, requestConfig);
     }
 
     @Override
-    public ItemsClient getItemsClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyItemsClient(baseTarget, headers);
+    public ItemsClient getItemsClient(RequestConfig requestConfig) {
+        return new JerseyItemsClient(baseTarget, requestConfig);
     }
 
     @Override
@@ -181,8 +198,13 @@ public class JerseyNiFiRegistryClient implements NiFiRegistryClient {
 
     @Override
     public UserClient getUserClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyUserClient(baseTarget, headers);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyUserClient(baseTarget, requestConfig);
+    }
+
+    @Override
+    public UserClient getUserClient(RequestConfig requestConfig) {
+        return new JerseyUserClient(baseTarget, requestConfig);
     }
 
     @Override
@@ -192,8 +214,13 @@ public class JerseyNiFiRegistryClient implements NiFiRegistryClient {
 
     @Override
     public BundleClient getBundleClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyBundleClient(baseTarget, headers);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyBundleClient(baseTarget, requestConfig);
+    }
+
+    @Override
+    public BundleClient getBundleClient(RequestConfig requestConfig) {
+        return new JerseyBundleClient(baseTarget, requestConfig);
     }
 
     @Override
@@ -203,8 +230,13 @@ public class JerseyNiFiRegistryClient implements NiFiRegistryClient {
 
     @Override
     public BundleVersionClient getBundleVersionClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyBundleVersionClient(baseTarget, headers);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyBundleVersionClient(baseTarget, requestConfig);
+    }
+
+    @Override
+    public BundleVersionClient getBundleVersionClient(RequestConfig requestConfig) {
+        return new JerseyBundleVersionClient(baseTarget, requestConfig);
     }
 
     @Override
@@ -213,20 +245,30 @@ public class JerseyNiFiRegistryClient implements NiFiRegistryClient {
     }
 
     @Override
+    public ExtensionRepoClient getExtensionRepoClient(String... proxiedEntity) {
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyExtensionRepoClient(baseTarget, requestConfig);
+    }
+
+    @Override
+    public ExtensionRepoClient getExtensionRepoClient(RequestConfig requestConfig) {
+        return new JerseyExtensionRepoClient(baseTarget, requestConfig);
+    }
+
+    @Override
     public ExtensionClient getExtensionClient() {
         return new JerseyExtensionClient(baseTarget);
     }
 
     @Override
     public ExtensionClient getExtensionClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyExtensionClient(baseTarget, headers);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyExtensionClient(baseTarget, requestConfig);
     }
 
     @Override
-    public ExtensionRepoClient getExtensionRepoClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyExtensionRepoClient(baseTarget, headers);
+    public ExtensionClient getExtensionClient(RequestConfig requestConfig) {
+        return new JerseyExtensionClient(baseTarget, requestConfig);
     }
 
     @Override
@@ -236,8 +278,13 @@ public class JerseyNiFiRegistryClient implements NiFiRegistryClient {
 
     @Override
     public TenantsClient getTenantsClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyTenantsClient(baseTarget, headers);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyTenantsClient(baseTarget, requestConfig);
+    }
+
+    @Override
+    public TenantsClient getTenantsClient(RequestConfig requestConfig) {
+        return new JerseyTenantsClient(baseTarget, requestConfig);
     }
 
     @Override
@@ -247,27 +294,18 @@ public class JerseyNiFiRegistryClient implements NiFiRegistryClient {
 
     @Override
     public PoliciesClient getPoliciesClient(String... proxiedEntity) {
-        final Map<String,String> headers = getHeaders(proxiedEntity);
-        return new JerseyPoliciesClient(baseTarget, headers);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        return new JerseyPoliciesClient(baseTarget, requestConfig);
     }
 
-    private Map<String,String> getHeaders(String[] proxiedEntities) {
-        final String proxiedEntitiesValue = getProxiedEntitesValue(proxiedEntities);
-
-        final Map<String,String> headers = new HashMap<>();
-        if (proxiedEntitiesValue != null) {
-            headers.put(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxiedEntitiesValue);
-        }
-        return headers;
+    @Override
+    public PoliciesClient getPoliciesClient(RequestConfig requestConfig) {
+        return new JerseyPoliciesClient(baseTarget, requestConfig);
     }
 
-    private String getProxiedEntitesValue(String[] proxiedEntities) {
-        if (proxiedEntities == null) {
-            return null;
-        }
-
-        final List<String> proxiedEntityChain = Arrays.stream(proxiedEntities).map(ProxiedEntitiesUtils::formatProxyDn).collect(Collectors.toList());
-        return StringUtils.join(proxiedEntityChain, "");
+    @Override
+    public AccessClient getAccessClient() {
+        return new JerseyAccessClient(baseTarget);
     }
 
     @Override
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyPoliciesClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyPoliciesClient.java
index 15577f6..ae9e8dd 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyPoliciesClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyPoliciesClient.java
@@ -20,11 +20,10 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.authorization.AccessPolicy;
 import org.apache.nifi.registry.client.NiFiRegistryException;
 import org.apache.nifi.registry.client.PoliciesClient;
+import org.apache.nifi.registry.client.RequestConfig;
 
 import javax.ws.rs.client.WebTarget;
 import java.io.IOException;
-import java.util.Collections;
-import java.util.Map;
 
 public class JerseyPoliciesClient extends AbstractCRUDJerseyClient implements PoliciesClient {
 
@@ -32,11 +31,11 @@ public class JerseyPoliciesClient extends AbstractCRUDJerseyClient implements Po
     public static final String POLICIES_PATH = "policies";
 
     public JerseyPoliciesClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyPoliciesClient(final WebTarget baseTarget, final Map<String, String> headers) {
-        super(baseTarget, headers);
+    public JerseyPoliciesClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(baseTarget, requestConfig);
     }
 
     @Override
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyTenantsClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyTenantsClient.java
index 78867c7..3f95357 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyTenantsClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyTenantsClient.java
@@ -19,15 +19,14 @@ package org.apache.nifi.registry.client.impl;
 import org.apache.nifi.registry.authorization.User;
 import org.apache.nifi.registry.authorization.UserGroup;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.client.TenantsClient;
 import org.apache.nifi.registry.revision.entity.RevisionInfo;
 
 import javax.ws.rs.client.WebTarget;
 import java.io.IOException;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 public class JerseyTenantsClient extends AbstractCRUDJerseyClient implements TenantsClient {
     public static final String USER = "User";
@@ -37,11 +36,11 @@ public class JerseyTenantsClient extends AbstractCRUDJerseyClient implements Ten
     public static final String USER_GROUPS_PATH = "user-groups";
 
     public JerseyTenantsClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyTenantsClient(final WebTarget baseTarget, final Map<String, String> headers) {
-        super(baseTarget.path("/tenants"), headers);
+    public JerseyTenantsClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(baseTarget.path("/tenants"), requestConfig);
     }
 
     @Override
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyUserClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyUserClient.java
index 7625f35..484c5cb 100644
--- a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyUserClient.java
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyUserClient.java
@@ -16,25 +16,24 @@
  */
 package org.apache.nifi.registry.client.impl;
 
+import org.apache.nifi.registry.authorization.CurrentUser;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.client.UserClient;
-import org.apache.nifi.registry.authorization.CurrentUser;
 
 import javax.ws.rs.client.WebTarget;
 import java.io.IOException;
-import java.util.Collections;
-import java.util.Map;
 
 public class JerseyUserClient extends AbstractJerseyClient implements UserClient {
 
     private final WebTarget accessTarget;
 
     public JerseyUserClient(final WebTarget baseTarget) {
-        this(baseTarget, Collections.emptyMap());
+        this(baseTarget, null);
     }
 
-    public JerseyUserClient(final WebTarget baseTarget, final Map<String,String> headers) {
-        super(headers);
+    public JerseyUserClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+        super(requestConfig);
         this.accessTarget = baseTarget.path("/access");
     }
 
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/BasicAuthRequestConfig.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/BasicAuthRequestConfig.java
new file mode 100644
index 0000000..4105b4f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/BasicAuthRequestConfig.java
@@ -0,0 +1,55 @@
+/*
+ * 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.nifi.registry.client.impl.request;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.registry.client.RequestConfig;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of RequestConfig for a request with basic auth.
+ */
+public class BasicAuthRequestConfig implements RequestConfig {
+
+    public static final String AUTHORIZATION_HEADER = "Authorization";
+    public static final String BASIC = "Basic";
+
+    private final String username;
+    private final String password;
+
+    public BasicAuthRequestConfig(final String username, final String password) {
+        this.username = Validate.notBlank(username);
+        this.password = Validate.notBlank(password);
+    }
+
+    @Override
+    public Map<String, String> getHeaders() {
+        final String basicCreds = username + ":" + password;
+        final byte[] basicCredsBytes = basicCreds.getBytes(StandardCharsets.UTF_8);
+
+        final Base64.Encoder encoder = Base64.getEncoder();
+        final String encodedBasicCreds = encoder.encodeToString(basicCredsBytes);
+
+        final Map<String,String> headers = new HashMap<>();
+        headers.put(AUTHORIZATION_HEADER, BASIC + " " + encodedBasicCreds);
+        return headers;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/BearerTokenRequestConfig.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/BearerTokenRequestConfig.java
new file mode 100644
index 0000000..9daf77b
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/BearerTokenRequestConfig.java
@@ -0,0 +1,45 @@
+/*
+ * 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.nifi.registry.client.impl.request;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.registry.client.RequestConfig;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of RequestConfig for a request with a bearer token.
+ */
+public class BearerTokenRequestConfig implements RequestConfig {
+
+    public static final String AUTHORIZATION_HEADER = "Authorization";
+    public static final String BEARER = "Bearer";
+
+    private final String token;
+
+    public BearerTokenRequestConfig(final String token) {
+        this.token = Validate.notBlank(token);
+    }
+
+    @Override
+    public Map<String, String> getHeaders() {
+        final Map<String,String> headers = new HashMap<>();
+        headers.put(AUTHORIZATION_HEADER, BEARER + " " + token);
+        return headers;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/ProxiedEntityRequestConfig.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/ProxiedEntityRequestConfig.java
new file mode 100644
index 0000000..3a89898
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/request/ProxiedEntityRequestConfig.java
@@ -0,0 +1,62 @@
+/*
+ * 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.nifi.registry.client.impl.request;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.registry.client.RequestConfig;
+import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Implementation of RequestConfig that produces headers for a request with proxied-entities.
+ */
+public class ProxiedEntityRequestConfig implements RequestConfig {
+
+    private final String[] proxiedEntities;
+
+    public ProxiedEntityRequestConfig(final String... proxiedEntities) {
+        this.proxiedEntities = Validate.notNull(proxiedEntities);
+    }
+
+    @Override
+    public Map<String, String> getHeaders() {
+        final String proxiedEntitiesValue = getProxiedEntitesValue(proxiedEntities);
+
+        final Map<String,String> headers = new HashMap<>();
+        if (proxiedEntitiesValue != null) {
+            headers.put(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxiedEntitiesValue);
+        }
+        return headers;
+    }
+
+    private String getProxiedEntitesValue(final String[] proxiedEntities) {
+        if (proxiedEntities == null) {
+            return null;
+        }
+
+        final List<String> proxiedEntityChain = Arrays.stream(proxiedEntities)
+                .map(ProxiedEntitiesUtils::formatProxyDn).collect(Collectors.toList());
+        return StringUtils.join(proxiedEntityChain, "");
+    }
+
+}
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestBasicAuthRequestConfig.java b/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestBasicAuthRequestConfig.java
new file mode 100644
index 0000000..91c22cf
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestBasicAuthRequestConfig.java
@@ -0,0 +1,48 @@
+/*
+ * 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.nifi.registry.client.impl.request;
+
+import org.apache.nifi.registry.client.RequestConfig;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+
+public class TestBasicAuthRequestConfig {
+
+    @Test
+    public void testBasicAuthRequestConfig() {
+        final String username = "user1";
+        final String password = "password";
+        final String basicCreds = username + ":" + password;
+
+        final String expectedHeaderValue = "Basic " + Base64.getEncoder().encodeToString(basicCreds.getBytes(StandardCharsets.UTF_8));
+
+        final RequestConfig requestConfig = new BasicAuthRequestConfig(username, password);
+
+        final Map<String,String> headers = requestConfig.getHeaders();
+        assertNotNull(headers);
+        assertEquals(1, headers.size());
+
+        final String authorizationHeaderValue = headers.get("Authorization");
+        assertEquals(expectedHeaderValue, authorizationHeaderValue);
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestBearerTokenRequestConfig.java b/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestBearerTokenRequestConfig.java
new file mode 100644
index 0000000..eeaacd3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestBearerTokenRequestConfig.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.nifi.registry.client.impl.request;
+
+import org.apache.nifi.registry.client.RequestConfig;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class TestBearerTokenRequestConfig {
+
+    @Test
+    public void testBearerTokenRequestConfig() {
+        final String token = "some-token";
+        final String expectedHeaderValue = "Bearer " + token;
+
+        final RequestConfig requestConfig = new BearerTokenRequestConfig(token);
+
+        final Map<String,String> headers = requestConfig.getHeaders();
+        assertNotNull(headers);
+        assertEquals(1, headers.size());
+
+        final String authorizationHeaderValue = headers.get("Authorization");
+        assertEquals(expectedHeaderValue, authorizationHeaderValue);
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestProxiedEntityRequestConfig.java b/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestProxiedEntityRequestConfig.java
new file mode 100644
index 0000000..e5427ac
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/test/java/org/apache/nifi/registry/client/impl/request/TestProxiedEntityRequestConfig.java
@@ -0,0 +1,62 @@
+/*
+ * 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.nifi.registry.client.impl.request;
+
+import org.apache.nifi.registry.client.RequestConfig;
+import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class TestProxiedEntityRequestConfig {
+
+    @Test
+    public void testSingleProxiedEntity() {
+        final String proxiedEntity = "user1";
+        final String expectedProxiedEntitiesChain = "<user1>";
+
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+
+        final Map<String,String> headers = requestConfig.getHeaders();
+        assertNotNull(headers);
+        assertEquals(1, headers.size());
+
+        final String proxiedEntitiesChainHeaderValue = headers.get(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN);
+        assertEquals(expectedProxiedEntitiesChain, proxiedEntitiesChainHeaderValue);
+    }
+
+    @Test
+    public void testMultipleProxiedEntity() {
+        final String proxiedEntity1 = "user1";
+        final String proxiedEntity2 = "user2";
+        final String proxiedEntity3 = "user3";
+        final String expectedProxiedEntitiesChain = "<user1><user2><user3>";
+
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(
+                proxiedEntity1, proxiedEntity2, proxiedEntity3);
+
+        final Map<String,String> headers = requestConfig.getHeaders();
+        assertNotNull(headers);
+        assertEquals(1, headers.size());
+
+        final String proxiedEntitiesChainHeaderValue = headers.get(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN);
+        assertEquals(expectedProxiedEntitiesChain, proxiedEntitiesChainHeaderValue);
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CertificateUtils.java b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CertificateUtils.java
index 5fd6d07..30e77db 100644
--- a/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CertificateUtils.java
+++ b/nifi-registry-core/nifi-registry-security-utils/src/main/java/org/apache/nifi/registry/security/util/CertificateUtils.java
@@ -17,8 +17,6 @@
 package org.apache.nifi.registry.security.util;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.registry.security.util.KeyStoreUtils;
-import org.apache.nifi.registry.security.util.KeystoreType;
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Set;
@@ -41,6 +39,7 @@ import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cert.X509v3CertificateBuilder;
 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
 import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.crypto.tls.TlsException;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.operator.ContentSigner;
 import org.bouncycastle.operator.OperatorCreationException;
@@ -52,16 +51,13 @@ import org.slf4j.LoggerFactory;
 import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
+import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSocket;
-import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.math.BigInteger;
 import java.net.Socket;
-import java.net.URL;
 import java.security.KeyPair;
-import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.Security;
@@ -80,12 +76,19 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public final class CertificateUtils {
     private static final Logger logger = LoggerFactory.getLogger(CertificateUtils.class);
     private static final String PEER_NOT_AUTHENTICATED_MSG = "peer not authenticated";
     private static final Map<ASN1ObjectIdentifier, Integer> dnOrderMap = createDnOrderMap();
 
+    public static final String JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION = "TLSv1.2";
+    public static final String JAVA_11_MAX_SUPPORTED_TLS_PROTOCOL_VERSION = "TLSv1.3";
+    public static final String[] JAVA_8_SUPPORTED_TLS_PROTOCOL_VERSIONS = new String[]{JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION};
+    public static final String[] JAVA_11_SUPPORTED_TLS_PROTOCOL_VERSIONS = new String[]{JAVA_11_MAX_SUPPORTED_TLS_PROTOCOL_VERSION, JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION};
+
     static {
         Security.addProvider(new BouncyCastleProvider());
     }
@@ -120,67 +123,6 @@ public final class CertificateUtils {
         return Collections.unmodifiableMap(orderMap);
     }
 
-    public enum ClientAuth {
-        NONE(0, "none"),
-        WANT(1, "want"),
-        NEED(2, "need");
-
-        private int value;
-        private String description;
-
-        ClientAuth(int value, String description) {
-            this.value = value;
-            this.description = description;
-        }
-
-        @Override
-        public String toString() {
-            return "Client Auth: " + this.description + " (" + this.value + ")";
-        }
-    }
-
-    /**
-     * Returns true if the given keystore can be loaded using the given keystore type and password. Returns false otherwise.
-     *
-     * @param keystore     the keystore to validate
-     * @param keystoreType the type of the keystore
-     * @param password     the password to access the keystore
-     * @return true if valid; false otherwise
-     */
-    public static boolean isStoreValid(final URL keystore, final KeystoreType keystoreType, final char[] password) {
-
-        if (keystore == null) {
-            throw new IllegalArgumentException("keystore may not be null");
-        } else if (keystoreType == null) {
-            throw new IllegalArgumentException("keystore type may not be null");
-        } else if (password == null) {
-            throw new IllegalArgumentException("password may not be null");
-        }
-
-        BufferedInputStream bis = null;
-        final KeyStore ks;
-        try {
-
-            // load the keystore
-            bis = new BufferedInputStream(keystore.openStream());
-            ks = KeyStoreUtils.getKeyStore(keystoreType.name());
-            ks.load(bis, password);
-
-            return true;
-
-        } catch (Exception e) {
-            return false;
-        } finally {
-            if (bis != null) {
-                try {
-                    bis.close();
-                } catch (final IOException ioe) {
-                    logger.warn("Failed to close input stream", ioe);
-                }
-            }
-        }
-    }
-
     /**
      * Extracts the username from the specified DN. If the username cannot be extracted because the CN is in an unrecognized format, the entire CN is returned. If the CN cannot be extracted because
      * the DN is in an unrecognized format, the entire DN is returned.
@@ -245,7 +187,7 @@ public final class CertificateUtils {
 
     /**
      * Returns the DN extracted from the peer certificate (the server DN if run on the client; the client DN (if available) if run on the server).
-     *
+     * <p>
      * If the client auth setting is WANT or NONE and a client certificate is not present, this method will return {@code null}.
      * If the client auth is NEED, it will throw a {@link CertificateException}.
      *
@@ -260,15 +202,15 @@ public final class CertificateUtils {
 
             boolean clientMode = sslSocket.getUseClientMode();
             logger.debug("SSL Socket in {} mode", clientMode ? "client" : "server");
-            ClientAuth clientAuth = getClientAuthStatus(sslSocket);
+            SslContextFactory.ClientAuth clientAuth = getClientAuthStatus(sslSocket);
             logger.debug("SSL Socket client auth status: {}", clientAuth);
 
             if (clientMode) {
                 logger.debug("This socket is in client mode, so attempting to extract certificate from remote 'server' socket");
-               dn = extractPeerDNFromServerSSLSocket(sslSocket);
+                dn = extractPeerDNFromServerSSLSocket(sslSocket);
             } else {
                 logger.debug("This socket is in server mode, so attempting to extract certificate from remote 'client' socket");
-               dn = extractPeerDNFromClientSSLSocket(sslSocket);
+                dn = extractPeerDNFromClientSSLSocket(sslSocket);
             }
         }
 
@@ -277,7 +219,7 @@ public final class CertificateUtils {
 
     /**
      * Returns the DN extracted from the client certificate.
-     *
+     * <p>
      * If the client auth setting is WANT or NONE and a certificate is not present (and {@code respectClientAuth} is {@code true}), this method will return {@code null}.
      * If the client auth is NEED, it will throw a {@link CertificateException}.
      *
@@ -288,34 +230,34 @@ public final class CertificateUtils {
     private static String extractPeerDNFromClientSSLSocket(SSLSocket sslSocket) throws CertificateException {
         String dn = null;
 
-            /** The clientAuth value can be "need", "want", or "none"
-             * A client must send client certificates for need, should for want, and will not for none.
-             * This method should throw an exception if none are provided for need, return null if none are provided for want, and return null (without checking) for none.
-             */
-
-            ClientAuth clientAuth = getClientAuthStatus(sslSocket);
-            logger.debug("SSL Socket client auth status: {}", clientAuth);
-
-            if (clientAuth != ClientAuth.NONE) {
-                try {
-                    final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
-                    if (certChains != null && certChains.length > 0) {
-                        X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
-                        dn = x509Certificate.getSubjectDN().getName().trim();
-                        logger.debug("Extracted DN={} from client certificate", dn);
-                    }
-                } catch (SSLPeerUnverifiedException e) {
-                    if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
-                        logger.error("The incoming request did not contain client certificates and thus the DN cannot" +
-                                " be extracted. Check that the other endpoint is providing a complete client certificate chain");
-                    }
-                    if (clientAuth == ClientAuth.WANT) {
-                        logger.warn("Suppressing missing client certificate exception because client auth is set to 'want'");
-                        return dn;
-                    }
-                    throw new CertificateException(e);
+        /** The clientAuth value can be "need", "want", or "none"
+         * A client must send client certificates for need, should for want, and will not for none.
+         * This method should throw an exception if none are provided for need, return null if none are provided for want, and return null (without checking) for none.
+         */
+
+        SslContextFactory.ClientAuth clientAuth = getClientAuthStatus(sslSocket);
+        logger.debug("SSL Socket client auth status: {}", clientAuth);
+
+        if (clientAuth != SslContextFactory.ClientAuth.NONE) {
+            try {
+                final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
+                if (certChains != null && certChains.length > 0) {
+                    X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
+                    dn = x509Certificate.getSubjectDN().getName().trim();
+                    logger.debug("Extracted DN={} from client certificate", dn);
+                }
+            } catch (SSLPeerUnverifiedException e) {
+                if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
+                    logger.error("The incoming request did not contain client certificates and thus the DN cannot" +
+                            " be extracted. Check that the other endpoint is providing a complete client certificate chain");
+                }
+                if (clientAuth == SslContextFactory.ClientAuth.WANT) {
+                    logger.warn("Suppressing missing client certificate exception because client auth is set to 'want'");
+                    return dn;
                 }
+                throw new CertificateException(e);
             }
+        }
         return dn;
     }
 
@@ -330,26 +272,26 @@ public final class CertificateUtils {
         String dn = null;
         if (socket instanceof SSLSocket) {
             final SSLSocket sslSocket = (SSLSocket) socket;
-                try {
-                    final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
-                    if (certChains != null && certChains.length > 0) {
-                        X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
-                        dn = x509Certificate.getSubjectDN().getName().trim();
-                        logger.debug("Extracted DN={} from server certificate", dn);
-                    }
-                } catch (SSLPeerUnverifiedException e) {
-                    if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
-                        logger.error("The server did not present a certificate and thus the DN cannot" +
-                                " be extracted. Check that the other endpoint is providing a complete certificate chain");
-                    }
-                    throw new CertificateException(e);
+            try {
+                final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
+                if (certChains != null && certChains.length > 0) {
+                    X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
+                    dn = x509Certificate.getSubjectDN().getName().trim();
+                    logger.debug("Extracted DN={} from server certificate", dn);
+                }
+            } catch (SSLPeerUnverifiedException e) {
+                if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
+                    logger.error("The server did not present a certificate and thus the DN cannot" +
+                            " be extracted. Check that the other endpoint is providing a complete certificate chain");
                 }
+                throw new CertificateException(e);
+            }
         }
         return dn;
     }
 
-    private static ClientAuth getClientAuthStatus(SSLSocket sslSocket) {
-        return sslSocket.getNeedClientAuth() ? ClientAuth.NEED : sslSocket.getWantClientAuth() ? ClientAuth.WANT : ClientAuth.NONE;
+    private static SslContextFactory.ClientAuth getClientAuthStatus(SSLSocket sslSocket) {
+        return sslSocket.getNeedClientAuth() ? SslContextFactory.ClientAuth.REQUIRED : sslSocket.getWantClientAuth() ? SslContextFactory.ClientAuth.WANT : SslContextFactory.ClientAuth.NONE;
     }
 
     /**
@@ -360,6 +302,7 @@ public final class CertificateUtils {
      * @return a new {@code java.security.cert.X509Certificate}
      * @throws CertificateException if there is an error generating the new certificate
      */
+    @SuppressWarnings("deprecation")
     public static X509Certificate convertLegacyX509Certificate(javax.security.cert.X509Certificate legacyCertificate) throws CertificateException {
         if (legacyCertificate == null) {
             throw new IllegalArgumentException("The X.509 certificate cannot be null");
@@ -373,14 +316,14 @@ public final class CertificateUtils {
     }
 
     /**
-     * Accepts an abstract {@link Certificate} and returns an {@link X509Certificate}. Because {@code sslSocket.getSession().getPeerCertificates()} returns an array of the
+     * Accepts an abstract {@link java.security.cert.Certificate} and returns an {@link X509Certificate}. Because {@code sslSocket.getSession().getPeerCertificates()} returns an array of the
      * abstract certificates, they must be translated to X.509 to replace the functionality of {@code sslSocket.getSession().getPeerCertificateChain()}.
      *
      * @param abstractCertificate the {@code java.security.cert.Certificate}
      * @return a new {@code java.security.cert.X509Certificate}
      * @throws CertificateException if there is an error generating the new certificate
      */
-    public static X509Certificate convertAbstractX509Certificate(Certificate abstractCertificate) throws CertificateException {
+    public static X509Certificate convertAbstractX509Certificate(java.security.cert.Certificate abstractCertificate) throws CertificateException {
         if (abstractCertificate == null || !(abstractCertificate instanceof X509Certificate)) {
             throw new IllegalArgumentException("The certificate cannot be null and must be an X.509 certificate");
         }
@@ -405,9 +348,9 @@ public final class CertificateUtils {
 
     /**
      * Reorders DN to the order the elements appear in the RFC 2253 table
-     *
+     * <p>
      * https://www.ietf.org/rfc/rfc2253.txt
-     *
+     * <p>
      * String  X.500 AttributeType
      * ------------------------------
      * CN      commonName
@@ -456,7 +399,7 @@ public final class CertificateUtils {
 
     /**
      * Reverses the X500Name in order make the certificate be in the right order
-     * [see https://stackoverflow.com/questions/7567837/attributes-reversed-in-certificate-subject-and-issuer/12645265]
+     * [see http://stackoverflow.com/questions/7567837/attributes-reversed-in-certificate-subject-and-issuer/12645265]
      *
      * @param x500Name the X500Name created with the intended order
      * @return the X500Name reversed
@@ -498,7 +441,7 @@ public final class CertificateUtils {
      * @param signingAlgorithm        the signing algorithm to use for the {@link X509Certificate}
      * @param certificateDurationDays the duration in days for which the {@link X509Certificate} should be valid
      * @return a self-signed {@link X509Certificate} suitable for use as a Certificate Authority
-     * @throws CertificateException      if there is an generating the new certificate
+     * @throws CertificateException if there is an generating the new certificate
      */
     public static X509Certificate generateSelfSignedX509Certificate(KeyPair keyPair, String dn, String signingAlgorithm, int certificateDurationDays)
             throws CertificateException {
@@ -540,12 +483,12 @@ public final class CertificateUtils {
     /**
      * Generates an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
      *
-     * @param dn the distinguished name to use
-     * @param publicKey the public key to issue the certificate to
-     * @param issuer the issuer's certificate
-     * @param issuerKeyPair the issuer's keypair
+     * @param dn               the distinguished name to use
+     * @param publicKey        the public key to issue the certificate to
+     * @param issuer           the issuer's certificate
+     * @param issuerKeyPair    the issuer's keypair
      * @param signingAlgorithm the signing algorithm to use
-     * @param days the number of days it should be valid for
+     * @param days             the number of days it should be valid for
      * @return an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
      * @throws CertificateException if there is an error issuing the certificate
      */
@@ -557,13 +500,13 @@ public final class CertificateUtils {
     /**
      * Generates an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
      *
-     * @param dn the distinguished name to use
-     * @param publicKey the public key to issue the certificate to
-     * @param extensions extensions extracted from the CSR
-     * @param issuer the issuer's certificate
-     * @param issuerKeyPair the issuer's keypair
+     * @param dn               the distinguished name to use
+     * @param publicKey        the public key to issue the certificate to
+     * @param extensions       extensions extracted from the CSR
+     * @param issuer           the issuer's certificate
+     * @param issuerKeyPair    the issuer's keypair
      * @param signingAlgorithm the signing algorithm to use
-     * @param days the number of days it should be valid for
+     * @param days             the number of days it should be valid for
      * @return an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
      * @throws CertificateException if there is an error issuing the certificate
      */
@@ -596,7 +539,7 @@ public final class CertificateUtils {
             certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth}));
 
             // (3) subjectAlternativeName
-            if(extensions != null && extensions.getExtension(Extension.subjectAlternativeName) != null) {
+            if (extensions != null && extensions.getExtension(Extension.subjectAlternativeName) != null) {
                 certBuilder.addExtension(Extension.subjectAlternativeName, false, extensions.getExtensionParsedValue(Extension.subjectAlternativeName));
             }
 
@@ -609,15 +552,15 @@ public final class CertificateUtils {
 
     /**
      * Returns true if the two provided DNs are equivalent, regardless of the order of the elements. Returns false if one or both are invalid DNs.
-     *
+     * <p>
      * Example:
-     *
+     * <p>
      * CN=test1, O=testOrg, C=US compared to CN=test1, O=testOrg, C=US -> true
      * CN=test1, O=testOrg, C=US compared to O=testOrg, CN=test1, C=US -> true
      * CN=test1, O=testOrg, C=US compared to CN=test2, O=testOrg, C=US -> false
      * CN=test1, O=testOrg, C=US compared to O=testOrg, CN=test2, C=US -> false
      * CN=test1, O=testOrg, C=US compared to                           -> false
-     *                           compared to                           -> true
+     * compared to                           -> true
      *
      * @param dn1 the first DN to compare
      * @param dn2 the second DN to compare
@@ -665,6 +608,86 @@ public final class CertificateUtils {
         return null;
     }
 
+    /**
+     * Returns {@code true} if this exception is due to a TLS problem (either directly or because of its cause, if present). Traverses the cause chain recursively.
+     *
+     * @param e the exception to evaluate
+     * @return true if the direct or indirect cause of this exception was TLS-related
+     */
+    public static boolean isTlsError(Throwable e) {
+        if (e == null) {
+            return false;
+        } else {
+            if (e instanceof CertificateException || e instanceof TlsException || e instanceof SSLException) {
+                return true;
+            } else if (e.getCause() != null) {
+                return isTlsError(e.getCause());
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Returns the JVM Java major version based on the System properties (e.g. {@code JVM 1.8.0.231} -> {code 8}).
+     *
+     * @return the Java major version
+     */
+    public static int getJavaVersion() {
+        String version = System.getProperty("java.version");
+        return parseJavaVersion(version);
+    }
+
+    /**
+     * Returns the major version parsed from the provided Java version string (e.g. {@code "1.8.0.231"} -> {@code 8}).
+     *
+     * @param version the Java version string
+     * @return the major version as an int
+     */
+    public static int parseJavaVersion(String version) {
+        String majorVersion;
+        if (version.startsWith("1.")) {
+            majorVersion = version.substring(2, 3);
+        } else {
+            Pattern majorVersion9PlusPattern = Pattern.compile("(\\d+).*");
+            Matcher m = majorVersion9PlusPattern.matcher(version);
+            if (m.find()) {
+                majorVersion = m.group(1);
+            } else {
+                throw new IllegalArgumentException("Could not detect major version of " + version);
+            }
+        }
+        return Integer.parseInt(majorVersion);
+    }
+
+    /**
+     * Returns a {@code String[]} of supported TLS protocol versions based on the current Java platform version.
+     *
+     * @return the supported TLS protocol version(s)
+     */
+    public static String[] getCurrentSupportedTlsProtocolVersions() {
+        int javaMajorVersion = getJavaVersion();
+        if (javaMajorVersion < 11) {
+            return JAVA_8_SUPPORTED_TLS_PROTOCOL_VERSIONS;
+        } else {
+            return JAVA_11_SUPPORTED_TLS_PROTOCOL_VERSIONS;
+        }
+    }
+
+    /**
+     * Returns the highest supported TLS protocol version based on the current Java platform version.
+     *
+     * @return the TLS protocol (e.g. {@code "TLSv1.2"})
+     */
+    public static String getHighestCurrentSupportedTlsProtocolVersion() {
+        int javaMajorVersion = getJavaVersion();
+        if (javaMajorVersion < 11) {
+            return JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION;
+        } else {
+            return JAVA_11_MAX_SUPPORTED_TLS_PROTOCOL_VERSION;
+        }
+    }
+
     private CertificateUtils() {
     }
 }
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
index 7b0446d..3af1a0d 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
@@ -24,6 +24,14 @@ import org.apache.nifi.registry.authorization.CurrentUser;
 import org.apache.nifi.registry.authorization.Permissions;
 import org.apache.nifi.registry.authorization.Tenant;
 import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.client.AccessClient;
+import org.apache.nifi.registry.client.NiFiRegistryClient;
+import org.apache.nifi.registry.client.NiFiRegistryClientConfig;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
+import org.apache.nifi.registry.client.UserClient;
+import org.apache.nifi.registry.client.impl.JerseyNiFiRegistryClient;
+import org.apache.nifi.registry.client.impl.request.BearerTokenRequestConfig;
 import org.apache.nifi.registry.extension.ExtensionManager;
 import org.apache.nifi.registry.properties.AESSensitivePropertyProvider;
 import org.apache.nifi.registry.properties.NiFiRegistryProperties;
@@ -36,6 +44,7 @@ import org.apache.nifi.registry.security.crypto.CryptoKeyProvider;
 import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.apache.nifi.registry.service.RegistryService;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,9 +64,9 @@ import org.springframework.test.context.junit4.SpringRunner;
 
 import javax.sql.DataSource;
 import javax.ws.rs.client.Entity;
-import javax.ws.rs.core.Form;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.io.IOException;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -614,6 +623,51 @@ public class SecureLdapIT extends IntegrationTestBase {
 
     }
 
+    @Test
+    public void testAccessClient() throws IOException, NiFiRegistryException {
+        final String baseUrl = createBaseURL();
+        LOGGER.info("Using base url = " + baseUrl);
+
+        final NiFiRegistryClientConfig clientConfig = createClientConfig(baseUrl);
+        Assert.assertNotNull(clientConfig);
+
+        final NiFiRegistryClient client = new JerseyNiFiRegistryClient.Builder()
+                .config(clientConfig)
+                .build();
+
+        final String username = "pasteur";
+        final String password = "password";
+
+        // authenticate with the username and password to obtain a token
+        final AccessClient accessClient = client.getAccessClient();
+        final String token = accessClient.getToken(username, password);
+        assertNotNull(token);
+
+        // use the token to check the status of the current user
+        final RequestConfig requestConfig = new BearerTokenRequestConfig(token);
+        final UserClient userClient = client.getUserClient(requestConfig);
+        assertEquals(username, userClient.getAccessStatus().getIdentity());
+
+        // use the token to logout
+        accessClient.logout(token);
+
+        // check the status of the current user again and should be unauthorized
+        try {
+            userClient.getAccessStatus();
+            Assert.fail("Should have failed with an unauthorized exception");
+        } catch (Exception e) {
+            //LOGGER.error(e.getMessage(), e);
+        }
+
+        // try to get a token with an invalid username and password
+        try {
+            accessClient.getToken("user-does-not-exist", "bad-password");
+            Assert.fail("Should have failed with an unauthorized exception");
+        } catch (Exception e) {
+
+        }
+    }
+
     /** A helper method to lookup identifiers for tenant identities using the REST API
      *
      * @param tenantIdentity - the identity to lookup
@@ -746,12 +800,6 @@ public class SecureLdapIT extends IntegrationTestBase {
 
     }
 
-    private static Form encodeCredentialsForURLFormParams(String username, String password) {
-        return new Form()
-                .param("username", username)
-                .param("password", password);
-    }
-
     private static String encodeCredentialsForBasicAuth(String username, String password) {
         final String credentials = username + ":" + password;
         final String base64credentials =  new String(Base64.getEncoder().encode(credentials.getBytes(Charset.forName("UTF-8"))));
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
index e49edf2..92f5458 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
@@ -28,9 +28,11 @@ import org.apache.nifi.registry.client.FlowSnapshotClient;
 import org.apache.nifi.registry.client.NiFiRegistryClient;
 import org.apache.nifi.registry.client.NiFiRegistryClientConfig;
 import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.RequestConfig;
 import org.apache.nifi.registry.client.TenantsClient;
 import org.apache.nifi.registry.client.UserClient;
 import org.apache.nifi.registry.client.impl.JerseyNiFiRegistryClient;
+import org.apache.nifi.registry.client.impl.request.ProxiedEntityRequestConfig;
 import org.apache.nifi.registry.flow.VersionedFlow;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
@@ -164,7 +166,8 @@ public class SecureNiFiRegistryClientIT extends IntegrationTestBase {
     @Test
     public void testGetAccessStatusWithProxiedEntity() throws IOException, NiFiRegistryException {
         final String proxiedEntity = "user2";
-        final UserClient userClient = client.getUserClient(proxiedEntity);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        final UserClient userClient = client.getUserClient(requestConfig);
         final CurrentUser status = userClient.getAccessStatus();
         assertEquals("user2", status.getIdentity());
         assertFalse(status.isAnonymous());
@@ -173,7 +176,8 @@ public class SecureNiFiRegistryClientIT extends IntegrationTestBase {
     @Test
     public void testCreatedBucketWithProxiedEntity() throws IOException, NiFiRegistryException {
         final String proxiedEntity = "user2";
-        final BucketClient bucketClient = client.getBucketClient(proxiedEntity);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        final BucketClient bucketClient = client.getBucketClient(requestConfig);
 
         final Bucket bucket = new Bucket();
         bucket.setName("Bucket 1");
@@ -193,8 +197,9 @@ public class SecureNiFiRegistryClientIT extends IntegrationTestBase {
         // this user shouldn't have access to anything
         final String proxiedEntity = NO_ACCESS_IDENTITY;
 
-        final FlowClient proxiedFlowClient = client.getFlowClient(proxiedEntity);
-        final FlowSnapshotClient proxiedFlowSnapshotClient = client.getFlowSnapshotClient(proxiedEntity);
+        final RequestConfig requestConfig = new ProxiedEntityRequestConfig(proxiedEntity);
+        final FlowClient proxiedFlowClient = client.getFlowClient(requestConfig);
+        final FlowSnapshotClient proxiedFlowSnapshotClient = client.getFlowSnapshotClient(requestConfig);
 
         try {
             proxiedFlowClient.get("1");