You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2016/07/12 15:21:25 UTC

[2/2] nifi git commit: NIFI-2003 Creating abstract authentication provider and incorporating into existing providers NIFI-2201 Add support for seeding cluster nodes in authorizations.xml - Passing client address along in user context on authorization req

NIFI-2003 Creating abstract authentication provider and incorporating into existing providers
NIFI-2201 Add support for seeding cluster nodes in authorizations.xml
- Passing client address along in user context on authorization requests
- This closes #628


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

Branch: refs/heads/master
Commit: ba763b95e8147be159a39031b9c7396e88293ebd
Parents: 4b9df7d
Author: Bryan Bende <bb...@apache.org>
Authored: Thu Jun 16 11:51:00 2016 -0400
Committer: Matt Gilman <ma...@gmail.com>
Committed: Tue Jul 12 11:20:29 2016 -0400

----------------------------------------------------------------------
 .../AbstractPolicyBasedAuthorizer.java          |  24 ++-
 .../nifi/authorization/UserContextKeys.java     |  26 +++
 .../authorization/UsersAndAccessPolicies.java   |   7 +-
 .../authorization/resource/Authorizable.java    |  20 +-
 .../nifi/authorization/user/NiFiUser.java       |   6 +
 .../TestAbstractPolicyBasedAuthorizer.java      |  23 +--
 .../org/apache/nifi/util/NiFiProperties.java    |   2 +
 .../authorization/AuthorizationsHolder.java     |  16 +-
 .../nifi/authorization/FileAuthorizer.java      | 100 +++++++--
 .../nifi/authorization/FileAuthorizerTest.java  |  56 ++++-
 .../authorization/user/StandardNiFiUser.java    |  17 +-
 .../org/apache/nifi/controller/Template.java    |  13 +-
 .../src/main/resources/conf/authorizations.xml  |  55 +----
 .../src/main/resources/conf/authorizers.xml     |  23 +++
 .../src/main/resources/conf/nifi.properties     |  10 +
 .../StandardNiFiWebConfigurationContext.java    |  10 +
 .../org/apache/nifi/web/api/AccessResource.java |  16 +-
 .../apache/nifi/web/api/ControllerResource.java |  12 ++
 .../apache/nifi/web/api/CountersResource.java   |  12 ++
 .../org/apache/nifi/web/api/FlowResource.java   |  19 ++
 .../apache/nifi/web/api/ProvenanceResource.java |  10 +
 .../apache/nifi/web/api/ResourceResource.java   |  12 ++
 .../nifi/web/api/SystemDiagnosticsResource.java |  12 ++
 .../nifi/web/controller/ControllerFacade.java   |   6 +-
 .../web/dao/impl/StandardConnectionDAO.java     |  12 ++
 .../security/NiFiAuthenticationProvider.java    | 185 +++++++++++++++++
 .../NiFiAuthenticationRequestToken.java         |  41 ++++
 .../security/jwt/JwtAuthenticationFilter.java   |   2 +-
 .../security/jwt/JwtAuthenticationProvider.java |  10 +-
 .../jwt/JwtAuthenticationRequestToken.java      |   9 +-
 .../security/otp/OtpAuthenticationFilter.java   |   4 +-
 .../security/otp/OtpAuthenticationProvider.java |  10 +-
 .../otp/OtpAuthenticationRequestToken.java      |  10 +-
 .../security/x509/X509AuthenticationFilter.java |   2 +-
 .../x509/X509AuthenticationProvider.java        |  35 +++-
 .../x509/X509AuthenticationRequestToken.java    |   8 +-
 .../resources/nifi-web-security-context.xml     |   3 +
 .../NiFiAuthenticationProviderTest.java         | 203 +++++++++++++++++++
 .../otp/OtpAuthenticationProviderTest.java      |   9 +-
 .../TestPersistentProvenanceRepository.java     |   6 +
 .../TestVolatileProvenanceRepository.java       |   5 +
 41 files changed, 915 insertions(+), 146 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java b/nifi-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java
index b173155..8fbe0fb 100644
--- a/nifi-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java
+++ b/nifi-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java
@@ -105,31 +105,33 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
         final UsersAndAccessPolicies usersAndAccessPolicies = getUsersAndAccessPolicies();
         final String resourceIdentifier = request.getResource().getIdentifier();
 
-        final Set<AccessPolicy> policies = usersAndAccessPolicies.getAccessPolicies(resourceIdentifier);
-        if (policies == null || policies.isEmpty()) {
+        final AccessPolicy policy = usersAndAccessPolicies.getAccessPolicy(resourceIdentifier, request.getAction());
+        if (policy == null) {
             return AuthorizationResult.resourceNotFound();
         }
 
         final User user = usersAndAccessPolicies.getUser(request.getIdentity());
-
         if (user == null) {
             return AuthorizationResult.denied("Unknown user with identity " + request.getIdentity());
         }
 
         final Set<Group> userGroups = usersAndAccessPolicies.getGroups(user.getIdentity());
-
-        for (AccessPolicy policy : policies) {
-            final boolean containsUser = policy.getUsers().contains(user.getIdentifier());
-            if (policy.getAction() == request.getAction() && (containsUser || containsGroup(userGroups, policy)) ) {
-                return AuthorizationResult.approved();
-            }
+        if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) {
+            return AuthorizationResult.approved();
         }
 
+
         return AuthorizationResult.denied();
     }
 
-
-    private boolean containsGroup(Set<Group> userGroups, final AccessPolicy policy) {
+    /**
+     * Determines if the policy contains one of the user's groups.
+     *
+     * @param userGroups the set of the user's groups
+     * @param policy the policy
+     * @return true if one of the Groups in userGroups is contained in the policy
+     */
+    private boolean containsGroup(final Set<Group> userGroups, final AccessPolicy policy) {
         if (userGroups.isEmpty() || policy.getGroups().isEmpty()) {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-api/src/main/java/org/apache/nifi/authorization/UserContextKeys.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/UserContextKeys.java b/nifi-api/src/main/java/org/apache/nifi/authorization/UserContextKeys.java
new file mode 100644
index 0000000..fca4b74
--- /dev/null
+++ b/nifi-api/src/main/java/org/apache/nifi/authorization/UserContextKeys.java
@@ -0,0 +1,26 @@
+/*
+ * 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.authorization;
+
+/**
+ * Constants for keys that can be passed in the AuthorizationRequest user context Map.
+ */
+public enum UserContextKeys {
+
+    CLIENT_ADDRESS;
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-api/src/main/java/org/apache/nifi/authorization/UsersAndAccessPolicies.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/UsersAndAccessPolicies.java b/nifi-api/src/main/java/org/apache/nifi/authorization/UsersAndAccessPolicies.java
index d7a825c..552b753 100644
--- a/nifi-api/src/main/java/org/apache/nifi/authorization/UsersAndAccessPolicies.java
+++ b/nifi-api/src/main/java/org/apache/nifi/authorization/UsersAndAccessPolicies.java
@@ -25,12 +25,13 @@ import java.util.Set;
 public interface UsersAndAccessPolicies {
 
     /**
-     * Retrieves the set of access policies for a given resource.
+     * Retrieves the set of access policies for a given resource and action.
      *
      * @param resourceIdentifier the resource identifier to retrieve policies for
-     * @return the set of access policies for the given resource
+     * @param action the action to retrieve policies for
+     * @return the access policy for the given resource and action
      */
-    public Set<AccessPolicy> getAccessPolicies(final String resourceIdentifier);
+    public AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action);
 
     /**
      * Retrieves a user by an identity string.

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java b/nifi-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java
index 90374dd..7cb21ae 100644
--- a/nifi-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java
+++ b/nifi-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java
@@ -23,9 +23,11 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.user.NiFiUser;
 
 import java.util.Map;
+import java.util.HashMap;
 
 public interface Authorizable {
 
@@ -67,7 +69,13 @@ public interface Authorizable {
      * @return is authorized
      */
     default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
-        // TODO - include user details context
+        final Map<String,String> userContext;
+        if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
 
         // build the request
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
@@ -77,6 +85,7 @@ public interface Authorizable {
                 .action(action)
                 .resource(getResource())
                 .resourceContext(resourceContext)
+                .userContext(userContext)
                 .build();
 
         // perform the authorization
@@ -119,7 +128,13 @@ public interface Authorizable {
      * @param resourceContext resource context
      */
     default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
-        // TODO - include user details context
+        final Map<String,String> userContext;
+        if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
 
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .identity(user.getIdentity())
@@ -128,6 +143,7 @@ public interface Authorizable {
                 .action(action)
                 .resource(getResource())
                 .resourceContext(resourceContext)
+                .userContext(userContext)
                 .build();
 
         final AuthorizationResult result = authorizer.authorize(request);

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java b/nifi-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java
index 9ec04e2..c450bc4 100644
--- a/nifi-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java
+++ b/nifi-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java
@@ -36,4 +36,10 @@ public interface NiFiUser {
      * @return <code>true</code> if the user is the unauthenticated Anonymous user
      */
     boolean isAnonymous();
+
+    /**
+     * @return the address of the client that made the request which created this user
+     */
+    String getClientAddress();
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java b/nifi-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
index 6be3f1a..b173d3d 100644
--- a/nifi-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
+++ b/nifi-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
@@ -54,15 +54,14 @@ public class TestAbstractPolicyBasedAuthorizer {
         final String userIdentifier = "userIdentifier1";
         final String userIdentity = "userIdentity1";
 
-        final Set<AccessPolicy> policiesForResource = new HashSet<>();
-        policiesForResource.add(new AccessPolicy.Builder()
+        final AccessPolicy policy = new AccessPolicy.Builder()
                 .identifier("1")
                 .resource(TEST_RESOURCE.getIdentifier())
                 .addUser(userIdentifier)
                 .action(RequestAction.READ)
-                .build());
+                .build();
 
-        when(usersAndAccessPolicies.getAccessPolicies(TEST_RESOURCE.getIdentifier())).thenReturn(policiesForResource);
+        when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
 
         final User user = new User.Builder()
                 .identity(userIdentity)
@@ -92,15 +91,14 @@ public class TestAbstractPolicyBasedAuthorizer {
         final String userIdentity = "userIdentity1";
         final String groupIdentifier = "groupIdentifier1";
 
-        final Set<AccessPolicy> policiesForResource = new HashSet<>();
-        policiesForResource.add(new AccessPolicy.Builder()
+        final AccessPolicy policy = new AccessPolicy.Builder()
                 .identifier("1")
                 .resource(TEST_RESOURCE.getIdentifier())
                 .addGroup(groupIdentifier)
                 .action(RequestAction.READ)
-                .build());
+                .build();
 
-        when(usersAndAccessPolicies.getAccessPolicies(TEST_RESOURCE.getIdentifier())).thenReturn(policiesForResource);
+        when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
 
         final User user = new User.Builder()
                 .identity(userIdentity)
@@ -137,15 +135,14 @@ public class TestAbstractPolicyBasedAuthorizer {
         final String userIdentifier = "userIdentifier1";
         final String userIdentity = "userIdentity1";
 
-        final Set<AccessPolicy> policiesForResource = new HashSet<>();
-        policiesForResource.add(new AccessPolicy.Builder()
+        final AccessPolicy policy = new AccessPolicy.Builder()
                 .identifier("1")
                 .resource(TEST_RESOURCE.getIdentifier())
                 .addUser("NOT_USER_1")
                 .action(RequestAction.READ)
-                .build());
+                .build();
 
-        when(usersAndAccessPolicies.getAccessPolicies(TEST_RESOURCE.getIdentifier())).thenReturn(policiesForResource);
+        when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
 
         final User user = new User.Builder()
                 .identity(userIdentity)
@@ -171,7 +168,7 @@ public class TestAbstractPolicyBasedAuthorizer {
         UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
         when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
 
-        when(usersAndAccessPolicies.getAccessPolicies(TEST_RESOURCE.getIdentifier())).thenReturn(new HashSet<>());
+        when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(null);
 
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .identity("userIdentity")

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 2e84c5b..3a619cb 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -135,6 +135,8 @@ public class NiFiProperties extends Properties {
     public static final String SECURITY_CLUSTER_AUTHORITY_PROVIDER_THREADS = "nifi.security.cluster.authority.provider.threads";
     public static final String SECURITY_OCSP_RESPONDER_URL = "nifi.security.ocsp.responder.url";
     public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate";
+    public static final String SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX = "nifi.security.identity.mapping.pattern.";
+    public static final String SECURITY_IDENTITY_MAPPING_VALUE_PREFIX = "nifi.security.identity.mapping.value.";
 
     // web properties
     public static final String WEB_WAR_DIR = "nifi.web.war.directory";

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java
index 13adedc..383281d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java
@@ -337,11 +337,23 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
     }
 
     @Override
-    public Set<AccessPolicy> getAccessPolicies(String resourceIdentifier) {
+    public AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action) {
         if (resourceIdentifier == null) {
             throw new IllegalArgumentException("Resource Identifier cannot be null");
         }
-        return policiesByResource.get(resourceIdentifier);
+
+        final Set<AccessPolicy> resourcePolicies = policiesByResource.get(resourceIdentifier);
+        if (resourcePolicies == null) {
+            return null;
+        }
+
+        for (AccessPolicy accessPolicy : resourcePolicies) {
+            if (accessPolicy.getAction() == action) {
+                return accessPolicy;
+            }
+        }
+
+        return null;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
index c11dc40..a672b30 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
@@ -66,6 +66,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.zip.GZIPInputStream;
 
 /**
@@ -103,6 +105,7 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
     static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
     static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
     static final String PROP_LEGACY_AUTHORIZED_USERS_FILE = "Legacy Authorized Users File";
+    static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile("Node Identity \\S+");
 
     private Schema flowSchema;
     private Schema usersSchema;
@@ -114,6 +117,7 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
     private String rootGroupId;
     private String initialAdminIdentity;
     private String legacyAuthorizedUsersFile;
+    private Set<String> nodeIdentities;
 
     private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>();
 
@@ -169,12 +173,23 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
                 }
             }
 
+            // get the value of the initial admin identity
             final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
             initialAdminIdentity = initialAdminIdentityProp == null ? null : initialAdminIdentityProp.getValue();
 
+            // get the value of the legacy authorized users file
             final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(PROP_LEGACY_AUTHORIZED_USERS_FILE);
             legacyAuthorizedUsersFile = legacyAuthorizedUsersProp == null ? null : legacyAuthorizedUsersProp.getValue();
 
+            // extract any node identities
+            nodeIdentities = new HashSet<>();
+            for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
+                Matcher matcher = NODE_IDENTITY_PATTERN.matcher(entry.getKey());
+                if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+                    nodeIdentities.add(entry.getValue());
+                }
+            }
+
             // load the authorizations
             load();
 
@@ -235,6 +250,8 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
                 convertLegacyAuthorizedUsers(authorizations);
             }
 
+            populateNodes(authorizations);
+
             // save any changes that were made and repopulate the holder
             saveAndRefreshHolder(authorizations);
         } else {
@@ -338,6 +355,38 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
     }
 
     /**
+     * Creates a user for each node and gives the nodes write permission to /proxy.
+     *
+     * @param authorizations the overall authorizations
+     */
+    private void populateNodes(Authorizations authorizations) {
+        for (String nodeIdentity : nodeIdentities) {
+            // see if we have an existing user for the given node identity
+            org.apache.nifi.authorization.file.generated.User jaxbNodeUser = null;
+            for (org.apache.nifi.authorization.file.generated.User user : authorizations.getUsers().getUser()) {
+                if (user.getIdentity().equals(nodeIdentity)) {
+                    jaxbNodeUser = user;
+                    break;
+                }
+            }
+
+            // if we didn't find an existing user then create a new one
+            if (jaxbNodeUser == null) {
+                // generate an identifier and add a User with the given identifier and identity
+                final UUID nodeIdentifier = UUID.nameUUIDFromBytes(nodeIdentity.getBytes(StandardCharsets.UTF_8));
+                final User nodeUser = new User.Builder().identifier(nodeIdentifier.toString()).identity(nodeIdentity).build();
+
+                jaxbNodeUser = createJAXBUser(nodeUser);
+                authorizations.getUsers().getUser().add(jaxbNodeUser);
+            }
+
+            // grant access to the proxy resource
+            addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), jaxbNodeUser.getIdentifier(), READ_CODE);
+            addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), jaxbNodeUser.getIdentifier(), WRITE_CODE);
+        }
+    }
+
+    /**
      * Unmarshalls an existing authorized-users.xml and converts the object model to the new model.
      *
      * @param authorizations the current Authorizations instance that users, groups, and policies will be added to
@@ -509,25 +558,42 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
      * @param action the action for the policy
      */
     private void addAccessPolicy(final Authorizations authorizations, final String resource, final String identity, final String action) {
-        final String uuidSeed = resource + identity;
-        final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8));
-
-        final AccessPolicy.Builder builder = new AccessPolicy.Builder()
-                .identifier(policyIdentifier.toString())
-                .resource(resource)
-                .addUser(identity);
-
-        if (action.equals(READ_CODE)) {
-            builder.action(RequestAction.READ);
-        } else if (action.equals(WRITE_CODE)) {
-            builder.action(RequestAction.WRITE);
-        } else {
-            throw new IllegalStateException("Unknown Policy Action: " + action);
+        // first try to find an existing policy for the given resource and action
+        Policy foundPolicy = null;
+        for (Policy policy : authorizations.getPolicies().getPolicy()) {
+            if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
+                foundPolicy = policy;
+                break;
+            }
         }
 
-        final AccessPolicy accessPolicy = builder.build();
-        final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
-        authorizations.getPolicies().getPolicy().add(jaxbPolicy);
+        if (foundPolicy == null) {
+            // if we didn't find an existing policy create a new one
+            final String uuidSeed = resource + identity + action;
+            final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8));
+
+            final AccessPolicy.Builder builder = new AccessPolicy.Builder()
+                    .identifier(policyIdentifier.toString())
+                    .resource(resource)
+                    .addUser(identity);
+
+            if (action.equals(READ_CODE)) {
+                builder.action(RequestAction.READ);
+            } else if (action.equals(WRITE_CODE)) {
+                builder.action(RequestAction.WRITE);
+            } else {
+                throw new IllegalStateException("Unknown Policy Action: " + action);
+            }
+
+            final AccessPolicy accessPolicy = builder.build();
+            final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
+            authorizations.getPolicies().getPolicy().add(jaxbPolicy);
+        } else {
+            // otherwise add the user to the existing policy
+            Policy.User policyUser = new Policy.User();
+            policyUser.setIdentifier(identity);
+            foundPolicy.getUser().add(policyUser);
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
index efe078f..1eed4e6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
@@ -157,10 +157,12 @@ public class FileAuthorizerTest {
         assertEquals(1, users.size());
 
         UsersAndAccessPolicies usersAndAccessPolicies = authorizer.getUsersAndAccessPolicies();
-        assertEquals(1, usersAndAccessPolicies.getAccessPolicies(ResourceType.Flow.getValue()).size());
-        assertEquals(2, usersAndAccessPolicies.getAccessPolicies(ResourceType.Controller.getValue()).size());
-        assertEquals(1, usersAndAccessPolicies.getAccessPolicies(ResourceType.System.getValue()).size());
-        assertEquals(2, usersAndAccessPolicies.getAccessPolicies(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size());
+        assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.Flow.getValue(), RequestAction.READ));
+        assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.Controller.getValue(), RequestAction.READ));
+        assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.Controller.getValue(), RequestAction.WRITE));
+        assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.System.getValue(), RequestAction.READ));
+        assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID, RequestAction.READ));
+        assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID, RequestAction.WRITE));
     }
 
     @Test
@@ -340,7 +342,7 @@ public class FileAuthorizerTest {
         assertEquals(adminIdentity, adminUser.getIdentity());
 
         final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
-        assertEquals(4, policies.size());
+        assertEquals(7, policies.size());
 
         final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
 
@@ -377,7 +379,7 @@ public class FileAuthorizerTest {
         assertEquals(adminIdentity, adminUser.getIdentity());
 
         final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
-        assertEquals(3, policies.size());
+        assertEquals(5, policies.size());
 
         final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
 
@@ -414,7 +416,7 @@ public class FileAuthorizerTest {
         assertEquals(adminIdentity, adminUser.getIdentity());
 
         final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
-        assertEquals(3, policies.size());
+        assertEquals(5, policies.size());
 
         final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
 
@@ -430,6 +432,46 @@ public class FileAuthorizerTest {
     }
 
     @Test
+    public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception {
+        final String adminIdentity = "admin-user";
+
+        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+
+        final String nodeIdentity1 = "node1";
+        final String nodeIdentity2 = "node2";
+
+        final Map<String,String> props = new HashMap<>();
+        props.put("Node Identity 1", nodeIdentity1);
+        props.put("Node Identity 2", nodeIdentity2);
+
+        when(configurationContext.getProperties()).thenReturn(props);
+
+        writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE);
+        authorizer.onConfigured(configurationContext);
+
+        User adminUser = authorizer.getUserByIdentity(adminIdentity);
+        assertNotNull(adminUser);
+
+        User nodeUser1 = authorizer.getUserByIdentity(nodeIdentity1);
+        assertNotNull(nodeUser1);
+
+        User nodeUser2 = authorizer.getUserByIdentity(nodeIdentity2);
+        assertNotNull(nodeUser2);
+
+        AccessPolicy proxyReadPolicy = authorizer.getUsersAndAccessPolicies().getAccessPolicy(ResourceType.Proxy.getValue(), RequestAction.READ);
+        AccessPolicy proxyWritePolicy = authorizer.getUsersAndAccessPolicies().getAccessPolicy(ResourceType.Proxy.getValue(), RequestAction.WRITE);
+
+        assertNotNull(proxyReadPolicy);
+        assertTrue(proxyReadPolicy.getUsers().contains(nodeUser1.getIdentifier()));
+        assertTrue(proxyReadPolicy.getUsers().contains(nodeUser2.getIdentifier()));
+
+        assertNotNull(proxyWritePolicy);
+        assertTrue(proxyWritePolicy.getUsers().contains(nodeUser1.getIdentifier()));
+        assertTrue(proxyWritePolicy.getUsers().contains(nodeUser2.getIdentifier()));
+    }
+
+    @Test
     public void testOnConfiguredWhenAuthorizationsFileDoesNotExist() {
         authorizer.onConfigured(configurationContext);
         assertEquals(0, authorizer.getAccessPolicies().size());

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
index 8d50151..372d89f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
@@ -27,14 +27,24 @@ public class StandardNiFiUser implements NiFiUser {
 
     private final String identity;
     private final NiFiUser chain;
+    private final String clientAddress;
 
     public StandardNiFiUser(String identity) {
-        this(identity, null);
+        this(identity, null, null);
+    }
+
+    public StandardNiFiUser(String identity, String clientAddress) {
+        this(identity, null, clientAddress);
     }
 
     public StandardNiFiUser(String identity, NiFiUser chain) {
+        this(identity, chain, null);
+    }
+
+    public StandardNiFiUser(String identity, NiFiUser chain, String clientAddress) {
         this.identity = identity;
         this.chain = chain;
+        this.clientAddress = clientAddress;
     }
 
 
@@ -54,6 +64,11 @@ public class StandardNiFiUser implements NiFiUser {
     }
 
     @Override
+    public String getClientAddress() {
+        return clientAddress;
+    }
+
+    @Override
     public boolean equals(Object obj) {
         if (obj == null) {
             return false;

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java
index 5231095..ecc9e6f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.controller;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.AccessDeniedException;
 import org.apache.nifi.authorization.AuthorizationRequest;
 import org.apache.nifi.authorization.AuthorizationResult;
@@ -23,6 +24,7 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.resource.ResourceType;
@@ -41,7 +43,9 @@ import org.apache.nifi.web.api.dto.ProcessorDTO;
 import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
 import org.apache.nifi.web.api.dto.TemplateDTO;
 
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 public class Template implements Authorizable {
@@ -168,7 +172,13 @@ public class Template implements Authorizable {
     }
 
     private AuthorizationResult checkAuthorization(final Authorizer authorizer, final RequestAction action, final boolean accessAttempt, final NiFiUser user) {
-        // TODO - include user details context
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
 
         // build the request
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
@@ -177,6 +187,7 @@ public class Template implements Authorizable {
             .accessAttempt(accessAttempt)
             .action(action)
             .resource(getResource())
+            .userContext(userContext)
             .build();
 
         // perform the authorization

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizations.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizations.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizations.xml
index 6403d95..a4aa839 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizations.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizations.xml
@@ -14,59 +14,10 @@
   limitations under the License.
 -->
 <!--
-    This file lists all authorizations for this NiFi instance. Refer to the properties file and authorizers.xml for configuration details.
-    
-    Available resources:
-        /flow                       - READ - allows user/entity to load the UI and see the flow structure
-                                    - WRITE - NA
-        /resource                   - READ - allows user/entity to retrieve the available resources
-                                    - WRITE - NA
-        /system                     - READ - allows user/entity to retrieve system level diagnostics (CPU load, disk utilization, etc)
-                                    - WRITE - NA
-        /controller                 - READ - allows user/entity to retrieve configuration details for the controller (controller bulletins, thread pool, reporting tasks, etc)
-                                    - WRITE - allows user/entity to modify configuration details for the controller
-        /provenance                 - READ - allows user/entity to perform provenance requests. results will be filtered based on access to provenance data per component
-                                    - WRITE - NA
-        /token                      - READ - NA
-                                    - WRITE - allows user/entity to create a token for access the REST API
-        /site-to-site               - READ - allows user/entity to retrieve configuration details for performing site to site data transfers with this NiFi
-                                    - WRITE - NA
-        /proxy                      - READ - NA
-                                    - WRITE - allows user/entity to create a proxy request on behalf of another user
-        /process-groups/{id}        - READ - allows user/entity to retrieve configuration details for the process group and all descendant components without explicit access policies
-                                    - WRITE - allows user/entity to create/update/delete configuration details for the process group and all descendant components without explicit access policies
-        /processors/{id}            - READ - allows user/entity to retrieve configuration details for the processor overriding any inherited authorizations from an ancestor process group
-                                    - WRITE - allows user/entity to update/delete the processor overriding any inherited authorizations from an ancestor process group
-        /input-ports/{id}           - READ - allows user/entity to retrieve configuration details for the input port overriding any inherited authorizations from an ancestor process group
-                                    - WRITE - allows user/entity to update/delete the input port overriding any inherited authorizations from an ancestor process group
-        /output-ports/{id}          - READ - allows user/entity to retrieve configuration details for the output port overriding any inherited authorizations from an ancestor process group
-                                    - WRITE - allows user/entity to update/delete the output port overriding any inherited authorizations from an ancestor process group
-        /labels/{id}                - READ - allows user/entity to retrieve configuration details for the label overriding any inherited authorizations from an ancestor process group
-                                    - WRITE - allows user/entity to update/delete the label overriding any inherited authorizations from an ancestor process group
-        /connections/{id}           - READ - allows user/entity to retrieve configuration details for the connection overriding any inherited authorizations from an ancestor process group
-                                    - WRITE - allows user/entity to update/delete the label overriding any inherited authorizations from an ancestor process group
-        /remote-process-groups/{id} - READ - allows user/entity to retrieve configuration details for the remote process group overriding any inherited authorizations from an ancestor process group
-                                    - WRITE - allows user/entity to update/delete the remote process group overriding any inherited authorizations from an ancestor process group
-        /templates/{id}             - READ - allows user/entity to retrieve configuration details for the template overriding any inherited authorizations from an ancestor process group
-                                    - WRITE - allows user/entity to create/update/delete the template overriding any inherited authorizations from an ancestor process group
-        /controller-services/{id}   - READ - allows user/entity to retrieve configuration details for the controller service overriding any inherited authorizations from an ancestor process group
-                                    - WRITE - allows user/entity to update/delete the controller service overriding any inherited authorizations from an ancestor process group
-        /reporting-tasks/{id}       - READ - allows user/entity to retrieve configuration details for the reporting tasks overriding any inherited authorizations from the controller
-                                    - WRITE - allows user/entity to create/update/delete the reporting tasks overriding any inherited authorizations from the controller
-        /{type}/{id}/provenance     - READ - allows user/entity to view provenance data from the underlying component
-                                    - WRITE - NA
+    This file should not be manually edited. Authorizations should only be created through the NiFi UI, and during
+    initial setup of a new instance. Refer to authorizers.xml for properties that allow seeding a new installation
+    with initial authorizations.
 -->
 <authorizations>
-    <!--
-    <users>
-      <user identifier="1" identity="" />
-    </users>
-
-    <policies>
-      <policy identifier="1" resource="/flow" action="RW">
-        <user identifier="1" />
-      </policy>
-    </policies>
-    -->
 
 </authorizations>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
index 12efb79..aa47a14 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
@@ -19,11 +19,34 @@
     must be specified in the nifi.properties file.
 -->
 <authorizers>
+
+    <!--
+        The FileAuthorizer is NiFi's provided authorizer and has the following properties:
+
+        - Authorizations File - The file where the FileAuthorizer will store authorizations.
+
+        - Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and
+            given the ability to create additional users, groups, and policies. The value of this property could be
+            a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there
+            are no other users, groups, and policies defined. If this property is specified then a Legacy Authorized
+            Users File can not be specified.
+
+        - Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically
+            converted to the new authorizations model. If this property is specified then an Initial Admin Identity can
+            not be specified, and this property will only be used when there are no other users, groups, and policies defined.
+
+        - Node Identity [unique key] - The identity of a NiFi cluster node. When clustered, a property for each node
+            should be defined, so that every node knows about every other node. If not clustered these properties can be ignored.
+    -->
     <authorizer>
         <identifier>file-provider</identifier>
         <class>org.apache.nifi.authorization.FileAuthorizer</class>
         <property name="Authorizations File">./conf/authorizations.xml</property>
         <property name="Initial Admin Identity"></property>
         <property name="Legacy Authorized Users File"></property>
+        <!--
+        <property name="Node Identity 1"></property>
+        <property name="Node Identity 2"></property>
+        -->
     </authorizer>
 </authorizers>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index dfbc6fd..cc69489 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -144,6 +144,16 @@ nifi.security.user.login.identity.provider=${nifi.security.user.login.identity.p
 nifi.security.ocsp.responder.url=${nifi.security.ocsp.responder.url}
 nifi.security.ocsp.responder.certificate=${nifi.security.ocsp.responder.certificate}
 
+# Identity Mapping Properties #
+# These properties allow normalizing user identities such that identities coming from different identity providers
+# (certificates, LDAP, Kerberos) can be treated the same internally in NiFi. The following example demonstrates normalizing
+# DNs from certificates and principals from Kerberos into a common identity string:
+#
+# nifi.security.identity.mapping.pattern.dn=^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$
+# nifi.security.identity.mapping.value.dn=$1@$2
+# nifi.security.identity.mapping.pattern.kerb=^(.*?)/instance@(.*?)$
+# nifi.security.identity.mapping.value.kerb=$1@$2
+
 # cluster common properties (all nodes must have same values) #
 nifi.cluster.protocol.heartbeat.interval=${nifi.cluster.protocol.heartbeat.interval}
 nifi.cluster.protocol.is.secure=${nifi.cluster.protocol.is.secure}

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
index e340623..641ed38 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
@@ -48,6 +48,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
@@ -97,12 +98,21 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
     private void authorizeFlowAccess(final NiFiUser user) {
         // authorize access
         serviceFacade.authorizeAccess(lookup -> {
+            final Map<String,String> userContext;
+            if (!StringUtils.isBlank(user.getClientAddress())) {
+                userContext = new HashMap<>();
+                userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+            } else {
+                userContext = null;
+            }
+
             final AuthorizationRequest request = new AuthorizationRequest.Builder()
                     .resource(ResourceFactory.getFlowResource())
                     .identity(user.getIdentity())
                     .anonymous(user.isAnonymous())
                     .accessAttempt(true)
                     .action(RequestAction.READ)
+                    .userContext(userContext)
                     .build();
 
             final AuthorizationResult result = authorizer.authorize(request);

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index e0162bc..6f79a23 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -34,6 +34,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
@@ -77,6 +78,8 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.net.URI;
 import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -108,12 +111,21 @@ public class AccessResource extends ApplicationResource {
      * Authorizes access to the flow.
      */
     private boolean hasFlowAccess(final NiFiUser user) {
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getFlowResource())
                 .identity(user.getIdentity())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(RequestAction.READ)
+                .userContext(userContext)
                 .build();
 
         final AuthorizationResult result = authorizer.authorize(request);
@@ -198,7 +210,7 @@ public class AccessResource extends ApplicationResource {
                         // Extract the Base64 encoded token from the Authorization header
                         final String token = StringUtils.substringAfterLast(authorization, " ");
 
-                        final JwtAuthenticationRequestToken jwtRequest = new JwtAuthenticationRequestToken(token);
+                        final JwtAuthenticationRequestToken jwtRequest = new JwtAuthenticationRequestToken(token, httpServletRequest.getRemoteAddr());
                         final NiFiAuthenticationToken authenticationResponse = (NiFiAuthenticationToken) jwtAuthenticationProvider.authenticate(jwtRequest);
                         final NiFiUser nifiUser = ((NiFiUserDetails) authenticationResponse.getDetails()).getNiFiUser();
 
@@ -215,7 +227,7 @@ public class AccessResource extends ApplicationResource {
             } else {
                 try {
                     final X509AuthenticationRequestToken x509Request = new X509AuthenticationRequestToken(
-                            httpServletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN), principalExtractor, certificates);
+                            httpServletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN), principalExtractor, certificates, httpServletRequest.getRemoteAddr());
 
                     final NiFiAuthenticationToken authenticationResponse = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(x509Request);
                     final NiFiUser nifiUser = ((NiFiUserDetails) authenticationResponse.getDetails()).getNiFiUser();

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
index 31ecd4c..836d254 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
@@ -30,6 +30,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
@@ -66,6 +67,8 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * RESTful endpoint for managing a Flow Controller.
@@ -92,12 +95,21 @@ public class ControllerResource extends ApplicationResource {
     private void authorizeController(final RequestAction action) {
         final NiFiUser user = NiFiUserUtils.getNiFiUser();
 
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getControllerResource())
                 .identity(user.getIdentity())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(action)
+                .userContext(userContext)
                 .build();
 
         final AuthorizationResult result = authorizer.authorize(request);

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java
index fea5d30..48d34e1 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java
@@ -17,6 +17,8 @@
 package org.apache.nifi.web.api;
 
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 import javax.servlet.http.HttpServletRequest;
@@ -40,6 +42,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
@@ -84,12 +87,21 @@ public class CountersResource extends ApplicationResource {
     private void authorizeCounters(final RequestAction action) {
         final NiFiUser user = NiFiUserUtils.getNiFiUser();
 
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getCountersResource())
                 .identity(user.getIdentity())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(action)
+                .userContext(userContext)
                 .build();
 
         final AuthorizationResult result = authorizer.authorize(request);

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
index 961984b..4447014 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
@@ -49,6 +49,7 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
@@ -210,12 +211,21 @@ public class FlowResource extends ApplicationResource {
     private void authorizeFlow(final RequestAction action) {
         final NiFiUser user = NiFiUserUtils.getNiFiUser();
 
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
             .resource(ResourceFactory.getFlowResource())
             .identity(user.getIdentity())
             .anonymous(user.isAnonymous())
             .accessAttempt(true)
             .action(action)
+            .userContext(userContext)
             .build();
 
         final AuthorizationResult result = authorizer.authorize(request);
@@ -228,12 +238,21 @@ public class FlowResource extends ApplicationResource {
     private boolean isAuthorized(final RequestAction action, final Resource resource) {
         final NiFiUser user = NiFiUserUtils.getNiFiUser();
 
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(resource)
                 .identity(user.getIdentity())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(false)
                 .action(action)
+                .userContext(userContext)
                 .build();
 
         final AuthorizationResult result = authorizer.authorize(request);

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java
index 70339ea..9fa2871 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java
@@ -29,6 +29,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
@@ -92,12 +93,21 @@ public class ProvenanceResource extends ApplicationResource {
     private void authorizeProvenanceRequest() {
         final NiFiUser user = NiFiUserUtils.getNiFiUser();
 
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
             .resource(ResourceFactory.getProvenanceResource())
             .identity(user.getIdentity())
             .anonymous(user.isAnonymous())
             .accessAttempt(true)
             .action(RequestAction.READ)
+            .userContext(userContext)
             .build();
 
         final AuthorizationResult result = authorizer.authorize(request);

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java
index 2df4db7..83f313f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java
@@ -16,7 +16,9 @@
  */
 package org.apache.nifi.web.api;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -33,6 +35,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
@@ -62,12 +65,21 @@ public class ResourceResource extends ApplicationResource {
     private void authorizeResource() {
         final NiFiUser user = NiFiUserUtils.getNiFiUser();
 
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
             .resource(ResourceFactory.getResourceResource())
             .identity(user.getIdentity())
             .anonymous(user.isAnonymous())
             .accessAttempt(true)
             .action(RequestAction.READ)
+            .userContext(userContext)
             .build();
 
         final AuthorizationResult result = authorizer.authorize(request);

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java
index 429fb4b..ef5c961 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java
@@ -17,6 +17,8 @@
 package org.apache.nifi.web.api;
 
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 import javax.ws.rs.Consumes;
@@ -36,6 +38,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
@@ -68,12 +71,21 @@ public class SystemDiagnosticsResource extends ApplicationResource {
     private void authorizeSystem() {
         final NiFiUser user = NiFiUserUtils.getNiFiUser();
 
+        final Map<String,String> userContext;
+        if (!StringUtils.isBlank(user.getClientAddress())) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
             .resource(ResourceFactory.getSystemResource())
             .identity(user.getIdentity())
             .anonymous(user.isAnonymous())
             .accessAttempt(true)
             .action(RequestAction.READ)
+            .userContext(userContext)
             .build();
 
         final AuthorizationResult result = authorizer.authorize(request);

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 2c6d948..b7472fc 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -1210,7 +1210,8 @@ public class ControllerFacade implements Authorizable {
         final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user);
         for (final String identity : dnChain) {
             final Authorizable eventAuthorizable = flowController.createProvenanceAuthorizable(componentId);
-            final NiFiUser chainUser = new StandardNiFiUser(identity) {
+            final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null;
+            final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) {
                 @Override
                 public boolean isAnonymous() {
                     // allow current user to drive anonymous flag as anonymous users are never chained... supports single user case
@@ -1243,7 +1244,8 @@ public class ControllerFacade implements Authorizable {
         final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user);
         for (final String identity : dnChain) {
             final Authorizable eventAuthorizable = flowController.createProvenanceAuthorizable(componentId);
-            final NiFiUser chainUser = new StandardNiFiUser(identity) {
+            final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null;
+            final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) {
                 @Override
                 public boolean isAnonymous() {
                     // allow current user to drive anonymous flag as anonymous users are never chained... supports single user case

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java
index 1ac13ce..0aff212 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java
@@ -16,12 +16,14 @@
  */
 package org.apache.nifi.web.dao.impl;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.AccessDeniedException;
 import org.apache.nifi.authorization.AuthorizationRequest;
 import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.connectable.Connectable;
@@ -57,6 +59,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -598,6 +601,14 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO
             final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user);
             dnChain.forEach(identity -> {
                 // build the request
+                final Map<String,String> userContext;
+                if (!StringUtils.isBlank(user.getClientAddress())) {
+                    userContext = new HashMap<>();
+                    userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+                } else {
+                    userContext = null;
+                }
+
                 final AuthorizationRequest request = new AuthorizationRequest.Builder()
                         .identity(identity)
                         .anonymous(user.isAnonymous())
@@ -605,6 +616,7 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO
                         .action(RequestAction.WRITE)
                         .resource(connection.getResource())
                         .resourceContext(attributes)
+                        .userContext(userContext)
                         .build();
 
                 // perform the authorization

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
new file mode 100644
index 0000000..f3f6bd0
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
@@ -0,0 +1,185 @@
+/*
+ * 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.web.security;
+
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Base AuthenticationProvider that provides common functionality to mapping identities.
+ */
+public abstract class NiFiAuthenticationProvider implements AuthenticationProvider {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(NiFiAuthenticationProvider.class);
+    private static final Pattern backReferencePattern = Pattern.compile("\\$(\\d+)");
+
+    private NiFiProperties properties;
+    private List<IdentityMapping> mappings;
+
+    /**
+     * @param properties the NiFiProperties instance
+     */
+    public NiFiAuthenticationProvider(final NiFiProperties properties) {
+        this.properties = properties;
+        this.mappings = Collections.unmodifiableList(getIdentityMappings(properties));
+    }
+
+    /**
+     * Builds the identity mappings from NiFiProperties.
+     *
+     * @param properties the NiFiProperties instance
+     * @return a list of identity mappings
+     */
+    private List<IdentityMapping> getIdentityMappings(final NiFiProperties properties) {
+        final List<IdentityMapping> mappings = new ArrayList<>();
+
+        // go through each property
+        for (String propertyName : properties.stringPropertyNames()) {
+            if (StringUtils.startsWith(propertyName, NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX)) {
+                final String key = StringUtils.substringAfter(propertyName, NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX);
+                final String identityPattern = properties.getProperty(propertyName);
+
+                if (StringUtils.isBlank(identityPattern)) {
+                    LOGGER.warn("Identity Mapping property {} was found, but was empty", new Object[]{propertyName});
+                    continue;
+                }
+
+                final String identityValueProperty = NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + key;
+                final String identityValue = properties.getProperty(identityValueProperty);
+
+                if (StringUtils.isBlank(identityValue)) {
+                    LOGGER.warn("Identity Mapping property {} was found, but corresponding value {} was not found",
+                            new Object[]{propertyName, identityValueProperty});
+                    continue;
+                }
+
+                final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue);
+                mappings.add(identityMapping);
+
+                LOGGER.debug("Found Identity Mapping with key = {}, pattern = {}, value = {}",
+                        new Object[] {key, identityPattern, identityValue});
+            }
+        }
+
+        // sort the list by the key so users can control the ordering in nifi.properties
+        Collections.sort(mappings, new Comparator<IdentityMapping>() {
+            @Override
+            public int compare(IdentityMapping m1, IdentityMapping m2) {
+                return m1.getKey().compareTo(m2.getKey());
+            }
+        });
+
+        return mappings;
+    }
+
+    public List<IdentityMapping> getMappings() {
+        return mappings;
+    }
+
+    protected String mapIdentity(final String identity) {
+        for (IdentityMapping mapping : mappings) {
+            Matcher m = mapping.getPattern().matcher(identity);
+            if (m.matches()) {
+                final String pattern = mapping.getPattern().pattern();
+                final String replacementValue = escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount());
+                return identity.replaceAll(pattern, replacementValue);
+            }
+        }
+
+        return identity;
+    }
+
+    // If we find a back reference that is not valid, then we will treat it as a literal string. For example, if we have 3 capturing
+    // groups and the Replacement Value has the value is "I owe $8 to him", then we want to treat the $8 as a literal "$8", rather
+    // than attempting to use it as a back reference.
+    private static String escapeLiteralBackReferences(final String unescaped, final int numCapturingGroups) {
+        if (numCapturingGroups == 0) {
+            return unescaped;
+        }
+
+        String value = unescaped;
+        final Matcher backRefMatcher = backReferencePattern.matcher(value);
+        while (backRefMatcher.find()) {
+            final String backRefNum = backRefMatcher.group(1);
+            if (backRefNum.startsWith("0")) {
+                continue;
+            }
+            final int originalBackRefIndex = Integer.parseInt(backRefNum);
+            int backRefIndex = originalBackRefIndex;
+
+            // if we have a replacement value like $123, and we have less than 123 capturing groups, then
+            // we want to truncate the 3 and use capturing group 12; if we have less than 12 capturing groups,
+            // then we want to truncate the 2 and use capturing group 1; if we don't have a capturing group then
+            // we want to truncate the 1 and get 0.
+            while (backRefIndex > numCapturingGroups && backRefIndex >= 10) {
+                backRefIndex /= 10;
+            }
+
+            if (backRefIndex > numCapturingGroups) {
+                final StringBuilder sb = new StringBuilder(value.length() + 1);
+                final int groupStart = backRefMatcher.start(1);
+
+                sb.append(value.substring(0, groupStart - 1));
+                sb.append("\\");
+                sb.append(value.substring(groupStart - 1));
+                value = sb.toString();
+            }
+        }
+
+        return value;
+    }
+
+    /**
+     * Holder to pass around the key, pattern, and replacement from an identity mapping in NiFiProperties.
+     */
+    public static final class IdentityMapping {
+
+        private final String key;
+        private final Pattern pattern;
+        private final String replacementValue;
+
+        public IdentityMapping(String key, Pattern pattern, String replacementValue) {
+            this.key = key;
+            this.pattern = pattern;
+            this.replacementValue = replacementValue;
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public Pattern getPattern() {
+            return pattern;
+        }
+
+        public String getReplacementValue() {
+            return replacementValue;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/ba763b95/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationRequestToken.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationRequestToken.java
new file mode 100644
index 0000000..21397b6
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationRequestToken.java
@@ -0,0 +1,41 @@
+/*
+ * 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.web.security;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+/**
+ * Base class for authentication request tokens in NiFI.
+ */
+public abstract class NiFiAuthenticationRequestToken extends AbstractAuthenticationToken {
+
+    private final String clientAddress;
+
+    /**
+     * @param clientAddress   The address of the client making the request
+     */
+    public NiFiAuthenticationRequestToken(final String clientAddress) {
+        super(null);
+        setAuthenticated(false);
+        this.clientAddress = clientAddress;
+    }
+
+    public String getClientAddress() {
+        return clientAddress;
+    }
+
+}