NIFIREG-292 Create database implementations of UserGroupProvider and AccessPolicyProvider

- Modified AuthorizerFactory to allow injecting a DataSource into providers
- Extracted common code from file-based implementations to utility classes
- Implemented DB providers with unit tests

NIFIREG-292 Making DB providers create initial users and policies even
 when there are existing users and policies

NIFIREG-292 Remove FK constraint in UGP_USER_GROUP table so users from
 other providers can be used

NIFIREG-292 Removed unused delete-by-id methods from user/group/policy
 related interfaces

NIFIREG-292 Introduce IdentityMapper interface and remove direct use of
 IdentityMappingUtil and NiFiRegistryProperties from user-group and
 policy providers, collapse abstract database providers into
 implementations

This closes #232.

Signed-off-by: Kevin Doran <kdoran@apache.org>
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AbstractConfigurableAccessPolicyProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AbstractConfigurableAccessPolicyProvider.java
new file mode 100644
index 0000000..beb3f95
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AbstractConfigurableAccessPolicyProvider.java
@@ -0,0 +1,84 @@
+/*
+ * 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.security.authorization;
+
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.util.PropertyValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractConfigurableAccessPolicyProvider implements ConfigurableAccessPolicyProvider {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigurableAccessPolicyProvider.class);
+
+    public static final String PROP_USER_GROUP_PROVIDER = "User Group Provider";
+
+    private UserGroupProvider userGroupProvider;
+    private UserGroupProviderLookup userGroupProviderLookup;
+
+    @Override
+    public final void initialize(final AccessPolicyProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
+        LOGGER.debug("Initializing " + getClass().getCanonicalName());
+        userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
+        doInitialize(initializationContext);
+        LOGGER.debug("Done initializing " + getClass().getCanonicalName());
+    }
+
+    /**
+     * Sub-classes can override this method to perform additional initialization.
+     */
+    protected void doInitialize(final AccessPolicyProviderInitializationContext initializationContext)
+            throws SecurityProviderCreationException {
+
+    }
+
+    @Override
+    public final void onConfigured(final AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+        try {
+            LOGGER.debug("Configuring " + getClass().getCanonicalName());
+
+            final PropertyValue userGroupProviderIdentifier = configurationContext.getProperty(PROP_USER_GROUP_PROVIDER);
+            if (!userGroupProviderIdentifier.isSet()) {
+                throw new SecurityProviderCreationException("The user group provider must be specified.");
+            }
+
+            userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderIdentifier.getValue());
+            if (userGroupProvider == null) {
+                throw new SecurityProviderCreationException("Unable to locate user group provider with identifier '" + userGroupProviderIdentifier.getValue() + "'");
+            }
+
+            doOnConfigured(configurationContext);
+
+            LOGGER.debug("Done configuring " + getClass().getCanonicalName());
+        } catch (Exception e) {
+            throw new SecurityProviderCreationException(e);
+        }
+    }
+
+    /**
+     * Sub-classes can override this method to perform additional actions during onConfigured.
+     */
+    protected void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+
+    }
+
+    @Override
+    public UserGroupProvider getUserGroupProvider() {
+        return userGroupProvider;
+    }
+
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AbstractPolicyBasedAuthorizer.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AbstractPolicyBasedAuthorizer.java
index 3e721de..0509a38 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AbstractPolicyBasedAuthorizer.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AbstractPolicyBasedAuthorizer.java
@@ -180,15 +180,6 @@
     public abstract Group deleteGroup(Group group) throws AuthorizationAccessException;
 
     /**
-     * Deletes the group with the given identifier.
-     *
-     * @param groupIdentifier the id of the group to delete
-     * @return the deleted group, or null if no matching group was found
-     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
-     */
-    public abstract Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException;
-
-    /**
      * Retrieves all groups.
      *
      * @return a list of groups
@@ -267,15 +258,6 @@
     public abstract User deleteUser(User user) throws AuthorizationAccessException;
 
     /**
-     * Deletes the user with the given id.
-     *
-     * @param userIdentifier the identifier of the user to delete
-     * @return the user that was deleted, or null if no matching user was found
-     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
-     */
-    public abstract User deleteUser(String userIdentifier) throws AuthorizationAccessException;
-
-    /**
      * Retrieves all users.
      *
      * @return a list of users
@@ -331,15 +313,6 @@
     public abstract AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException;
 
     /**
-     * Deletes the policy with the given id.
-     *
-     * @param policyIdentifier the id of the policy to delete
-     * @return the deleted policy, or null if no matching policy was found
-     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
-     */
-    public abstract AccessPolicy deleteAccessPolicy(String policyIdentifier) throws AuthorizationAccessException;
-
-    /**
      * Retrieves all access policies.
      *
      * @return a list of policies
@@ -518,11 +491,6 @@
             }
 
             @Override
-            public AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException {
-                return AbstractPolicyBasedAuthorizer.this.deleteAccessPolicy(accessPolicyIdentifier);
-            }
-
-            @Override
             public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
                 final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies();
                 return usersAndAccessPolicies.getAccessPolicy(resourceIdentifier, action);
@@ -565,11 +533,6 @@
                     }
 
                     @Override
-                    public User deleteUser(String userIdentifier) throws AuthorizationAccessException {
-                        return AbstractPolicyBasedAuthorizer.this.deleteUser(userIdentifier);
-                    }
-
-                    @Override
                     public Group addGroup(Group group) throws AuthorizationAccessException {
                         return AbstractPolicyBasedAuthorizer.this.addGroup(group);
                     }
@@ -585,11 +548,6 @@
                     }
 
                     @Override
-                    public Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
-                        return AbstractPolicyBasedAuthorizer.this.deleteGroup(groupIdentifier);
-                    }
-
-                    @Override
                     public Set<User> getUsers() throws AuthorizationAccessException {
                         return AbstractPolicyBasedAuthorizer.this.getUsers();
                     }
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
index a9dbe5d..1fb3d90 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
@@ -17,13 +17,13 @@
 package org.apache.nifi.registry.security.authorization;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
 import org.apache.nifi.registry.extension.ExtensionClassLoader;
 import org.apache.nifi.registry.extension.ExtensionCloseable;
 import org.apache.nifi.registry.extension.ExtensionManager;
 import org.apache.nifi.registry.properties.NiFiRegistryProperties;
 import org.apache.nifi.registry.properties.SensitivePropertyProtectionException;
 import org.apache.nifi.registry.properties.SensitivePropertyProvider;
-import org.apache.nifi.registry.provider.StandardProviderFactory;
 import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
 import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
 import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
@@ -31,6 +31,7 @@
 import org.apache.nifi.registry.security.authorization.generated.Prop;
 import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
 import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.apache.nifi.registry.security.util.ClassLoaderUtils;
 import org.apache.nifi.registry.security.util.XmlUtils;
 import org.apache.nifi.registry.service.RegistryService;
@@ -41,8 +42,10 @@
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.lang.Nullable;
+import org.springframework.transaction.annotation.Transactional;
 import org.xml.sax.SAXException;
 
+import javax.sql.DataSource;
 import javax.xml.XMLConstants;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBElement;
@@ -60,6 +63,7 @@
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -71,11 +75,16 @@
  *
  * This implementation of AuthorizerFactory in NiFi Registry is based on a combination of
  * NiFi's AuthorizerFactory and AuthorizerFactoryBean.
+ *
+ * This class is annotated with Spring's @Transactional because a provider may have a DataSource injected and perform
+ * database operations during initialization and configuration when we are outside of the application's normal
+ * transactional scope during the processing of an incoming request.
  */
+@Transactional
 @Configuration("authorizerFactory")
 public class AuthorizerFactory implements UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup, DisposableBean {
 
-    private static final Logger logger = LoggerFactory.getLogger(StandardProviderFactory.class);
+    private static final Logger logger = LoggerFactory.getLogger(AuthorizerFactory.class);
 
     private static final String AUTHORIZERS_XSD = "/authorizers.xsd";
     private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.security.authorization.generated";
@@ -96,6 +105,8 @@
     private final ExtensionManager extensionManager;
     private final SensitivePropertyProvider sensitivePropertyProvider;
     private final RegistryService registryService;
+    private final DataSource dataSource;
+    private final IdentityMapper identityMapper;
 
     private Authorizer authorizer;
     private final Map<String, UserGroupProvider> userGroupProviders = new HashMap<>();
@@ -107,24 +118,16 @@
             final NiFiRegistryProperties properties,
             final ExtensionManager extensionManager,
             @Nullable final SensitivePropertyProvider sensitivePropertyProvider,
-            final RegistryService registryService) {
+            final RegistryService registryService,
+            final DataSource dataSource,
+            final IdentityMapper identityMapper) {
 
-        this.properties = properties;
-        this.extensionManager = extensionManager;
+        this.properties = Validate.notNull(properties);
+        this.extensionManager = Validate.notNull(extensionManager);
         this.sensitivePropertyProvider = sensitivePropertyProvider;
-        this.registryService = registryService;
-
-        if (this.properties == null) {
-            throw new IllegalStateException("NiFiRegistryProperties cannot be null");
-        }
-
-        if (this.extensionManager == null) {
-            throw new IllegalStateException("ExtensionManager cannot be null");
-        }
-
-        if (this.registryService == null) {
-            throw new IllegalStateException("RegistryService cannot be null");
-        }
+        this.registryService = Validate.notNull(registryService);
+        this.dataSource = Validate.notNull(dataSource);
+        this.identityMapper = Validate.notNull(identityMapper);
     }
 
     /***** UserGroupProviderLookup *****/
@@ -447,6 +450,12 @@
                         if (NiFiRegistryProperties.class.isAssignableFrom(argumentType)) {
                             // nifi properties injection
                             method.invoke(instance, properties);
+                        } else if (DataSource.class.isAssignableFrom(argumentType)) {
+                            // data source injection
+                            method.invoke(instance, dataSource);
+                        } else if (IdentityMapper.class.isAssignableFrom(argumentType)) {
+                            // identity mapper injection
+                            method.invoke(instance, identityMapper);
                         }
                     }
                 } finally {
@@ -478,6 +487,12 @@
                         if (NiFiRegistryProperties.class.isAssignableFrom(fieldType)) {
                             // nifi properties injection
                             field.set(instance, properties);
+                        } else if (DataSource.class.isAssignableFrom(fieldType)) {
+                            // data source injection
+                            field.set(instance, dataSource);
+                        } else if (IdentityMapper.class.isAssignableFrom(fieldType)) {
+                            // identity mapper injection
+                            field.set(instance, identityMapper);
                         }
                     }
 
@@ -618,14 +633,6 @@
                     }
 
                     @Override
-                    public AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException {
-                        if (!baseConfigurableAccessPolicyProvider.isConfigurable(baseConfigurableAccessPolicyProvider.getAccessPolicy(accessPolicyIdentifier))) {
-                            throw new IllegalArgumentException("The specified access policy is not support modification.");
-                        }
-                        return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicyIdentifier);
-                    }
-
-                    @Override
                     public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
                         return baseConfigurableAccessPolicyProvider.getAccessPolicies();
                     }
@@ -694,14 +701,6 @@
                                 }
 
                                 @Override
-                                public User deleteUser(String userIdentifier) throws AuthorizationAccessException {
-                                    if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getUser(userIdentifier))) {
-                                        throw new IllegalArgumentException("The specified user does not support modification.");
-                                    }
-                                    return baseConfigurableUserGroupProvider.deleteUser(userIdentifier);
-                                }
-
-                                @Override
                                 public Group addGroup(Group group) throws AuthorizationAccessException {
                                     if (groupExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
                                         throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
@@ -740,14 +739,6 @@
                                 }
 
                                 @Override
-                                public Group deleteGroup(String groupId) throws AuthorizationAccessException {
-                                    if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getGroup(groupId))) {
-                                        throw new IllegalArgumentException("The specified group does not support modification.");
-                                    }
-                                    return baseConfigurableUserGroupProvider.deleteGroup(groupId);
-                                }
-
-                                @Override
                                 public Set<User> getUsers() throws AuthorizationAccessException {
                                     return baseConfigurableUserGroupProvider.getUsers();
                                 }
@@ -840,8 +831,9 @@
             final UserGroupProvider userGroupProvider = accessPolicyProvider.getUserGroupProvider();
 
             // ensure that only one policy per resource-action exists
-            for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
-                if (policyExists(accessPolicyProvider, accessPolicy)) {
+            final Set<AccessPolicy> allPolicies = accessPolicyProvider.getAccessPolicies();
+            for (AccessPolicy accessPolicy : allPolicies) {
+                if (policyExists(allPolicies, accessPolicy)) {
                     throw new SecurityProviderCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
                 }
             }
@@ -930,7 +922,17 @@
      * @return true if another access policy exists with the same resource and action, false otherwise
      */
     private static boolean policyExists(final AccessPolicyProvider accessPolicyProvider, final AccessPolicy checkAccessPolicy) {
-        for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
+        return policyExists(accessPolicyProvider.getAccessPolicies(), checkAccessPolicy);
+    }
+
+    /**
+     * Checks if another policy exists with the same resource and action as the given policy.
+     *
+     * @param checkAccessPolicy an access policy being checked
+     * @return true if another access policy exists with the same resource and action, false otherwise
+     */
+    private static boolean policyExists(final Collection<AccessPolicy> policies, final AccessPolicy checkAccessPolicy) {
+        for (AccessPolicy accessPolicy : policies) {
             if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier())
                     && accessPolicy.getResource().equals(checkAccessPolicy.getResource())
                     && accessPolicy.getAction().equals(checkAccessPolicy.getAction())) {
@@ -940,6 +942,7 @@
         return false;
     }
 
+
     /**
      * Checks if another user or group exists with the same identity.
      *
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/CompositeConfigurableUserGroupProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/CompositeConfigurableUserGroupProvider.java
index 6581e89..b913acf 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/CompositeConfigurableUserGroupProvider.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/CompositeConfigurableUserGroupProvider.java
@@ -118,11 +118,6 @@
     }
 
     @Override
-    public User deleteUser(String userIdentifier) throws AuthorizationAccessException {
-        return configurableUserGroupProvider.deleteUser(userIdentifier);
-    }
-
-    @Override
     public Group addGroup(Group group) throws AuthorizationAccessException {
         return configurableUserGroupProvider.addGroup(group);
     }
@@ -143,11 +138,6 @@
     }
 
     @Override
-    public Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
-        return configurableUserGroupProvider.deleteGroup(groupIdentifier);
-    }
-
-    @Override
     public Set<User> getUsers() throws AuthorizationAccessException {
         final Set<User> users = new HashSet<>(configurableUserGroupProvider.getUsers());
         users.addAll(super.getUsers());
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/DatabaseAccessPolicyProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/DatabaseAccessPolicyProvider.java
new file mode 100644
index 0000000..2403784
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/DatabaseAccessPolicyProvider.java
@@ -0,0 +1,401 @@
+/*
+ * 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.security.authorization.database;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.registry.security.authorization.AbstractConfigurableAccessPolicyProvider;
+import org.apache.nifi.registry.security.authorization.AccessPolicy;
+import org.apache.nifi.registry.security.authorization.AccessPolicyProviderInitializationContext;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.User;
+import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.registry.security.authorization.database.entity.DatabaseAccessPolicy;
+import org.apache.nifi.registry.security.authorization.database.mapper.DatabaseAccessPolicyRowMapper;
+import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.registry.security.authorization.util.AccessPolicyProviderUtils;
+import org.apache.nifi.registry.security.authorization.util.InitialPolicies;
+import org.apache.nifi.registry.security.authorization.util.ResourceAndAction;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.util.CollectionUtils;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of {@link org.apache.nifi.registry.security.authorization.ConfigurableAccessPolicyProvider} backed by a relational database.
+ */
+public class DatabaseAccessPolicyProvider extends AbstractConfigurableAccessPolicyProvider {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseAccessPolicyProvider.class);
+
+    private DataSource dataSource;
+    private IdentityMapper identityMapper;
+
+    private JdbcTemplate jdbcTemplate;
+
+    @AuthorizerContext
+    public void setDataSource(final DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    @AuthorizerContext
+    public void setIdentityMapper(final IdentityMapper identityMapper) {
+        this.identityMapper = identityMapper;
+    }
+
+    @Override
+    protected void doInitialize(AccessPolicyProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
+        super.doInitialize(initializationContext);
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+        final String initialAdminIdentity = AccessPolicyProviderUtils.getInitialAdminIdentity(configurationContext, identityMapper);
+        final Set<String> nifiIdentities = AccessPolicyProviderUtils.getNiFiIdentities(configurationContext, identityMapper);
+        final String nifiGroupName = AccessPolicyProviderUtils.getNiFiGroupName(configurationContext, identityMapper);
+
+        if (!StringUtils.isBlank(initialAdminIdentity)) {
+            LOGGER.info("Populating authorizations for Initial Admin: '" + initialAdminIdentity + "'");
+            populateInitialAdmin(initialAdminIdentity);
+        }
+
+        if (!CollectionUtils.isEmpty(nifiIdentities)) {
+            LOGGER.info("Populating authorizations for NiFi identities: [{}]", StringUtils.join(nifiIdentities, ";"));
+            populateNiFiIdentities(nifiIdentities);
+        }
+
+        if (!StringUtils.isBlank(nifiGroupName)) {
+            LOGGER.info("Populating authorizations for NiFi Group: '" + nifiGroupName + "'");
+            populateNiFiGroup(nifiGroupName);
+        }
+    }
+
+    private void populateInitialAdmin(final String initialAdminIdentity) {
+        final User initialAdmin = getUserGroupProvider().getUserByIdentity(initialAdminIdentity);
+        if (initialAdmin == null) {
+            throw new SecurityProviderCreationException("Unable to locate initial admin '" + initialAdminIdentity + "' to seed policies");
+        }
+
+        for (final ResourceAndAction resourceAction : InitialPolicies.ADMIN_POLICIES) {
+            populateInitialPolicy(initialAdmin, resourceAction);
+        }
+    }
+
+    private void populateNiFiIdentities(final Set<String> nifiIdentities) {
+        for (final String nifiIdentity : nifiIdentities) {
+            final User nifiUser = getUserGroupProvider().getUserByIdentity(nifiIdentity);
+            if (nifiUser == null) {
+                throw new SecurityProviderCreationException("Unable to locate NiFi identity '" + nifiIdentity + "' to seed policies.");
+            }
+
+            for (final ResourceAndAction resourceAction : InitialPolicies.NIFI_POLICIES) {
+                populateInitialPolicy(nifiUser, resourceAction);
+            }
+        }
+    }
+
+    private void populateNiFiGroup(final String nifiGroupName) {
+        final Group nifiGroup = AccessPolicyProviderUtils.getGroup(nifiGroupName, getUserGroupProvider());
+
+        for (final ResourceAndAction resourceAction : InitialPolicies.NIFI_POLICIES) {
+            populateInitialPolicy(nifiGroup, resourceAction);
+        }
+    }
+
+    @Override
+    public void preDestruction() throws SecurityProviderDestructionException {
+
+    }
+
+    // ---- fingerprinting methods
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
+    }
+
+    @Override
+    public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+        throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+        throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
+    }
+
+    // ---- access policy methods
+
+    @Override
+    public AccessPolicy addAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        Validate.notNull(accessPolicy);
+
+        // insert to the policy table
+        final String policySql = "INSERT INTO APP_POLICY(IDENTIFIER, RESOURCE, ACTION) VALUES (?, ?, ?)";
+        jdbcTemplate.update(policySql, accessPolicy.getIdentifier(), accessPolicy.getResource(), accessPolicy.getAction().toString());
+
+        // insert to the policy-user and policy groups table
+        createPolicyUserAndGroups(accessPolicy);
+
+        return accessPolicy;
+    }
+
+    @Override
+    public AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        Validate.notNull(accessPolicy);
+
+        // determine if policy exists
+        final DatabaseAccessPolicy existingPolicy = getDatabaseAcessPolicy(accessPolicy.getIdentifier());
+        if (existingPolicy == null) {
+            return null;
+        }
+
+        // delete any policy-user associations
+        final String deletePolicyUsersSql = "DELETE FROM APP_POLICY_USER WHERE POLICY_IDENTIFIER = ?";
+        jdbcTemplate.update(deletePolicyUsersSql, accessPolicy.getIdentifier());
+
+        // delete any policy-group associations
+        final String deletePolicyGroupsSql = "DELETE FROM APP_POLICY_GROUP WHERE POLICY_IDENTIFIER = ?";
+        jdbcTemplate.update(deletePolicyGroupsSql, accessPolicy.getIdentifier());
+
+
+        // re-create the associations
+        createPolicyUserAndGroups(accessPolicy);
+
+        return accessPolicy;
+    }
+
+    @Override
+    public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+        // retrieve all the policies
+        final String sql = "SELECT * FROM APP_POLICY";
+        final List<DatabaseAccessPolicy> databasePolicies = jdbcTemplate.query(sql, new DatabaseAccessPolicyRowMapper());
+
+        // retrieve all users in policies, mapped by policy id
+        final Map<String,Set<String>> policyToUsers = new HashMap<>();
+        jdbcTemplate.query("SELECT * FROM APP_POLICY_USER", (rs) -> {
+            final String policyIdentifier = rs.getString("POLICY_IDENTIFIER");
+            final String userIdentifier = rs.getString("USER_IDENTIFIER");
+
+            final Set<String> userIdentifiers = policyToUsers.computeIfAbsent(policyIdentifier, (k) -> new HashSet<>());
+            userIdentifiers.add(userIdentifier);
+        });
+
+        // retrieve all groups in policies, mapped by policy id
+        final Map<String,Set<String>> policyToGroups = new HashMap<>();
+        jdbcTemplate.query("SELECT * FROM APP_POLICY_GROUP", (rs) -> {
+            final String policyIdentifier = rs.getString("POLICY_IDENTIFIER");
+            final String groupIdentifier = rs.getString("GROUP_IDENTIFIER");
+
+            final Set<String> groupIdentifiers = policyToGroups.computeIfAbsent(policyIdentifier, (k) -> new HashSet<>());
+            groupIdentifiers.add(groupIdentifier);
+        });
+
+        // convert the database model to the api model
+        final Set<AccessPolicy> policies = new HashSet<>();
+
+        databasePolicies.forEach(p -> {
+            final Set<String> userIdentifiers = policyToUsers.get(p.getIdentifier());
+            final Set<String> groupIdentifiers = policyToGroups.get(p.getIdentifier());
+            policies.add(mapTopAccessPolicy(p, userIdentifiers, groupIdentifiers));
+        });
+
+        return policies;
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException {
+        Validate.notBlank(identifier);
+
+        final DatabaseAccessPolicy databaseAccessPolicy = getDatabaseAcessPolicy(identifier);
+        if (databaseAccessPolicy == null) {
+            return null;
+        }
+
+        final Set<String> userIdentifiers = getPolicyUsers(identifier);
+        final Set<String> groupIdentifiers = getPolicyGroups(identifier);
+        return mapTopAccessPolicy(databaseAccessPolicy, userIdentifiers, groupIdentifiers);
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(final String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+        Validate.notBlank(resourceIdentifier);
+        Validate.notNull(action);
+
+        final String policySql = "SELECT * FROM APP_POLICY WHERE RESOURCE = ? AND ACTION = ?";
+        final Object[] args = new Object[]{resourceIdentifier, action.toString()};
+        final DatabaseAccessPolicy databaseAccessPolicy = queryForObject(policySql, args, new DatabaseAccessPolicyRowMapper());
+        if (databaseAccessPolicy == null) {
+            return null;
+        }
+
+        final Set<String> userIdentifiers = getPolicyUsers(databaseAccessPolicy.getIdentifier());
+        final Set<String> groupIdentifiers = getPolicyGroups(databaseAccessPolicy.getIdentifier());
+        return mapTopAccessPolicy(databaseAccessPolicy, userIdentifiers, groupIdentifiers);
+    }
+
+    @Override
+    public AccessPolicy deleteAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        Validate.notNull(accessPolicy);
+
+        final String sql = "DELETE FROM APP_POLICY WHERE IDENTIFIER = ?";
+        final int rowsUpdated = jdbcTemplate.update(sql, accessPolicy.getIdentifier());
+        if (rowsUpdated <= 0) {
+            return null;
+        }
+
+        return accessPolicy;
+    }
+
+    protected void createPolicyUserAndGroups(final AccessPolicy accessPolicy) {
+        if (accessPolicy.getUsers() != null) {
+            for (final String userIdentifier : accessPolicy.getUsers()) {
+                insertPolicyUser(accessPolicy.getIdentifier(), userIdentifier);
+            }
+        }
+
+        if (accessPolicy.getGroups() != null) {
+            for (final String groupIdentifier : accessPolicy.getGroups()) {
+                insertPolicyGroup(accessPolicy.getIdentifier(), groupIdentifier);
+            }
+        }
+    }
+
+    protected void insertPolicyGroup(final String policyIdentifier, final String groupIdentifier) {
+        final String policyGroupSql = "INSERT INTO APP_POLICY_GROUP(POLICY_IDENTIFIER, GROUP_IDENTIFIER) VALUES (?, ?)";
+        jdbcTemplate.update(policyGroupSql, policyIdentifier, groupIdentifier);
+    }
+
+    protected void insertPolicyUser(final String policyIdentifier, final String userIdentifier) {
+        final String policyUserSql = "INSERT INTO APP_POLICY_USER(POLICY_IDENTIFIER, USER_IDENTIFIER) VALUES (?, ?)";
+        jdbcTemplate.update(policyUserSql, policyIdentifier, userIdentifier);
+    }
+
+    protected DatabaseAccessPolicy getDatabaseAcessPolicy(final String policyIdentifier) {
+        final String sql = "SELECT * FROM APP_POLICY WHERE IDENTIFIER = ?";
+        return queryForObject(sql, new Object[] {policyIdentifier}, new DatabaseAccessPolicyRowMapper());
+    }
+
+    protected Set<String> getPolicyUsers(final String policyIdentifier) {
+        final String sql = "SELECT * FROM APP_POLICY_USER WHERE POLICY_IDENTIFIER = ?";
+
+        final Set<String> userIdentifiers = new HashSet<>();
+        jdbcTemplate.query(sql, new Object[]{policyIdentifier}, (rs) -> {
+            userIdentifiers.add(rs.getString("USER_IDENTIFIER"));
+        });
+        return userIdentifiers;
+    }
+
+    protected Set<String> getPolicyGroups(final String policyIdentifier) {
+        final String sql = "SELECT * FROM APP_POLICY_GROUP WHERE POLICY_IDENTIFIER = ?";
+
+        final Set<String> groupIdentifiers = new HashSet<>();
+        jdbcTemplate.query(sql, new Object[]{policyIdentifier}, (rs) -> {
+            groupIdentifiers.add(rs.getString("GROUP_IDENTIFIER"));
+        });
+        return groupIdentifiers;
+    }
+
+    protected AccessPolicy mapTopAccessPolicy(final DatabaseAccessPolicy databaseAccessPolicy, final Set<String> userIdentifiers, final Set<String> groupIdentifiers) {
+        return new AccessPolicy.Builder()
+                .identifier(databaseAccessPolicy.getIdentifier())
+                .resource(databaseAccessPolicy.getResource())
+                .action(RequestAction.valueOfValue(databaseAccessPolicy.getAction()))
+                .addUsers(userIdentifiers)
+                .addGroups(groupIdentifiers)
+                .build();
+    }
+
+    protected void populateInitialPolicy(final User initialUser, final ResourceAndAction resourceAndAction) {
+        final String userIdentifier = initialUser.getIdentifier();
+        final String resourceIdentifier = resourceAndAction.getResource().getIdentifier();
+        final RequestAction action = resourceAndAction.getAction();
+
+        final AccessPolicy existingPolicy = getAccessPolicy(resourceIdentifier, action);
+        if (existingPolicy == null) {
+            // no policy exists for the given resource and action, so create a new one and add the given user
+            // we don't need to seed the identifier here since there is only a single external DB
+            final AccessPolicy accessPolicy = new AccessPolicy.Builder()
+                    .identifierGenerateRandom()
+                    .resource(resourceIdentifier)
+                    .action(action)
+                    .addUser(userIdentifier)
+                    .build();
+
+            addAccessPolicy(accessPolicy);
+        } else {
+            // a policy already exists for the given resource and action, so just associate the user with that policy
+            if (existingPolicy.getUsers().contains(initialUser.getIdentifier())) {
+                LOGGER.debug("'{}' is already part of the policy for {} {}",
+                        new Object[]{initialUser.getIdentity(), action.toString(), resourceIdentifier});
+            } else {
+                LOGGER.debug("Adding '{}' to the policy for {} {}",
+                        new Object[]{initialUser.getIdentity(), action.toString(), resourceIdentifier});
+                insertPolicyUser(existingPolicy.getIdentifier(), userIdentifier);
+            }
+        }
+    }
+
+    protected void populateInitialPolicy(final Group initialGroup, final ResourceAndAction resourceAndAction) {
+        final String resourceIdentifier = resourceAndAction.getResource().getIdentifier();
+        final RequestAction action = resourceAndAction.getAction();
+
+        final AccessPolicy existingPolicy = getAccessPolicy(resourceIdentifier, action);
+        if (existingPolicy == null) {
+            // no policy exists for the given resource and action, so create a new one and add the given group
+            // we don't need to seed the identifier here since there is only a single external DB
+            final AccessPolicy accessPolicy = new AccessPolicy.Builder()
+                    .identifierGenerateRandom()
+                    .resource(resourceIdentifier)
+                    .action(action)
+                    .addGroup(initialGroup.getIdentifier())
+                    .build();
+
+            addAccessPolicy(accessPolicy);
+        } else {
+            // a policy already exists for the given resource and action, so just associate the group with that policy
+            insertPolicyGroup(existingPolicy.getIdentifier(), initialGroup.getIdentifier());
+        }
+    }
+
+    //-- util methods
+
+    protected <T> T queryForObject(final String sql, final Object[] args, final RowMapper<T> rowMapper) {
+        try {
+            return jdbcTemplate.queryForObject(sql, args, rowMapper);
+        } catch(final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/DatabaseUserGroupProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/DatabaseUserGroupProvider.java
new file mode 100644
index 0000000..c44a553
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/DatabaseUserGroupProvider.java
@@ -0,0 +1,387 @@
+/*
+ * 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.security.authorization.database;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.User;
+import org.apache.nifi.registry.security.authorization.UserAndGroups;
+import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext;
+import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.registry.security.authorization.database.entity.DatabaseGroup;
+import org.apache.nifi.registry.security.authorization.database.entity.DatabaseUser;
+import org.apache.nifi.registry.security.authorization.database.mapper.DatabaseGroupRowMapper;
+import org.apache.nifi.registry.security.authorization.database.mapper.DatabaseUserRowMapper;
+import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.registry.security.authorization.util.UserGroupProviderUtils;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+
+import javax.sql.DataSource;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of {@link org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider} backed by a relational database.
+ */
+public class DatabaseUserGroupProvider implements ConfigurableUserGroupProvider {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseUserGroupProvider.class);
+
+    private DataSource dataSource;
+    private IdentityMapper identityMapper;
+
+    private JdbcTemplate jdbcTemplate;
+
+    @AuthorizerContext
+    public void setDataSource(final DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    @AuthorizerContext
+    public void setIdentityMapper(final IdentityMapper identityMapper) {
+        this.identityMapper = identityMapper;
+    }
+
+    @Override
+    public void initialize(final UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+        final Set<String> initialUserIdentities = UserGroupProviderUtils.getInitialUserIdentities(configurationContext, identityMapper);
+
+        for (final String initialUserIdentity : initialUserIdentities) {
+            final User existingUser = getUserByIdentity(initialUserIdentity);
+            if (existingUser == null) {
+                final User initialUser = new User.Builder()
+                        .identifierGenerateFromSeed(initialUserIdentity)
+                        .identity(initialUserIdentity)
+                        .build();
+                addUser(initialUser);
+                LOGGER.info("Created initial user with identity {}", new Object[]{initialUserIdentity});
+            } else {
+                LOGGER.debug("User already exists with identity {}", new Object[]{initialUserIdentity});
+            }
+        }
+    }
+
+    @Override
+    public void preDestruction() throws SecurityProviderDestructionException {
+
+    }
+
+    //-- fingerprint methods
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
+    }
+
+    @Override
+    public void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException {
+        throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
+    }
+
+    @Override
+    public void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+        throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
+    }
+
+    //-- User CRUD
+
+    @Override
+    public User addUser(final User user) throws AuthorizationAccessException {
+        Validate.notNull(user);
+        final String sql = "INSERT INTO UGP_USER(IDENTIFIER, IDENTITY) VALUES (?, ?)";
+        jdbcTemplate.update(sql, new Object[] {user.getIdentifier(), user.getIdentity()});
+        return user;
+    }
+
+    @Override
+    public User updateUser(final User user) throws AuthorizationAccessException {
+        Validate.notNull(user);
+
+        // update the user identity
+        final String sql = "UPDATE UGP_USER SET IDENTITY = ? WHERE IDENTIFIER = ?";
+        final int updated = jdbcTemplate.update(sql, user.getIdentity(), user.getIdentifier());
+
+        // if no rows were updated then there is no user with the given identifier, so return null
+        if (updated <= 0) {
+            return null;
+        }
+
+        return user;
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        final String sql = "SELECT * FROM UGP_USER";
+        final List<DatabaseUser> databaseUsers = jdbcTemplate.query(sql, new DatabaseUserRowMapper());
+
+        final Set<User> users = new HashSet<>();
+        databaseUsers.forEach(u -> {
+            users.add(mapToUser(u));
+        });
+        return users;
+    }
+
+    @Override
+    public User getUser(final String identifier) throws AuthorizationAccessException {
+        Validate.notBlank(identifier);
+
+        final DatabaseUser databaseUser = getDatabaseUser(identifier);
+        if (databaseUser == null) {
+            return null;
+        }
+
+        return mapToUser(databaseUser);
+    }
+
+    @Override
+    public User getUserByIdentity(final String identity) throws AuthorizationAccessException {
+        Validate.notBlank(identity);
+
+        final String sql = "SELECT * FROM UGP_USER WHERE IDENTITY = ?";
+        final DatabaseUser databaseUser = queryForObject(sql, new Object[] {identity}, new DatabaseUserRowMapper());
+        if (databaseUser == null) {
+            return null;
+        }
+
+        return mapToUser(databaseUser);
+    }
+
+    @Override
+    public UserAndGroups getUserAndGroups(final String userIdentity) throws AuthorizationAccessException {
+        Validate.notBlank(userIdentity);
+
+        // retrieve the user
+        final User user = getUserByIdentity(userIdentity);
+
+        // if the user exists, then retrieve the groups for the user
+        final Set<Group> groups;
+        if (user == null) {
+            groups = null;
+        } else {
+            final String userGroupSql =
+                    "SELECT " +
+                            "G.IDENTIFIER AS IDENTIFIER, " +
+                            "G.IDENTITY AS IDENTITY " +
+                    "FROM " +
+                            "UGP_GROUP AS G, " +
+                            "UGP_USER_GROUP AS UG " +
+                    "WHERE " +
+                            "G.IDENTIFIER = UG.GROUP_IDENTIFIER AND " +
+                            "UG.USER_IDENTIFIER = ?";
+
+            final Object[] args = {user.getIdentifier()};
+            final List<DatabaseGroup> databaseGroups = jdbcTemplate.query(userGroupSql, args, new DatabaseGroupRowMapper());
+
+            groups = new HashSet<>();
+            databaseGroups.forEach(g -> {
+                final Set<String> userIdentifiers = getUserIdentifiers(g.getIdentifier());
+                groups.add(mapToGroup(g, userIdentifiers));
+            });
+        }
+
+        return new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return groups;
+            }
+        };
+    }
+
+    @Override
+    public User deleteUser(final User user) throws AuthorizationAccessException {
+        Validate.notNull(user);
+
+        final String deleteFromUserGroupSql = "DELETE FROM UGP_USER_GROUP WHERE USER_IDENTIFIER = ?";
+        jdbcTemplate.update(deleteFromUserGroupSql, user.getIdentifier());
+
+        final String deleteFromUserSql = "DELETE FROM UGP_USER WHERE IDENTIFIER = ?";
+        final int rowsDeletedFromUser = jdbcTemplate.update(deleteFromUserSql, user.getIdentifier());
+        if (rowsDeletedFromUser <= 0) {
+            return null;
+        }
+
+        return user;
+    }
+
+    private DatabaseUser getDatabaseUser(final String userIdentifier) {
+        final String sql = "SELECT * FROM UGP_USER WHERE IDENTIFIER = ?";
+        return queryForObject(sql, new Object[] {userIdentifier}, new DatabaseUserRowMapper());
+    }
+
+    private User mapToUser(final DatabaseUser databaseUser) {
+        return new User.Builder()
+                .identifier(databaseUser.getIdentifier())
+                .identity(databaseUser.getIdentity())
+                .build();
+    }
+
+    //-- Group CRUD
+
+    @Override
+    public Group addGroup(final Group group) throws AuthorizationAccessException {
+        Validate.notNull(group);
+
+        // insert to the group table...
+        final String groupSql = "INSERT INTO UGP_GROUP(IDENTIFIER, IDENTITY) VALUES (?, ?)";
+        jdbcTemplate.update(groupSql, group.getIdentifier(), group.getName());
+
+        // insert to the user-group table...
+        createUserGroups(group);
+
+        return group;
+    }
+
+    @Override
+    public Group updateGroup(final Group group) throws AuthorizationAccessException {
+        Validate.notNull(group);
+
+        // update the group identity
+        final String updateGroupSql = "UPDATE UGP_GROUP SET IDENTITY = ? WHERE IDENTIFIER = ?";
+        final int updated = jdbcTemplate.update(updateGroupSql, group.getName(), group.getIdentifier());
+
+        // if no rows were updated then a group does not exist for the given identifier, so return null
+        if (updated <= 0) {
+            return null;
+        }
+
+        // delete any user-group associations
+        final String deleteUserGroups = "DELETE FROM UGP_USER_GROUP WHERE GROUP_IDENTIFIER = ?";
+        jdbcTemplate.update(deleteUserGroups, group.getIdentifier());
+
+        // re-create any user-group associations
+        createUserGroups(group);
+
+        return group;
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        // retrieve all the groups
+        final String sql = "SELECT * FROM UGP_GROUP";
+        final List<DatabaseGroup> databaseGroups = jdbcTemplate.query(sql, new DatabaseGroupRowMapper());
+
+        // retrieve all the users in the groups, mapped by group id
+        final Map<String,Set<String>> groupToUsers = new HashMap<>();
+        jdbcTemplate.query("SELECT * FROM UGP_USER_GROUP", (rs) -> {
+            final String groupIdentifier = rs.getString("GROUP_IDENTIFIER");
+            final String userIdentifier = rs.getString("USER_IDENTIFIER");
+
+            final Set<String> userIdentifiers = groupToUsers.computeIfAbsent(groupIdentifier, (k) -> new HashSet<>());
+            userIdentifiers.add(userIdentifier);
+        });
+
+        // convert from database model to api model
+        final Set<Group> groups = new HashSet<>();
+        databaseGroups.forEach(g -> {
+            groups.add(mapToGroup(g, groupToUsers.get(g.getIdentifier())));
+        });
+        return groups;
+    }
+
+    @Override
+    public Group getGroup(final String groupIdentifier) throws AuthorizationAccessException {
+        Validate.notBlank(groupIdentifier);
+
+        final DatabaseGroup databaseGroup = getDatabaseGroup(groupIdentifier);
+        if (databaseGroup == null) {
+            return null;
+        }
+
+        final Set<String> userIdentifiers = getUserIdentifiers(groupIdentifier);
+        return mapToGroup(databaseGroup, userIdentifiers);
+    }
+
+    @Override
+    public Group deleteGroup(final Group group) throws AuthorizationAccessException {
+        Validate.notNull(group);
+
+        final String sql = "DELETE FROM UGP_GROUP WHERE IDENTIFIER = ?";
+        final int rowsUpdated = jdbcTemplate.update(sql, group.getIdentifier());
+        if (rowsUpdated <= 0) {
+            return null;
+        }
+
+        return group;
+    }
+
+    private void createUserGroups(final Group group) {
+        if (group.getUsers() != null) {
+            for (final String userIdentifier : group.getUsers()) {
+                final String userGroupSql = "INSERT INTO UGP_USER_GROUP (USER_IDENTIFIER, GROUP_IDENTIFIER) VALUES (?, ?)";
+                jdbcTemplate.update(userGroupSql, userIdentifier, group.getIdentifier());
+            }
+        }
+    }
+
+    private DatabaseGroup getDatabaseGroup(final String groupIdentifier) {
+        final String sql = "SELECT * FROM UGP_GROUP WHERE IDENTIFIER = ?";
+        return queryForObject(sql, new Object[] {groupIdentifier}, new DatabaseGroupRowMapper());
+    }
+
+    private Set<String> getUserIdentifiers(final String groupIdentifier) {
+        final String sql = "SELECT * FROM UGP_USER_GROUP WHERE GROUP_IDENTIFIER = ?";
+
+        final Set<String> userIdentifiers = new HashSet<>();
+        jdbcTemplate.query(sql, new Object[]{groupIdentifier}, (rs) -> {
+            userIdentifiers.add(rs.getString("USER_IDENTIFIER"));
+        });
+
+        return userIdentifiers;
+    }
+
+    private Group mapToGroup(final DatabaseGroup databaseGroup, final Set<String> userIdentifiers) {
+        return new Group.Builder()
+                .identifier(databaseGroup.getIdentifier())
+                .name(databaseGroup.getIdentity())
+                .addUsers(userIdentifiers == null ? Collections.emptySet() : userIdentifiers)
+                .build();
+    }
+
+    //-- util methods
+
+    private <T> T queryForObject(final String sql, final Object[] args, final RowMapper<T> rowMapper) {
+        try {
+            return jdbcTemplate.queryForObject(sql, args, rowMapper);
+        } catch(final EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseAccessPolicy.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseAccessPolicy.java
new file mode 100644
index 0000000..310d18b
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseAccessPolicy.java
@@ -0,0 +1,50 @@
+/*
+ * 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.security.authorization.database.entity;
+
+public class DatabaseAccessPolicy {
+
+    private String identifier;
+
+    private String resource;
+
+    private String action;
+
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    public void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(String resource) {
+        this.resource = resource;
+    }
+
+    public String getAction() {
+        return action;
+    }
+
+    public void setAction(String action) {
+        this.action = action;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseGroup.java
similarity index 62%
copy from nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java
copy to nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseGroup.java
index 91e673f..ccdc892 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseGroup.java
@@ -14,22 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.registry.security.authorization.file;
+package org.apache.nifi.registry.security.authorization.database.entity;
 
-import org.apache.commons.lang3.StringUtils;
+public class DatabaseGroup {
 
-import java.nio.charset.StandardCharsets;
-import java.util.UUID;
+    private String identifier;
 
-public final class IdentifierUtil {
+    private String identity;
 
-    static String getIdentifier(final String seed) {
-        if (StringUtils.isBlank(seed)) {
-            return null;
-        }
-
-        return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
+    public String getIdentifier() {
+        return identifier;
     }
 
-    private IdentifierUtil() {}
+    public void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    public String getIdentity() {
+        return identity;
+    }
+
+    public void setIdentity(String identity) {
+        this.identity = identity;
+    }
+
 }
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseUser.java
similarity index 63%
rename from nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java
rename to nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseUser.java
index 91e673f..cca3ab9 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/entity/DatabaseUser.java
@@ -14,22 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.registry.security.authorization.file;
+package org.apache.nifi.registry.security.authorization.database.entity;
 
-import org.apache.commons.lang3.StringUtils;
+public class DatabaseUser {
 
-import java.nio.charset.StandardCharsets;
-import java.util.UUID;
+    private String identifier;
 
-public final class IdentifierUtil {
+    private String identity;
 
-    static String getIdentifier(final String seed) {
-        if (StringUtils.isBlank(seed)) {
-            return null;
-        }
-
-        return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
+    public String getIdentifier() {
+        return identifier;
     }
 
-    private IdentifierUtil() {}
+    public void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    public String getIdentity() {
+        return identity;
+    }
+
+    public void setIdentity(String identity) {
+        this.identity = identity;
+    }
+
 }
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseAccessPolicyRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseAccessPolicyRowMapper.java
new file mode 100644
index 0000000..d795f59
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseAccessPolicyRowMapper.java
@@ -0,0 +1,35 @@
+/*
+ * 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.security.authorization.database.mapper;
+
+import org.apache.nifi.registry.security.authorization.database.entity.DatabaseAccessPolicy;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class DatabaseAccessPolicyRowMapper implements RowMapper<DatabaseAccessPolicy> {
+
+    @Override
+    public DatabaseAccessPolicy mapRow(final ResultSet rs, final int i) throws SQLException {
+        final DatabaseAccessPolicy policy = new DatabaseAccessPolicy();
+        policy.setIdentifier(rs.getString("IDENTIFIER"));
+        policy.setResource(rs.getString("RESOURCE"));
+        policy.setAction(rs.getString("ACTION"));
+        return policy;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseGroupRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseGroupRowMapper.java
new file mode 100644
index 0000000..4fa473e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseGroupRowMapper.java
@@ -0,0 +1,34 @@
+/*
+ * 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.security.authorization.database.mapper;
+
+import org.apache.nifi.registry.security.authorization.database.entity.DatabaseGroup;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class DatabaseGroupRowMapper implements RowMapper<DatabaseGroup> {
+
+    @Override
+    public DatabaseGroup mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+        final DatabaseGroup group = new DatabaseGroup();
+        group.setIdentifier(rs.getString("IDENTIFIER"));
+        group.setIdentity(rs.getString("IDENTITY"));
+        return group;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseUserRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseUserRowMapper.java
new file mode 100644
index 0000000..532937c
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/database/mapper/DatabaseUserRowMapper.java
@@ -0,0 +1,35 @@
+/*
+ * 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.security.authorization.database.mapper;
+
+import org.apache.nifi.registry.security.authorization.database.entity.DatabaseUser;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class DatabaseUserRowMapper implements RowMapper<DatabaseUser> {
+
+    @Override
+    public DatabaseUser mapRow(final ResultSet rs, final int rowNum) throws SQLException {
+        final DatabaseUser user = new DatabaseUser();
+        user.setIdentifier(rs.getString("IDENTIFIER"));
+        user.setIdentity(rs.getString("IDENTITY"));
+        return user;
+    }
+
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java
index 8c32ab8..31e77a1 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java
@@ -17,26 +17,25 @@
 package org.apache.nifi.registry.security.authorization.file;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.registry.properties.NiFiRegistryProperties;
-import org.apache.nifi.registry.properties.util.IdentityMapping;
-import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
+import org.apache.nifi.registry.security.authorization.AbstractConfigurableAccessPolicyProvider;
 import org.apache.nifi.registry.security.authorization.AccessPolicy;
 import org.apache.nifi.registry.security.authorization.AccessPolicyProviderInitializationContext;
 import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
-import org.apache.nifi.registry.security.authorization.ConfigurableAccessPolicyProvider;
 import org.apache.nifi.registry.security.authorization.Group;
 import org.apache.nifi.registry.security.authorization.RequestAction;
 import org.apache.nifi.registry.security.authorization.User;
-import org.apache.nifi.registry.security.authorization.UserGroupProvider;
-import org.apache.nifi.registry.security.authorization.UserGroupProviderLookup;
 import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
 import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
 import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
 import org.apache.nifi.registry.security.authorization.file.generated.Authorizations;
 import org.apache.nifi.registry.security.authorization.file.generated.Policies;
 import org.apache.nifi.registry.security.authorization.file.generated.Policy;
+import org.apache.nifi.registry.security.authorization.util.AccessPolicyProviderUtils;
+import org.apache.nifi.registry.security.authorization.util.InitialPolicies;
+import org.apache.nifi.registry.security.authorization.util.ResourceAndAction;
 import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
 import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.apache.nifi.registry.util.PropertyValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -70,17 +69,13 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
-public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider {
+public class FileAccessPolicyProvider extends AbstractConfigurableAccessPolicyProvider {
 
     private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class);
 
@@ -115,61 +110,19 @@
     static final String WRITE_CODE = "W";
     static final String DELETE_CODE = "D";
 
-    /*  TODO - move this somewhere into nifi-registry-security-framework so it can be applied to any ConfigurableAccessPolicyProvider
-     *  (and also gets us away from requiring magic strings here) */
-    private static final ResourceActionPair[] INITIAL_ADMIN_ACCESS_POLICIES = {
-            new ResourceActionPair("/tenants", READ_CODE),
-            new ResourceActionPair("/tenants", WRITE_CODE),
-            new ResourceActionPair("/tenants", DELETE_CODE),
-            new ResourceActionPair("/policies", READ_CODE),
-            new ResourceActionPair("/policies", WRITE_CODE),
-            new ResourceActionPair("/policies", DELETE_CODE),
-            new ResourceActionPair("/buckets", READ_CODE),
-            new ResourceActionPair("/buckets", WRITE_CODE),
-            new ResourceActionPair("/buckets", DELETE_CODE),
-            new ResourceActionPair("/actuator", READ_CODE),
-            new ResourceActionPair("/actuator", WRITE_CODE),
-            new ResourceActionPair("/actuator", DELETE_CODE),
-            new ResourceActionPair("/swagger", READ_CODE),
-            new ResourceActionPair("/swagger", WRITE_CODE),
-            new ResourceActionPair("/swagger", DELETE_CODE),
-            new ResourceActionPair("/proxy", READ_CODE),
-            new ResourceActionPair("/proxy", WRITE_CODE),
-            new ResourceActionPair("/proxy", DELETE_CODE)
-    };
-
-    /*  TODO - move this somewhere into nifi-registry-security-framework so it can be applied to any ConfigurableAccessPolicyProvider
-     *  (and also gets us away from requiring magic strings here) */
-    private static final ResourceActionPair[] NIFI_ACCESS_POLICIES = {
-            new ResourceActionPair("/buckets", READ_CODE),
-            new ResourceActionPair("/proxy", READ_CODE),
-            new ResourceActionPair("/proxy", WRITE_CODE),
-            new ResourceActionPair("/proxy", DELETE_CODE)
-    };
-
-    static final String PROP_NIFI_IDENTITY_PREFIX = "NiFi Identity ";
-    static final String PROP_USER_GROUP_PROVIDER = "User Group Provider";
-    static final String PROP_NIFI_GROUP_NAME = "NiFi Group Name";
     static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
-    static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
-    static final Pattern NIFI_IDENTITY_PATTERN = Pattern.compile(PROP_NIFI_IDENTITY_PREFIX + "\\S+");
 
+    private IdentityMapper identityMapper;
     private Schema authorizationsSchema;
-    private NiFiRegistryProperties properties;
     private File authorizationsFile;
     private String initialAdminIdentity;
     private Set<String> nifiIdentities;
-    private String nifiIdentityGroupIdentifier;
-    private List<IdentityMapping> identityMappings;
+    private String nifiGroupName;
 
-    private UserGroupProvider userGroupProvider;
-    private UserGroupProviderLookup userGroupProviderLookup;
     private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>();
 
     @Override
-    public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
-        userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
-
+    public void doInitialize(final AccessPolicyProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
         try {
             final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
             authorizationsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD));
@@ -179,18 +132,8 @@
     }
 
     @Override
-    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+    public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
         try {
-            final PropertyValue userGroupProviderIdentifier = configurationContext.getProperty(PROP_USER_GROUP_PROVIDER);
-            if (!userGroupProviderIdentifier.isSet()) {
-                throw new SecurityProviderCreationException("The user group provider must be specified.");
-            }
-
-            userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderIdentifier.getValue());
-            if (userGroupProvider == null) {
-                throw new SecurityProviderCreationException("Unable to locate user group provider with identifier " + userGroupProviderIdentifier.getValue());
-            }
-
             final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE);
             if (StringUtils.isBlank(authorizationsPath.getValue())) {
                 throw new SecurityProviderCreationException("The authorizations file must be specified.");
@@ -203,54 +146,25 @@
                 saveAuthorizations(new Authorizations());
             }
 
-            // extract the identity mappings from nifi-registry.properties if any are provided
-            identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
-
             // get the value of the initial admin identity
-            final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
-            initialAdminIdentity = initialAdminIdentityProp.isSet() ? IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), identityMappings) : null;
+            initialAdminIdentity = AccessPolicyProviderUtils.getInitialAdminIdentity(configurationContext, identityMapper);
 
             // extract any nifi identities
-            nifiIdentities = new HashSet<>();
-            for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
-                Matcher matcher = NIFI_IDENTITY_PATTERN.matcher(entry.getKey());
-                if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
-                    nifiIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
-                }
-            }
+            nifiIdentities = AccessPolicyProviderUtils.getNiFiIdentities(configurationContext, identityMapper);
 
-            PropertyValue identityGroupNameProp = configurationContext.getProperty(PROP_NIFI_GROUP_NAME);
-            String identityGroupName = (identityGroupNameProp != null && identityGroupNameProp.isSet()) ? identityGroupNameProp.getValue() : null;
-            if (!StringUtils.isBlank(identityGroupName)) {
-                logger.debug("{} is: {}", PROP_NIFI_GROUP_NAME, identityGroupName);
-                Set<Group> groups = userGroupProvider.getGroups();
-                logger.trace("All authorization groups: {}", groups);
-                Optional<Group> identityGroupsOptional =
-                        groups.stream()
-                            .filter(group -> group.getName().equals(identityGroupName))
-                            .findFirst();
-                Group identityGroup = identityGroupsOptional
-                        .orElseThrow(() ->
-                            new SecurityProviderCreationException(String.format("Authorizations node group '%s' could not be found", identityGroupName))
-                        );
-                logger.debug("Identity Group is: {}", identityGroup);
-                nifiIdentityGroupIdentifier = identityGroup.getIdentifier();
-            }
+            // extract the group for nifi identities, if one exists
+            nifiGroupName = AccessPolicyProviderUtils.getNiFiGroupName(configurationContext, identityMapper);
+
             // load the authorizations
             load();
 
             logger.info(String.format("Authorizations file loaded at %s", new Date().toString()));
-        } catch (SecurityProviderCreationException | JAXBException | IllegalStateException | SAXException e) {
+        } catch (JAXBException | SAXException e) {
             throw new SecurityProviderCreationException(e);
         }
     }
 
     @Override
-    public UserGroupProvider getUserGroupProvider() {
-        return userGroupProvider;
-    }
-
-    @Override
     public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
         return authorizationsHolder.get().getAllPolicies();
     }
@@ -328,8 +242,7 @@
         return deleteAccessPolicy(accessPolicy.getIdentifier());
     }
 
-    @Override
-    public synchronized AccessPolicy deleteAccessPolicy(String accessPolicyIdentifer) throws AuthorizationAccessException {
+    private synchronized AccessPolicy deleteAccessPolicy(String accessPolicyIdentifer) throws AuthorizationAccessException {
         if (accessPolicyIdentifer == null) {
             throw new IllegalArgumentException("Access policy identifier cannot be null");
         }
@@ -360,8 +273,8 @@
     }
 
     @AuthorizerContext
-    public void setNiFiProperties(NiFiRegistryProperties properties) {
-        this.properties = properties;
+    public void setIdentityMapper(final IdentityMapper identityMapper) {
+        this.identityMapper = identityMapper;
     }
 
     @Override
@@ -520,22 +433,20 @@
         // if we are starting fresh then we might need to populate an initial admin
         if (emptyAuthorizations) {
             if (hasInitialAdminIdentity) {
-               logger.info("Populating authorizations for Initial Admin: " + initialAdminIdentity);
+               logger.info("Populating authorizations for Initial Admin: '" + initialAdminIdentity + "'");
                populateInitialAdmin(authorizations);
             }
 
             if (hasNiFiIdentities) {
-                logger.info("Populating proxy authorizations for NiFi clients: [{}]", StringUtils.join(nifiIdentities, ";"));
+                logger.info("Populating authorizations for NiFi identities: [{}]", StringUtils.join(nifiIdentities, ";"));
                 populateNiFiIdentities(authorizations);
             }
 
-            if (!StringUtils.isEmpty(nifiIdentityGroupIdentifier)) {
-                logger.info("Populating proxy authorizations for NiFi identity group: [{}]", nifiIdentityGroupIdentifier);
-                // grant access to the resources needed for initial nifi-proxy identities
-                for (ResourceActionPair resourceAction : NIFI_ACCESS_POLICIES) {
-                    addGroupToAccessPolicy(authorizations, resourceAction.resource, nifiIdentityGroupIdentifier, resourceAction.actionCode);
-                }
+            if (!StringUtils.isEmpty(nifiGroupName)) {
+                logger.info("Populating authorizations for NiFi identity group: [{}]", nifiGroupName);
+                populateNiFiGroup(authorizations);
             }
+
             saveAndRefreshHolder(authorizations);
         } else {
             this.authorizationsHolder.set(authorizationsHolder);
@@ -559,17 +470,17 @@
 
     /**
      *  Creates the initial admin user and sets policies managing buckets, users, and policies.
-     *
-     *  TODO - move this somewhere into nifi-registry-security-framework so it can be applied to any ConfigurableAccessPolicyProvider
      */
     private void populateInitialAdmin(final Authorizations authorizations) {
-        final User initialAdmin = userGroupProvider.getUserByIdentity(initialAdminIdentity);
+        final User initialAdmin = getUserGroupProvider().getUserByIdentity(initialAdminIdentity);
         if (initialAdmin == null) {
             throw new SecurityProviderCreationException("Unable to locate initial admin " + initialAdminIdentity + " to seed policies");
         }
 
-        for (ResourceActionPair resourceAction : INITIAL_ADMIN_ACCESS_POLICIES) {
-            addUserToAccessPolicy(authorizations, resourceAction.resource, initialAdmin.getIdentifier(), resourceAction.actionCode);
+        for (final ResourceAndAction resourceAction : InitialPolicies.ADMIN_POLICIES) {
+            final String resource = resourceAction.getResource().getIdentifier();
+            final String actionCode = getActionCode(resourceAction.getAction());
+            addUserToAccessPolicy(authorizations, resource, initialAdmin.getIdentifier(), actionCode);
         }
     }
 
@@ -578,48 +489,59 @@
      *
      * @param authorizations the overall authorizations
      */
-    private void populateNiFiIdentities(Authorizations authorizations) {
+    private void populateNiFiIdentities(final Authorizations authorizations) {
         for (String nifiIdentity : nifiIdentities) {
-            final User nifiUser = userGroupProvider.getUserByIdentity(nifiIdentity);
+            final User nifiUser = getUserGroupProvider().getUserByIdentity(nifiIdentity);
             if (nifiUser == null) {
-                throw new SecurityProviderCreationException("Unable to locate node " + nifiIdentity + " to seed policies.");
+                throw new SecurityProviderCreationException("Unable to locate NiFi identities'" + nifiIdentity + "' to seed policies.");
             }
 
-            // grant access to the resources needed for initial nifi-proxy identities
-            for (ResourceActionPair resourceAction : NIFI_ACCESS_POLICIES) {
-                addUserToAccessPolicy(authorizations, resourceAction.resource, nifiUser.getIdentifier(), resourceAction.actionCode);
+            // grant access to the resources needed for initial nifi identities
+            for (final ResourceAndAction resourceAction : InitialPolicies.NIFI_POLICIES) {
+                final String resource = resourceAction.getResource().getIdentifier();
+                final String actionCode = getActionCode(resourceAction.getAction());
+                addUserToAccessPolicy(authorizations, resource, nifiUser.getIdentifier(), actionCode);
             }
         }
     }
 
-    private void addGroupToAccessPolicy(Authorizations authorizations, String resource, String nifiIdentityGroupIdentifier, String action) {
-    Optional<Policy> policyOptional = authorizations.getPolicies().getPolicy().stream()
-            .filter(policy -> policy.getResource().equals(resource))
-            .filter(policy -> policy.getAction().equals(action))
-        .findAny();
-    if (policyOptional.isPresent()) {
-        Policy policy = policyOptional.get();
-        Policy.Group group = new Policy.Group();
-        group.setIdentifier(nifiIdentityGroupIdentifier);
-        policy.getGroup().add(group);
-    } else {
-        AccessPolicy.Builder accessPolicyBuilder =
-            new AccessPolicy.Builder()
-              .identifierGenerateFromSeed(resource + action)
-              .resource(resource)
-              .addGroup(nifiIdentityGroupIdentifier);
-      if (action.equals(READ_CODE)) {
-          accessPolicyBuilder.action(RequestAction.READ);
-      } else if (action.equals(WRITE_CODE)) {
-          accessPolicyBuilder.action(RequestAction.WRITE);
-      } else if (action.equals(DELETE_CODE)) {
-          accessPolicyBuilder.action(RequestAction.DELETE);
-      } else {
-          throw new IllegalStateException("Unknown Policy Action: " + action);
-      }
-      authorizations.getPolicies().getPolicy().add(createJAXBPolicy(accessPolicyBuilder.build()));
+    /**
+     * Populates the authorizations for the NiFi Group.
+     *
+     * @param authorizations the overall authorizations
+     */
+    private void populateNiFiGroup(final Authorizations authorizations) {
+        final Group nifiGroup = AccessPolicyProviderUtils.getGroup(nifiGroupName, getUserGroupProvider());
+
+        // grant access to the resources needed for initial nifi-proxy identities
+        for (final ResourceAndAction resourceAction : InitialPolicies.NIFI_POLICIES) {
+            final String resource = resourceAction.getResource().getIdentifier();
+            final String actionCode = getActionCode(resourceAction.getAction());
+            addGroupToAccessPolicy(authorizations, resource, nifiGroup.getIdentifier(), actionCode);
+        }
     }
-  }
+
+    private void addGroupToAccessPolicy(final Authorizations authorizations, final String resource, final String groupIdentifier, final String actionCode) {
+        Optional<Policy> policyOptional = authorizations.getPolicies().getPolicy().stream()
+                .filter(policy -> policy.getResource().equals(resource))
+                .filter(policy -> policy.getAction().equals(actionCode))
+            .findAny();
+        if (policyOptional.isPresent()) {
+            Policy policy = policyOptional.get();
+            Policy.Group group = new Policy.Group();
+            group.setIdentifier(groupIdentifier);
+            policy.getGroup().add(group);
+        } else {
+            AccessPolicy.Builder accessPolicyBuilder =
+                    new AccessPolicy.Builder()
+                            .identifierGenerateFromSeed(resource + actionCode)
+                            .resource(resource)
+                            .addGroup(groupIdentifier)
+                            .action(getAction(actionCode));
+
+            authorizations.getPolicies().getPolicy().add(createJAXBPolicy(accessPolicyBuilder.build()));
+        }
+    }
 
     /**
      * Creates and adds an access policy for the given resource, identity, and actions to the specified authorizations.
@@ -627,13 +549,13 @@
      * @param authorizations the Authorizations instance to add the policy to
      * @param resource the resource for the policy
      * @param userIdentifier the identifier for the user to add to the policy
-     * @param action the action for the policy
+     * @param actionCode the action for the policy
      */
-    private void addUserToAccessPolicy(final Authorizations authorizations, final String resource, final String userIdentifier, final String action) {
+    private void addUserToAccessPolicy(final Authorizations authorizations, final String resource, final String userIdentifier, final String actionCode) {
         // 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)) {
+            if (policy.getResource().equals(resource) && policy.getAction().equals(actionCode)) {
                 foundPolicy = policy;
                 break;
             }
@@ -641,22 +563,13 @@
 
         if (foundPolicy == null) {
             // if we didn't find an existing policy create a new one
-            final String uuidSeed = resource + action;
+            final String uuidSeed = resource + actionCode;
 
             final AccessPolicy.Builder builder = new AccessPolicy.Builder()
                     .identifierGenerateFromSeed(uuidSeed)
                     .resource(resource)
-                    .addUser(userIdentifier);
-
-            if (action.equals(READ_CODE)) {
-                builder.action(RequestAction.READ);
-            } else if (action.equals(WRITE_CODE)) {
-                builder.action(RequestAction.WRITE);
-            } else if (action.equals(DELETE_CODE)) {
-                builder.action(RequestAction.DELETE);
-            } else {
-                throw new IllegalStateException("Unknown Policy Action: " + action);
-            }
+                    .addUser(userIdentifier)
+                    .action(getAction(actionCode));
 
             final AccessPolicy accessPolicy = builder.build();
             final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
@@ -673,25 +586,37 @@
         final Policy policy = new Policy();
         policy.setIdentifier(accessPolicy.getIdentifier());
         policy.setResource(accessPolicy.getResource());
-
-        switch (accessPolicy.getAction()) {
-            case READ:
-                policy.setAction(READ_CODE);
-                break;
-            case WRITE:
-                policy.setAction(WRITE_CODE);
-                break;
-            case DELETE:
-                policy.setAction(DELETE_CODE);
-                break;
-            default:
-                break;
-        }
-
+        policy.setAction(getActionCode(accessPolicy.getAction()));
         transferUsersAndGroups(accessPolicy, policy);
         return policy;
     }
 
+    private String getActionCode(final RequestAction action) {
+        switch (action) {
+            case READ:
+                return READ_CODE;
+            case WRITE:
+                return WRITE_CODE;
+            case DELETE:
+                return DELETE_CODE;
+            default:
+                throw new IllegalStateException("Unknown action: " + action);
+        }
+    }
+
+    private RequestAction getAction(final String actionCode) {
+        switch (actionCode) {
+            case READ_CODE:
+                return RequestAction.READ;
+            case WRITE_CODE:
+                return RequestAction.WRITE;
+            case DELETE_CODE:
+                return RequestAction.DELETE;
+            default:
+                throw new IllegalStateException("Unknown action: " + actionCode);
+        }
+    }
+
     /**
      * Sets the given Policy to the state of the provided AccessPolicy. Users and Groups will be cleared and
      * set to match the AccessPolicy, the resource and action will be set to match the AccessPolicy.
@@ -720,90 +645,6 @@
     }
 
     /**
-     * Adds the given user identifier to the policy if it doesn't already exist.
-     *
-     * @param userIdentifier a user identifier
-     * @param policy a policy to add the user to
-     */
-    private void addUserToPolicy(final String userIdentifier, final Policy policy) {
-        // determine if the user already exists in the policy
-        boolean userExists = false;
-        for (Policy.User policyUser : policy.getUser()) {
-            if (policyUser.getIdentifier().equals(userIdentifier)) {
-                userExists = true;
-                break;
-            }
-        }
-
-        // add the user to the policy if doesn't already exist
-        if (!userExists) {
-            Policy.User policyUser = new Policy.User();
-            policyUser.setIdentifier(userIdentifier);
-            policy.getUser().add(policyUser);
-        }
-    }
-
-    /**
-     * Adds the given group identifier to the policy if it doesn't already exist.
-     *
-     * @param groupIdentifier a group identifier
-     * @param policy a policy to add the user to
-     */
-    private void addGroupToPolicy(final String groupIdentifier, final Policy policy) {
-        // determine if the group already exists in the policy
-        boolean groupExists = false;
-        for (Policy.Group policyGroup : policy.getGroup()) {
-            if (policyGroup.getIdentifier().equals(groupIdentifier)) {
-                groupExists = true;
-                break;
-            }
-        }
-
-        // add the group to the policy if doesn't already exist
-        if (!groupExists) {
-            Policy.Group policyGroup = new Policy.Group();
-            policyGroup.setIdentifier(groupIdentifier);
-            policy.getGroup().add(policyGroup);
-        }
-    }
-
-    /**
-     * Finds the Policy matching the resource and action, or creates a new one and adds it to the list of policies.
-     *
-     * @param policies the policies to search through
-     * @param seedIdentity the seedIdentity to use when creating identifiers for new policies
-     * @param resource the resource for the policy
-     * @param action the action string for the police (R or RW)
-     * @return the matching policy or a new policy
-     */
-    private Policy getOrCreatePolicy(final List<Policy> policies, final String seedIdentity, final String resource, final String action) {
-        Policy foundPolicy = null;
-
-        // try to find a policy with the same resource and actions
-        for (Policy policy : policies) {
-            if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
-                foundPolicy = policy;
-                break;
-            }
-        }
-
-        // if a matching policy wasn't found then create one
-        if (foundPolicy == null) {
-            final String uuidSeed = resource + action + seedIdentity;
-            final String policyIdentifier = IdentifierUtil.getIdentifier(uuidSeed);
-
-            foundPolicy = new Policy();
-            foundPolicy.setIdentifier(policyIdentifier);
-            foundPolicy.setResource(resource);
-            foundPolicy.setAction(action);
-
-            policies.add(foundPolicy);
-        }
-
-        return foundPolicy;
-    }
-
-    /**
      * Saves the Authorizations instance by marshalling to a file, then re-populates the
      * in-memory data structures and sets the new holder.
      *
@@ -826,12 +667,4 @@
     public void preDestruction() throws SecurityProviderDestructionException {
     }
 
-    private static class ResourceActionPair {
-        public String resource;
-        public String actionCode;
-        public ResourceActionPair(String resource, String actionCode) {
-            this.resource = resource;
-            this.actionCode = actionCode;
-        }
-    }
 }
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAuthorizer.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAuthorizer.java
index ad59eb6..314a922 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAuthorizer.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAuthorizer.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.registry.security.authorization.file;
 
-import org.apache.nifi.registry.properties.NiFiRegistryProperties;
 import org.apache.nifi.registry.security.authorization.AbstractPolicyBasedAuthorizer;
 import org.apache.nifi.registry.security.authorization.AccessPolicy;
 import org.apache.nifi.registry.security.authorization.AccessPolicyProviderInitializationContext;
@@ -32,7 +31,10 @@
 import org.apache.nifi.registry.security.authorization.UsersAndAccessPolicies;
 import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
 import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.security.authorization.util.AccessPolicyProviderUtils;
+import org.apache.nifi.registry.security.authorization.util.UserGroupProviderUtils;
 import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -115,8 +117,8 @@
         if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE)) {
             accessPolicyProperties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, configurationProperties.get(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE));
         }
-        if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) {
-            accessPolicyProperties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
+        if (configurationProperties.containsKey(AccessPolicyProviderUtils.PROP_INITIAL_ADMIN_IDENTITY)) {
+            accessPolicyProperties.put(AccessPolicyProviderUtils.PROP_INITIAL_ADMIN_IDENTITY, configurationProperties.get(AccessPolicyProviderUtils.PROP_INITIAL_ADMIN_IDENTITY));
         }
         if (configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)) {
             accessPolicyProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE));
@@ -124,20 +126,20 @@
 
         // ensure all nifi identities are seeded into the user provider
         configurationProperties.forEach((property, value) -> {
-            final Matcher matcher = FileAccessPolicyProvider.NIFI_IDENTITY_PATTERN.matcher(property);
+            final Matcher matcher = AccessPolicyProviderUtils.NIFI_IDENTITY_PATTERN.matcher(property);
             if (matcher.matches()) {
                 accessPolicyProperties.put(property, value);
-                userGroupProperties.put(property.replace(FileAccessPolicyProvider.PROP_NIFI_IDENTITY_PREFIX, FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX), value);
+                userGroupProperties.put(property.replace(AccessPolicyProviderUtils.PROP_NIFI_IDENTITY_PREFIX, UserGroupProviderUtils.PROP_INITIAL_USER_IDENTITY_PREFIX), value);
             }
         });
 
         // ensure the initial admin is seeded into the user provider if appropriate
-        if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) {
+        if (configurationProperties.containsKey(AccessPolicyProviderUtils.PROP_INITIAL_ADMIN_IDENTITY)) {
             int i = 0;
             while (true) {
-                final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
+                final String key = UserGroupProviderUtils.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
                 if (!userGroupProperties.containsKey(key)) {
-                    userGroupProperties.put(key, configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
+                    userGroupProperties.put(key, configurationProperties.get(AccessPolicyProviderUtils.PROP_INITIAL_ADMIN_IDENTITY));
                     break;
                 }
             }
@@ -178,11 +180,6 @@
     }
 
     @Override
-    public synchronized Group deleteGroup(String groupId) throws AuthorizationAccessException {
-        return userGroupProvider.deleteGroup(groupId);
-    }
-
-    @Override
     public Set<Group> getGroups() throws AuthorizationAccessException {
         return userGroupProvider.getGroups();
     }
@@ -215,11 +212,6 @@
     }
 
     @Override
-    public synchronized User deleteUser(final String userId) throws AuthorizationAccessException {
-        return userGroupProvider.deleteUser(userId);
-    }
-
-    @Override
     public Set<User> getUsers() throws AuthorizationAccessException {
         return userGroupProvider.getUsers();
     }
@@ -247,19 +239,14 @@
     }
 
     @Override
-    public synchronized AccessPolicy deleteAccessPolicy(final String accessPolicyIdentifier) throws AuthorizationAccessException {
-        return accessPolicyProvider.deleteAccessPolicy(accessPolicyIdentifier);
-    }
-
-    @Override
     public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
         return accessPolicyProvider.getAccessPolicies();
     }
 
     @AuthorizerContext
-    public void setNiFiProperties(NiFiRegistryProperties properties) {
-        userGroupProvider.setNiFiProperties(properties);
-        accessPolicyProvider.setNiFiProperties(properties);
+    public void setIdentityMapper(final IdentityMapper identityMapper) {
+        userGroupProvider.setIdentityMapper(identityMapper);
+        accessPolicyProvider.setIdentityMapper(identityMapper);
     }
 
     @Override
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileUserGroupProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileUserGroupProvider.java
index 4736fe2..ba7ca9c 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileUserGroupProvider.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileUserGroupProvider.java
@@ -17,9 +17,6 @@
 package org.apache.nifi.registry.security.authorization.file;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.registry.properties.NiFiRegistryProperties;
-import org.apache.nifi.registry.properties.util.IdentityMapping;
-import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
 import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
 import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
 import org.apache.nifi.registry.security.authorization.Group;
@@ -32,9 +29,10 @@
 import org.apache.nifi.registry.security.authorization.file.tenants.generated.Groups;
 import org.apache.nifi.registry.security.authorization.file.tenants.generated.Tenants;
 import org.apache.nifi.registry.security.authorization.file.tenants.generated.Users;
+import org.apache.nifi.registry.security.authorization.util.UserGroupProviderUtils;
 import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
 import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
-import org.apache.nifi.registry.util.FileUtils;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.apache.nifi.registry.util.PropertyValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -68,14 +66,10 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
 
@@ -108,17 +102,12 @@
     private static final String IDENTITY_ATTR = "identity";
     private static final String NAME_ATTR = "name";
 
-    static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User Identity ";
     static final String PROP_TENANTS_FILE = "Users File";
-    static final Pattern INITIAL_USER_IDENTITY_PATTERN = Pattern.compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+");
 
-    private Schema usersSchema;
     private Schema tenantsSchema;
-    private NiFiRegistryProperties properties;
     private File tenantsFile;
-    private File restoreTenantsFile;
     private Set<String> initialUserIdentities;
-    private List<IdentityMapping> identityMappings;
+    private IdentityMapper identityMapper;
 
     private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>();
 
@@ -127,7 +116,6 @@
         try {
             final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
             tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD));
-            //usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
         } catch (Exception e) {
             throw new SecurityProviderCreationException(e);
         }
@@ -148,29 +136,13 @@
                 saveTenants(new Tenants());
             }
 
-            final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile();
-
-            // extract the identity mappings from nifi-registry.properties if any are provided
-            identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
-
             // extract any nifi identities
-            initialUserIdentities = new HashSet<>();
-            for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
-                Matcher matcher = INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey());
-                if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
-                    initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
-                }
-            }
+            initialUserIdentities = UserGroupProviderUtils.getInitialUserIdentities(configurationContext, identityMapper);
 
             load();
 
-            // if we've copied the authorizations file to a restore directory synchronize it
-            if (restoreTenantsFile != null) {
-                FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger);
-            }
-
             logger.info(String.format("Users/Groups file loaded at %s", new Date().toString()));
-        } catch (IOException | SecurityProviderCreationException | JAXBException | IllegalStateException | SAXException e) {
+        } catch (SecurityProviderCreationException | JAXBException | IllegalStateException | SAXException e) {
             throw new SecurityProviderCreationException(e);
         }
     }
@@ -257,8 +229,7 @@
         return deleteUser(user.getIdentifier());
     }
 
-    @Override
-    public synchronized User deleteUser(String userIdentifier) throws AuthorizationAccessException {
+    private synchronized User deleteUser(String userIdentifier) throws AuthorizationAccessException {
         if (userIdentifier == null) {
             throw new IllegalArgumentException("User identifier cannot be null");
         }
@@ -404,8 +375,7 @@
         return deleteGroup(group.getIdentifier());
     }
 
-    @Override
-    public synchronized Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
+    private synchronized Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
         if (groupIdentifier == null) {
             throw new IllegalArgumentException("Group identifier cannot be null");
         }
@@ -436,8 +406,8 @@
     }
 
     @AuthorizerContext
-    public void setNiFiProperties(NiFiRegistryProperties properties) {
-        this.properties = properties;
+    public void setIdentityMapper(final IdentityMapper identityMapper) {
+        this.identityMapper = identityMapper;
     }
 
     @Override
@@ -662,10 +632,10 @@
         }
 
         if (foundUser == null) {
-            final String userIdentifier = IdentifierUtil.getIdentifier(userIdentity);
+            final User newUser = new User.Builder().identifierGenerateFromSeed(userIdentity).identity(userIdentity).build();
             foundUser = new org.apache.nifi.registry.security.authorization.file.tenants.generated.User();
-            foundUser.setIdentifier(userIdentifier);
-            foundUser.setIdentity(userIdentity);
+            foundUser.setIdentifier(newUser.getIdentifier());
+            foundUser.setIdentity(newUser.getIdentity());
             tenants.getUsers().getUser().add(foundUser);
         }
 
@@ -693,10 +663,10 @@
         }
 
         if (foundGroup == null) {
-            final String newGroupIdentifier = IdentifierUtil.getIdentifier(groupName);
+            final Group newGroup = new Group.Builder().identifierGenerateFromSeed(groupName).name(groupName).build();
             foundGroup = new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group();
-            foundGroup.setIdentifier(newGroupIdentifier);
-            foundGroup.setName(groupName);
+            foundGroup.setIdentifier(newGroup.getIdentifier());
+            foundGroup.setName(newGroup.getName());
             tenants.getGroups().getGroup().add(foundGroup);
         }
 
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/AccessPolicyProviderUtils.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/AccessPolicyProviderUtils.java
new file mode 100644
index 0000000..890626d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/AccessPolicyProviderUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.security.authorization.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.UserGroupProvider;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
+import org.apache.nifi.registry.util.PropertyValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods related to access policies for use by various {@link org.apache.nifi.registry.security.authorization.AccessPolicyProvider} implementations.
+ */
+public final class AccessPolicyProviderUtils {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AccessPolicyProviderUtils.class);
+
+    /**
+     * The prefix of a property from an AuthorizerConfigurationContext that specifies a NiFi Identity.
+     */
+    public static final String PROP_NIFI_IDENTITY_PREFIX = "NiFi Identity ";
+
+    /**
+     * The name of the property from an AuthorizerConfigurationContext that specifies the initial admin identity.
+     */
+    public static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
+
+    /**
+     * A Pattern for identifying properties that represent NiFi Identities.
+     */
+    public static final Pattern NIFI_IDENTITY_PATTERN = Pattern.compile(PROP_NIFI_IDENTITY_PREFIX + "\\S+");
+
+    /**
+     * The name of the property from AuthorizerConfigurationContext that specifies a name of a group for NiFi Identities.
+     */
+    public static final String PROP_NIFI_GROUP_NAME = "NiFi Group Name";
+
+    /**
+     * Returns the value of the 'Initial Admin Identity' property with any identity mappings applied.
+     *
+     * @param configurationContext the configuration context
+     * @param identityMapper the identity mapper
+     * @return the value for the initial admin identity
+     */
+    public static String getInitialAdminIdentity(final AuthorizerConfigurationContext configurationContext, final IdentityMapper identityMapper) {
+        final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
+        return initialAdminIdentityProp.isSet() ? identityMapper.mapUser(initialAdminIdentityProp.getValue()) : null;
+    }
+
+    /**
+     * Returns the values for the 'NiFi Identity' properties with any identity mappings applied.
+     *
+     * @param configurationContext the configuration context
+     * @param identityMapper the identity mapper
+     * @return the values for the NiFi identities
+     */
+    public static Set<String> getNiFiIdentities(final AuthorizerConfigurationContext configurationContext, final IdentityMapper identityMapper) {
+        final Set<String> nifiIdentities = new HashSet<>();
+
+        for (final Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
+            final Matcher matcher = NIFI_IDENTITY_PATTERN.matcher(entry.getKey());
+            if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+                nifiIdentities.add(identityMapper.mapUser(entry.getValue()));
+            }
+        }
+
+        return nifiIdentities;
+    }
+
+    /**
+     * Returns the value for the property 'NiFi Group Name' from the given configuration context.
+     *
+     * @param configurationContext the configuration context
+     * @return the group name, or null if not specified
+     */
+    public static String getNiFiGroupName(final AuthorizerConfigurationContext configurationContext, final IdentityMapper identityMapper) {
+        final PropertyValue nifiGroupNameProp = configurationContext.getProperty(PROP_NIFI_GROUP_NAME);
+        final String nifiGroupName = (nifiGroupNameProp != null && nifiGroupNameProp.isSet()) ? nifiGroupNameProp.getValue() : null;
+
+        if (StringUtils.isBlank(nifiGroupName)) {
+            LOGGER.debug("NiFi Group Name was not specified");
+            return null;
+        }
+
+        return identityMapper.mapGroup(nifiGroupName);
+    }
+
+    /**
+     * Returns the identifier of the group with the given group name.
+     *
+     * If no group exists with the given name then SecurityProviderCreationException is thrown.
+     *
+     * @param groupName the group name
+     * @param userGroupProvider the UserGroupProvider
+     * @return the identifier of the group, or null
+     */
+    public static Group getGroup(final String groupName, final UserGroupProvider userGroupProvider) {
+        final Set<Group> groups = userGroupProvider.getGroups();
+        LOGGER.trace("All groups: {}", groups);
+
+        final Optional<Group> groupOptional = groups.stream()
+                .filter(group -> group.getName().equals(groupName))
+                .findFirst();
+
+        final Group group = groupOptional.orElseThrow(() ->
+                        new SecurityProviderCreationException(
+                                String.format("Group '%s' could not be found", groupName))
+        );
+
+        LOGGER.debug("Group identifier is: {}", group);
+        return group;
+    }
+
+    private AccessPolicyProviderUtils() {
+
+    }
+
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/InitialPolicies.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/InitialPolicies.java
new file mode 100644
index 0000000..5f8c20e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/InitialPolicies.java
@@ -0,0 +1,99 @@
+/*
+ * 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.security.authorization.util;
+
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.resource.ResourceFactory;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Defines the initial policies to be created for the initial users.
+ */
+public final class InitialPolicies {
+
+    // Resource-actions pairs used by initial admins and NiFi identities
+
+    public static final ResourceAndAction TENANTS_READ = new ResourceAndAction(ResourceFactory.getTenantsResource(), RequestAction.READ);
+    public static final ResourceAndAction TENANTS_WRITE = new ResourceAndAction(ResourceFactory.getTenantsResource(), RequestAction.WRITE);
+    public static final ResourceAndAction TENANTS_DELETE = new ResourceAndAction(ResourceFactory.getTenantsResource(), RequestAction.DELETE);
+
+    public static final ResourceAndAction POLICIES_READ = new ResourceAndAction(ResourceFactory.getPoliciesResource(), RequestAction.READ);
+    public static final ResourceAndAction POLICIES_WRITE = new ResourceAndAction(ResourceFactory.getPoliciesResource(), RequestAction.WRITE);
+    public static final ResourceAndAction POLICIES_DELETE = new ResourceAndAction(ResourceFactory.getPoliciesResource(), RequestAction.DELETE);
+
+    public static final ResourceAndAction BUCKETS_READ = new ResourceAndAction(ResourceFactory.getBucketsResource(), RequestAction.READ);
+    public static final ResourceAndAction BUCKETS_WRITE = new ResourceAndAction(ResourceFactory.getBucketsResource(), RequestAction.WRITE);
+    public static final ResourceAndAction BUCKETS_DELETE = new ResourceAndAction(ResourceFactory.getBucketsResource(), RequestAction.DELETE);
+
+    public static final ResourceAndAction ACTUATOR_READ = new ResourceAndAction(ResourceFactory.getActuatorResource(), RequestAction.READ);
+    public static final ResourceAndAction ACTUATOR_WRITE = new ResourceAndAction(ResourceFactory.getActuatorResource(), RequestAction.WRITE);
+    public static final ResourceAndAction ACTUATOR_DELETE = new ResourceAndAction(ResourceFactory.getActuatorResource(), RequestAction.DELETE);
+
+    public static final ResourceAndAction SWAGGER_READ = new ResourceAndAction(ResourceFactory.getSwaggerResource(), RequestAction.READ);
+    public static final ResourceAndAction SWAGGER_WRITE = new ResourceAndAction(ResourceFactory.getSwaggerResource(), RequestAction.WRITE);
+    public static final ResourceAndAction SWAGGER_DELETE = new ResourceAndAction(ResourceFactory.getSwaggerResource(), RequestAction.DELETE);
+
+    public static final ResourceAndAction PROXY_READ = new ResourceAndAction(ResourceFactory.getProxyResource(), RequestAction.READ);
+    public static final ResourceAndAction PROXY_WRITE = new ResourceAndAction(ResourceFactory.getProxyResource(), RequestAction.WRITE);
+    public static final ResourceAndAction PROXY_DELETE = new ResourceAndAction(ResourceFactory.getProxyResource(), RequestAction.DELETE);
+
+    /**
+     * Resource-action pairs to create policies for an initial admin.
+     */
+    public static final Set<ResourceAndAction> ADMIN_POLICIES = Collections.unmodifiableSet(
+            new HashSet<>(Arrays.asList(
+                    TENANTS_READ,
+                    TENANTS_WRITE,
+                    TENANTS_DELETE,
+                    POLICIES_READ,
+                    POLICIES_WRITE,
+                    POLICIES_DELETE,
+                    BUCKETS_READ,
+                    BUCKETS_WRITE,
+                    BUCKETS_DELETE,
+                    ACTUATOR_READ,
+                    ACTUATOR_WRITE,
+                    ACTUATOR_DELETE,
+                    SWAGGER_READ,
+                    SWAGGER_WRITE,
+                    SWAGGER_DELETE,
+                    PROXY_READ,
+                    PROXY_WRITE,
+                    PROXY_DELETE
+            ))
+    );
+
+    /**
+     * Resource-action paris to create policies for initial NiFi users.
+     */
+    public static final Set<ResourceAndAction> NIFI_POLICIES = Collections.unmodifiableSet(
+            new HashSet<>(Arrays.asList(
+                    BUCKETS_READ,
+                    PROXY_READ,
+                    PROXY_WRITE,
+                    PROXY_DELETE
+            ))
+    );
+
+    private InitialPolicies() {
+
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/ResourceAndAction.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/ResourceAndAction.java
new file mode 100644
index 0000000..ed11c86
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/ResourceAndAction.java
@@ -0,0 +1,61 @@
+/*
+ * 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.security.authorization.util;
+
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.Resource;
+
+import java.util.Objects;
+
+public class ResourceAndAction {
+
+    private final Resource resource;
+
+    private final RequestAction action;
+
+    public ResourceAndAction(final Resource resource, final RequestAction action) {
+        this.resource = Objects.requireNonNull(resource);
+        this.action = Objects.requireNonNull(action);
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public RequestAction getAction() {
+        return action;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final ResourceAndAction that = (ResourceAndAction) o;
+        return Objects.equals(resource, that.resource) && action == that.action;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(resource, action);
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/UserGroupProviderUtils.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/UserGroupProviderUtils.java
new file mode 100644
index 0000000..beb415c
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/util/UserGroupProviderUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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.security.authorization.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods related to access policies for use by various {@link org.apache.nifi.registry.security.authorization.UserGroupProvider} implementations.
+ */
+public final class UserGroupProviderUtils {
+
+    public static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User Identity ";
+    public static final Pattern INITIAL_USER_IDENTITY_PATTERN = Pattern.compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+");
+
+    public static Set<String> getInitialUserIdentities(final AuthorizerConfigurationContext configurationContext, final IdentityMapper identityMapper) {
+        final Set<String> initialUserIdentities = new HashSet<>();
+        for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
+            Matcher matcher = UserGroupProviderUtils.INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey());
+            if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+                initialUserIdentities.add(identityMapper.mapUser(entry.getValue()));
+            }
+        }
+        return initialUserIdentities;
+    }
+
+    private UserGroupProviderUtils() {
+
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/identity/DefaultIdentityMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/identity/DefaultIdentityMapper.java
new file mode 100644
index 0000000..8bf2dc9
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/identity/DefaultIdentityMapper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.security.identity;
+
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.properties.util.IdentityMapping;
+import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.List;
+
+@Component
+public class DefaultIdentityMapper implements IdentityMapper {
+
+    final List<IdentityMapping> userIdentityMappings;
+    final List<IdentityMapping> groupMappings;
+
+    @Autowired
+    public DefaultIdentityMapper(final NiFiRegistryProperties properties) {
+        userIdentityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+        groupMappings = Collections.unmodifiableList(IdentityMappingUtil.getGroupMappings(properties));
+    }
+
+    @Override
+    public String mapUser(final String userIdentity) {
+        return IdentityMappingUtil.mapIdentity(userIdentity, userIdentityMappings);
+    }
+
+    @Override
+    public String mapGroup(final String groupName) {
+        return IdentityMappingUtil.mapIdentity(groupName, groupMappings);
+    }
+
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/identity/IdentityMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/identity/IdentityMapper.java
new file mode 100644
index 0000000..e5e3974
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/identity/IdentityMapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.security.identity;
+
+
+public interface IdentityMapper {
+
+    /**
+     * Maps the given user identity to a target identity.
+     * If the given identity does not match configured patters, the input is returned.
+     *
+     * @param userIdentity the identity to map
+     * @return the mapped identity, or the same identity if no mappings matched
+     */
+    String mapUser(String userIdentity);
+
+    /**
+     * Maps the given group name to a target group name.
+     * If the given group name does not match configured patters, the input is returned.
+     *
+     * @param groupName the group name to map
+     * @return the mapped group name, or the same group name if no mappings matched
+     */
+    String mapGroup(String groupName);
+
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/ldap/tenants/LdapUserGroupProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/ldap/tenants/LdapUserGroupProvider.java
index 23e350f..8ecd56d 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/ldap/tenants/LdapUserGroupProvider.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/ldap/tenants/LdapUserGroupProvider.java
@@ -17,9 +17,6 @@
 package org.apache.nifi.registry.security.ldap.tenants;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.registry.properties.NiFiRegistryProperties;
-import org.apache.nifi.registry.properties.util.IdentityMapping;
-import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
 import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
 import org.apache.nifi.registry.security.authorization.Group;
 import org.apache.nifi.registry.security.authorization.User;
@@ -30,6 +27,7 @@
 import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
 import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
 import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.apache.nifi.registry.security.ldap.LdapAuthenticationStrategy;
 import org.apache.nifi.registry.security.ldap.LdapsSocketFactory;
 import org.apache.nifi.registry.security.ldap.ReferralStrategy;
@@ -69,7 +67,6 @@
 import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -116,9 +113,7 @@
 
     public static final String PROP_SYNC_INTERVAL = "Sync Interval";
 
-    private List<IdentityMapping> identityMappings;
-    private List<IdentityMapping> groupMappings;
-    private NiFiRegistryProperties properties;
+    private IdentityMapper identityMapper;
 
     private ScheduledExecutorService ldapSync;
     private AtomicReference<TenantHolder> tenants = new AtomicReference<>(null);
@@ -355,10 +350,6 @@
         final String rawGroupMembershipEnforceCaseSensitivity = configurationContext.getProperty(PROP_GROUP_MEMBERSHIP_ENFORCE_CASE_SENSITIVITY).getValue();
         groupMembershipEnforceCaseSensitivity = Boolean.parseBoolean(rawGroupMembershipEnforceCaseSensitivity);
 
-        // extract the identity mappings from nifi-registry.properties if any are provided
-        identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
-        groupMappings = Collections.unmodifiableList(IdentityMappingUtil.getGroupMappings(properties));
-
         // set the base environment is necessary
         if (!baseEnvironment.isEmpty()) {
             context.setBaseEnvironmentProperties(baseEnvironment);
@@ -614,7 +605,7 @@
                                                 final String userIdentity;
                                                 if (useDnForUserIdentity) {
                                                     // use the user value to avoid the unnecessary look up
-                                                    userIdentity = IdentityMappingUtil.mapIdentity(userDn, identityMappings);
+                                                    userIdentity = identityMapper.mapUser(userDn);
                                                 } else {
                                                     // lookup the user to extract the user identity
                                                     userIdentity = getUserIdentity((DirContextAdapter) ldapTemplate.lookup(userDn));
@@ -661,7 +652,7 @@
                     final String groupName;
                     if (useDnForGroupName) {
                         // use the dn to avoid the unnecessary look up
-                        groupName = IdentityMappingUtil.mapIdentity(groupDn, groupMappings);
+                        groupName = identityMapper.mapGroup(groupDn);
                     } else {
                         groupName = getGroupName((DirContextAdapter) ldapTemplate.lookup(groupDn));
                     }
@@ -716,7 +707,7 @@
             }
         }
 
-        return IdentityMappingUtil.mapIdentity(identity, identityMappings);
+        return identityMapper.mapUser(identity);
     }
 
     private String getReferencedUserValue(final DirContextOperations ctx) {
@@ -758,7 +749,7 @@
             }
         }
 
-        return IdentityMappingUtil.mapIdentity(name, groupMappings);
+        return identityMapper.mapGroup(name);
     }
 
     private String getReferencedGroupValue(final DirContextOperations ctx) {
@@ -783,8 +774,8 @@
     }
 
     @AuthorizerContext
-    public void setNiFiProperties(NiFiRegistryProperties properties) {
-        this.properties = properties;
+    public void setIdentityMapper(final IdentityMapper identityMapper) {
+        this.identityMapper = identityMapper;
     }
 
     @Override
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java
index 503f27c..1a348c3 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java
@@ -457,7 +457,7 @@
             throw new ResourceNotFoundException("The specified policy does not exist in this registry.");
         }
 
-        configurableAccessPolicyProvider().deleteAccessPolicy(identifier);
+        configurableAccessPolicyProvider().deleteAccessPolicy(accessPolicy);
         return accessPolicyToDTO(accessPolicy);
     }
 
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.AccessPolicyProvider b/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.AccessPolicyProvider
index f57163f..29d7175 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.AccessPolicyProvider
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.AccessPolicyProvider
@@ -12,4 +12,5 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-org.apache.nifi.registry.security.authorization.file.FileAccessPolicyProvider
\ No newline at end of file
+org.apache.nifi.registry.security.authorization.file.FileAccessPolicyProvider
+org.apache.nifi.registry.security.authorization.database.DatabaseAccessPolicyProvider
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.UserGroupProvider b/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.UserGroupProvider
index a4ac129..73ec0cf 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.UserGroupProvider
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.UserGroupProvider
@@ -16,4 +16,5 @@
 org.apache.nifi.registry.security.authorization.CompositeConfigurableUserGroupProvider
 org.apache.nifi.registry.security.authorization.file.FileUserGroupProvider
 org.apache.nifi.registry.security.ldap.tenants.LdapUserGroupProvider
+org.apache.nifi.registry.security.authorization.database.DatabaseUserGroupProvider
 org.apache.nifi.registry.security.authorization.shell.ShellUserGroupProvider
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V8__AddUserGroupPolicy.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V8__AddUserGroupPolicy.sql
new file mode 100644
index 0000000..c734108
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V8__AddUserGroupPolicy.sql
@@ -0,0 +1,63 @@
+-- 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.
+
+-- UserGroupProvider tables
+
+CREATE TABLE UGP_USER (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    IDENTITY VARCHAR(4096) NOT NULL,
+    CONSTRAINT PK__UGP_USER_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__UGP_USER_IDENTITY UNIQUE (IDENTITY)
+);
+
+CREATE TABLE UGP_GROUP (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    IDENTITY VARCHAR(4096) NOT NULL,
+    CONSTRAINT PK__UGP_GROUP_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__UGP_GROUP_IDENTITY UNIQUE (IDENTITY)
+);
+
+-- There is no FK constraint from USER_IDENTIFIER to the UGP_USER table because users from multiple providers may be
+-- put into a group here, so it may not always be a user from the UGP_USER table
+CREATE TABLE UGP_USER_GROUP (
+    USER_IDENTIFIER VARCHAR(50) NOT NULL,
+    GROUP_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__UGP_USER_GROUP PRIMARY KEY (USER_IDENTIFIER, GROUP_IDENTIFIER),
+    CONSTRAINT FK__UGP_USER_GROUP_GROUP_IDENTIFIER FOREIGN KEY (GROUP_IDENTIFIER) REFERENCES UGP_GROUP(IDENTIFIER) ON DELETE CASCADE
+);
+
+-- AccessPolicyProvider tables
+
+CREATE TABLE APP_POLICY (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    RESOURCE VARCHAR(1000) NOT NULL,
+    ACTION VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__APP_POLICY_RESOURCE_ACTION UNIQUE (RESOURCE, ACTION)
+);
+
+CREATE TABLE APP_POLICY_USER (
+    POLICY_IDENTIFIER VARCHAR(50) NOT NULL,
+    USER_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_USER PRIMARY KEY (POLICY_IDENTIFIER, USER_IDENTIFIER),
+    CONSTRAINT FK__APP_POLICY_USER_POLICY_IDENTIFIER FOREIGN KEY (POLICY_IDENTIFIER) REFERENCES APP_POLICY(IDENTIFIER) ON DELETE CASCADE
+);
+
+CREATE TABLE APP_POLICY_GROUP (
+    POLICY_IDENTIFIER VARCHAR(50) NOT NULL,
+    GROUP_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_GROUP PRIMARY KEY (POLICY_IDENTIFIER, GROUP_IDENTIFIER),
+    CONSTRAINT FK__APP_POLICY_GROUP_POLICY_IDENTIFIER FOREIGN KEY (POLICY_IDENTIFIER) REFERENCES APP_POLICY(IDENTIFIER) ON DELETE CASCADE
+);
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V8__AddUserGroupPolicy.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V8__AddUserGroupPolicy.sql
new file mode 100644
index 0000000..495dd73
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V8__AddUserGroupPolicy.sql
@@ -0,0 +1,63 @@
+-- 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.
+
+-- UserGroupProvider tables
+
+CREATE TABLE UGP_USER (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    IDENTITY VARCHAR(767) NOT NULL,
+    CONSTRAINT PK__UGP_USER_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__UGP_USER_IDENTITY UNIQUE (IDENTITY)
+);
+
+CREATE TABLE UGP_GROUP (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    IDENTITY VARCHAR(767) NOT NULL,
+    CONSTRAINT PK__UGP_GROUP_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__UGP_GROUP_IDENTITY UNIQUE (IDENTITY)
+);
+
+-- There is no FK constraint from USER_IDENTIFIER to the UGP_USER table because users from multiple providers may be
+-- put into a group here, so it may not always be a user from the UGP_USER table
+CREATE TABLE UGP_USER_GROUP (
+    USER_IDENTIFIER VARCHAR(50) NOT NULL,
+    GROUP_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__UGP_USER_GROUP PRIMARY KEY (USER_IDENTIFIER, GROUP_IDENTIFIER),
+    CONSTRAINT FK__UGP_USER_GROUP_GROUP_IDENTIFIER FOREIGN KEY (GROUP_IDENTIFIER) REFERENCES UGP_GROUP(IDENTIFIER) ON DELETE CASCADE
+);
+
+-- AccessPolicyProvider tables
+
+CREATE TABLE APP_POLICY (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    RESOURCE VARCHAR(700) NOT NULL,
+    ACTION VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__APP_POLICY_RESOURCE_ACTION UNIQUE (RESOURCE, ACTION)
+);
+
+CREATE TABLE APP_POLICY_USER (
+    POLICY_IDENTIFIER VARCHAR(50) NOT NULL,
+    USER_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_USER PRIMARY KEY (POLICY_IDENTIFIER, USER_IDENTIFIER),
+    CONSTRAINT FK__APP_POLICY_USER_POLICY_IDENTIFIER FOREIGN KEY (POLICY_IDENTIFIER) REFERENCES APP_POLICY(IDENTIFIER) ON DELETE CASCADE
+);
+
+CREATE TABLE APP_POLICY_GROUP (
+    POLICY_IDENTIFIER VARCHAR(50) NOT NULL,
+    GROUP_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_GROUP PRIMARY KEY (POLICY_IDENTIFIER, GROUP_IDENTIFIER),
+    CONSTRAINT FK__APP_POLICY_GROUP_POLICY_IDENTIFIER FOREIGN KEY (POLICY_IDENTIFIER) REFERENCES APP_POLICY(IDENTIFIER) ON DELETE CASCADE
+);
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/postgres/V8__AddUserGroupPolicy.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/postgres/V8__AddUserGroupPolicy.sql
new file mode 100644
index 0000000..344069e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/postgres/V8__AddUserGroupPolicy.sql
@@ -0,0 +1,63 @@
+-- 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.
+
+-- UserGroupProvider tables
+
+CREATE TABLE UGP_USER (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    IDENTITY VARCHAR(4096) NOT NULL,
+    CONSTRAINT PK__UGP_USER_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__UGP_USER_IDENTITY UNIQUE (IDENTITY)
+);
+
+CREATE TABLE UGP_GROUP (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    IDENTITY VARCHAR(4096) NOT NULL,
+    CONSTRAINT PK__UGP_GROUP_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__UGP_GROUP_IDENTITY UNIQUE (IDENTITY)
+);
+
+-- There is no FK constraint from USER_IDENTIFIER to the UGP_USER table because users from multiple providers may be
+-- put into a group here, so it may not always be a user from the UGP_USER table
+CREATE TABLE UGP_USER_GROUP (
+    USER_IDENTIFIER VARCHAR(50) NOT NULL,
+    GROUP_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__UGP_USER_GROUP PRIMARY KEY (USER_IDENTIFIER, GROUP_IDENTIFIER),
+    CONSTRAINT FK__UGP_USER_GROUP_GROUP_IDENTIFIER FOREIGN KEY (GROUP_IDENTIFIER) REFERENCES UGP_GROUP(IDENTIFIER) ON DELETE CASCADE
+);
+
+-- AccessPolicyProvider tables
+
+CREATE TABLE APP_POLICY (
+    IDENTIFIER VARCHAR(50) NOT NULL,
+    RESOURCE VARCHAR(1000) NOT NULL,
+    ACTION VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_IDENTIFIER PRIMARY KEY (IDENTIFIER),
+    CONSTRAINT UNIQUE__APP_POLICY_RESOURCE_ACTION UNIQUE (RESOURCE, ACTION)
+);
+
+CREATE TABLE APP_POLICY_USER (
+    POLICY_IDENTIFIER VARCHAR(50) NOT NULL,
+    USER_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_USER PRIMARY KEY (POLICY_IDENTIFIER, USER_IDENTIFIER),
+    CONSTRAINT FK__APP_POLICY_POLICY_IDENTIFIER FOREIGN KEY (POLICY_IDENTIFIER) REFERENCES APP_POLICY(IDENTIFIER) ON DELETE CASCADE
+);
+
+CREATE TABLE APP_POLICY_GROUP (
+    POLICY_IDENTIFIER VARCHAR(50) NOT NULL,
+    GROUP_IDENTIFIER VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__APP_POLICY_GROUP PRIMARY KEY (POLICY_IDENTIFIER, GROUP_IDENTIFIER),
+    CONSTRAINT FK__APP_POLICY_POLICY_IDENTIFIER FOREIGN KEY (POLICY_IDENTIFIER) REFERENCES APP_POLICY(IDENTIFIER) ON DELETE CASCADE
+);
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
index bd79e55..60a1084 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
+++ b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
@@ -20,14 +20,19 @@
 import org.apache.nifi.registry.extension.ExtensionManager
 import org.apache.nifi.registry.properties.NiFiRegistryProperties
 import org.apache.nifi.registry.security.authorization.resource.ResourceFactory
+import org.apache.nifi.registry.security.identity.IdentityMapper
 import org.apache.nifi.registry.service.RegistryService
 import spock.lang.Specification
 
+import javax.sql.DataSource
+
 class AuthorizerFactorySpec extends Specification {
 
     def mockProperties = Mock(NiFiRegistryProperties)
     def mockExtensionManager = Mock(ExtensionManager)
     def mockRegistryService = Mock(RegistryService)
+    def mockDataSource = Mock(DataSource)
+    def mockIdentityMapper = Mock(IdentityMapper)
 
     AuthorizerFactory authorizerFactory
 
@@ -36,7 +41,7 @@
         mockExtensionManager.getExtensionClassLoader(_) >> new ExtensionClassLoader("/tmp", new URL[0],this.getClass().getClassLoader())
         mockProperties.getPropertyKeys() >> new HashSet<String>() // Called by IdentityMappingUtil.getIdentityMappings()
 
-        authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null, mockRegistryService)
+        authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null, mockRegistryService, mockDataSource, mockIdentityMapper)
     }
 
     // runs after every feature method
@@ -89,7 +94,7 @@
 
         when: "a bad configuration is provided and getAuthorizer() is called"
         setMockPropsAuthorizersConfig(authorizersConfigFile, selectedAuthorizer)
-        authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null, mockRegistryService)
+        authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null, mockRegistryService, mockDataSource, mockIdentityMapper)
         authorizerFactory.getAuthorizer()
 
         then: "expect an exception"
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
index 8035bd8..8388262 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
+++ b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
@@ -484,33 +484,25 @@
     def "delete access policy"() {
 
         setup:
+        def policy1 = new AuthAccessPolicy.Builder()
+                .identifier("policy1")
+                .resource("/resource")
+                .action(RequestAction.READ)
+                .addGroups(new HashSet<String>())
+                .addUsers(new HashSet<String>())
+                .build()
+
         userGroupProvider.getGroups() >> new HashSet<Group>()
         userGroupProvider.getUsers() >> new HashSet<AuthUser>()
-        accessPolicyProvider.getAccessPolicy("id") >> {
-            String id -> new AuthAccessPolicy.Builder()
-                    .identifier("id")
-                    .resource("/resource")
-                    .action(RequestAction.READ)
-                    .addGroups(new HashSet<String>())
-                    .addUsers(new HashSet<String>())
-                    .build()
-        }
-        accessPolicyProvider.deleteAccessPolicy(!null as String) >> {
-            String id -> new AuthAccessPolicy.Builder()
-                    .identifier(id)
-                    .resource("/resource")
-                    .action(RequestAction.READ)
-                    .addGroups(new HashSet<String>())
-                    .addUsers(new HashSet<String>())
-                    .build()
-        }
+        accessPolicyProvider.getAccessPolicy("id") >> policy1
+        accessPolicyProvider.deleteAccessPolicy(!null as String) >> policy1
 
         when: "access policy is deleted"
         def policy = authorizationService.deleteAccessPolicy("id")
 
         then: "deleted policy is returned"
         with(policy) {
-            identifier == "id"
+            identifier == "policy1"
             resource == "/resource"
             action == RequestAction.READ.toString()
         }
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseAccessPolicyProvider.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseAccessPolicyProvider.java
new file mode 100644
index 0000000..7f522ff
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseAccessPolicyProvider.java
@@ -0,0 +1,496 @@
+/*
+ * 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.security.authorization.database;
+
+import org.apache.nifi.registry.db.DatabaseBaseTest;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.security.authorization.AbstractConfigurableAccessPolicyProvider;
+import org.apache.nifi.registry.security.authorization.AccessPolicy;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.registry.security.authorization.ConfigurableAccessPolicyProvider;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.User;
+import org.apache.nifi.registry.security.authorization.UserGroupProvider;
+import org.apache.nifi.registry.security.authorization.UserGroupProviderLookup;
+import org.apache.nifi.registry.security.authorization.resource.ResourceFactory;
+import org.apache.nifi.registry.security.authorization.util.AccessPolicyProviderUtils;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.security.identity.DefaultIdentityMapper;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
+import org.apache.nifi.registry.util.StandardPropertyValue;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TestDatabaseAccessPolicyProvider extends DatabaseBaseTest {
+
+    private static final String UGP_IDENTIFIER = "mock-user-group-provider";
+
+    private static final User ADMIN_USER = new User.Builder().identifierGenerateRandom().identity("admin").build();
+    private static final User ADMIN_USER2 = new User.Builder().identifierGenerateRandom().identity("admin2").build();
+
+    private static final User NIFI1_USER = new User.Builder().identifierGenerateRandom().identity("nifi1").build();
+    private static final User NIFI2_USER = new User.Builder().identifierGenerateRandom().identity("nifi2").build();
+    private static final User NIFI3_USER = new User.Builder().identifierGenerateRandom().identity("nifi3").build();
+
+    private static final Set<String> NIFI_IDENTITIES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+                    NIFI1_USER.getIdentity(), NIFI2_USER.getIdentity(), NIFI3_USER.getIdentity())));
+
+    private static final Group NIFI_GROUP = new Group.Builder().identifierGenerateRandom().name("nifi-nodes").build();
+    private static final Group OTHERS_GROUP = new Group.Builder().identifierGenerateRandom().name("other-members").build();
+
+    @Autowired
+    private DataSource dataSource;
+    private JdbcTemplate jdbcTemplate;
+    private NiFiRegistryProperties properties;
+    private IdentityMapper identityMapper;
+
+    private UserGroupProviderLookup userGroupProviderLookup;
+    private UserGroupProvider userGroupProvider;
+
+    // Class under test
+    private ConfigurableAccessPolicyProvider policyProvider;
+
+    @Before
+    public void setup() {
+        properties = new NiFiRegistryProperties();
+        identityMapper = new DefaultIdentityMapper(properties);
+        jdbcTemplate = new JdbcTemplate(dataSource);
+
+        userGroupProvider = mock(UserGroupProvider.class);
+        when(userGroupProvider.getUserByIdentity(ADMIN_USER.getIdentity())).thenReturn(ADMIN_USER);
+        when(userGroupProvider.getUserByIdentity(ADMIN_USER2.getIdentity())).thenReturn(ADMIN_USER2);
+        when(userGroupProvider.getUserByIdentity(NIFI1_USER.getIdentity())).thenReturn(NIFI1_USER);
+        when(userGroupProvider.getUserByIdentity(NIFI2_USER.getIdentity())).thenReturn(NIFI2_USER);
+        when(userGroupProvider.getUserByIdentity(NIFI3_USER.getIdentity())).thenReturn(NIFI3_USER);
+        when(userGroupProvider.getGroups()).thenReturn(Collections.unmodifiableSet(
+                new HashSet<>(Arrays.asList(OTHERS_GROUP, NIFI_GROUP))));
+
+        userGroupProviderLookup = mock(UserGroupProviderLookup.class);
+        when(userGroupProviderLookup.getUserGroupProvider(UGP_IDENTIFIER)).thenReturn(userGroupProvider);
+
+        final AuthorizerInitializationContext initializationContext = mock(AuthorizerInitializationContext.class);
+        when(initializationContext.getUserGroupProviderLookup()).thenReturn(userGroupProviderLookup);
+
+        final DatabaseAccessPolicyProvider databaseProvider = new DatabaseAccessPolicyProvider();
+        databaseProvider.setDataSource(dataSource);
+        databaseProvider.setIdentityMapper(identityMapper);
+        databaseProvider.initialize(initializationContext);
+
+        policyProvider = databaseProvider;
+    }
+
+    private void configure(final String initialAdmin, final Set<String> nifiIdentifies) {
+        configure(initialAdmin, nifiIdentifies, null);
+    }
+
+    /**
+     * Helper method to call onConfigured with a configuration context.
+     *
+     * @param initialAdmin the initial admin identity to put in the context, or null
+     * @param nifiIdentifies the nifi identities to put in the context, or null
+     * @param nifiGroupName the name of the nifi group
+     */
+    private void configure(final String initialAdmin, final Set<String> nifiIdentifies, final String nifiGroupName) {
+        final Map<String,String> properties = new HashMap<>();
+        properties.put(AbstractConfigurableAccessPolicyProvider.PROP_USER_GROUP_PROVIDER, UGP_IDENTIFIER);
+
+        if (initialAdmin != null) {
+            properties.put(AccessPolicyProviderUtils.PROP_INITIAL_ADMIN_IDENTITY, initialAdmin);
+        }
+
+        if (nifiIdentifies != null) {
+            int i = 1;
+            for (final String nifiIdentity : nifiIdentifies) {
+                properties.put(AccessPolicyProviderUtils.PROP_NIFI_IDENTITY_PREFIX + i++, nifiIdentity);
+            }
+        }
+
+        if (nifiGroupName != null) {
+            properties.put(AccessPolicyProviderUtils.PROP_NIFI_GROUP_NAME, nifiGroupName);
+        }
+
+        final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
+        when(configurationContext.getProperties()).thenReturn(properties);
+        when(configurationContext.getProperty(AbstractConfigurableAccessPolicyProvider.PROP_USER_GROUP_PROVIDER))
+                .thenReturn(new StandardPropertyValue(UGP_IDENTIFIER));
+        when(configurationContext.getProperty(AccessPolicyProviderUtils.PROP_INITIAL_ADMIN_IDENTITY))
+                .thenReturn(new StandardPropertyValue(initialAdmin));
+        when(configurationContext.getProperty(AccessPolicyProviderUtils.PROP_NIFI_GROUP_NAME))
+                .thenReturn(new StandardPropertyValue(nifiGroupName));
+        policyProvider.onConfigured(configurationContext);
+    }
+
+    private void configure() {
+        configure(null, null, null);
+    }
+
+    // -- Helper methods for accessing the DB outside of the provider
+
+    private int getPolicyCount() {
+        return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM APP_POLICY", Integer.class);
+    }
+
+    private void createPolicy(final String identifier, final String resource, final RequestAction action) {
+        final String policySql = "INSERT INTO APP_POLICY(IDENTIFIER, RESOURCE, ACTION) VALUES (?, ?, ?)";
+        final int rowsUpdated = jdbcTemplate.update(policySql, identifier, resource, action.toString());
+        assertEquals(1, rowsUpdated);
+    }
+
+    private void addUserToPolicy(final String policyIdentifier, final String userIdentifier) {
+        final String policyUserSql = "INSERT INTO APP_POLICY_USER(POLICY_IDENTIFIER, USER_IDENTIFIER) VALUES (?, ?)";
+        final int rowsUpdated = jdbcTemplate.update(policyUserSql, policyIdentifier, userIdentifier);
+        assertEquals(1, rowsUpdated);
+    }
+
+    private void addGroupToPolicy(final String policyIdentifier, final String groupIdentifier) {
+        final String policyGroupSql = "INSERT INTO APP_POLICY_GROUP(POLICY_IDENTIFIER, GROUP_IDENTIFIER) VALUES (?, ?)";
+        final int rowsUpdated = jdbcTemplate.update(policyGroupSql, policyIdentifier, groupIdentifier);
+        assertEquals(1, rowsUpdated);
+    }
+
+    // -- Test onConfigured
+
+    @Test
+    public void testOnConfiguredCreatesInitialPolicies() {
+        // verify no policies in DB
+        assertEquals(0, getPolicyCount());
+
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES, NIFI_GROUP.getName());
+
+        // verify policies got created for admin and NiFi identities
+        final Set<AccessPolicy> policies = policyProvider.getAccessPolicies();
+        assertNotNull(policies);
+        assertEquals(18, policies.size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenOnlyInitialAdmin() {
+        // verify no policies in DB
+        assertEquals(0, getPolicyCount());
+
+        configure(ADMIN_USER.getIdentity(), null);
+
+        // verify policies got created for admin and NiFi identities
+        final Set<AccessPolicy> policies = policyProvider.getAccessPolicies();
+        assertNotNull(policies);
+        assertEquals(18, policies.size());
+
+        // verify each policy only has the initial admin
+        policies.forEach(p -> {
+            assertNotNull(p.getUsers());
+            assertEquals(1, p.getUsers().size());
+            assertTrue(p.getUsers().contains(ADMIN_USER.getIdentifier()));
+        });
+    }
+
+    @Test
+    public void testOnConfiguredWhenOnlyInitialNiFiIdentities() {
+        // verify no policies in DB
+        assertEquals(0, getPolicyCount());
+
+        configure(null, NIFI_IDENTITIES);
+
+        // verify policies got created for admin and NiFi identities
+        final Set<AccessPolicy> policies = policyProvider.getAccessPolicies();
+        assertNotNull(policies);
+        assertEquals(4, policies.size());
+
+        // verify each policy only has the initial admin
+        policies.forEach(p -> {
+            assertNotNull(p.getUsers());
+            assertEquals(3, p.getUsers().size());
+            assertFalse(p.getUsers().contains(ADMIN_USER.getIdentifier()));
+        });
+    }
+
+    @Test
+    public void testOnConfiguredWhenOnlyNiFiGroupName() {
+        // verify no policies in DB
+        assertEquals(0, getPolicyCount());
+
+        configure(null, null, NIFI_GROUP.getName());
+
+        // verify policies got created for admin and NiFi identities
+        final Set<AccessPolicy> policies = policyProvider.getAccessPolicies();
+        assertNotNull(policies);
+        assertEquals(4, policies.size());
+
+        // verify each policy only has the initial admin
+        policies.forEach(p -> {
+            assertNotNull(p.getGroups());
+            assertEquals(1, p.getGroups().size());
+            assertTrue(p.getGroups().contains(NIFI_GROUP.getIdentifier()));
+        });
+    }
+
+    @Test
+    public void testOnConfiguredStillCreatesInitialPoliciesWhenPoliciesAlreadyExist() {
+        // create one policy
+        createPolicy("policy1", "/foo", RequestAction.READ);
+        assertEquals(1, getPolicyCount());
+
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+        assertTrue(getPolicyCount() > 1);
+    }
+
+    @Test
+    public void testOnConfiguredWhenChangingInitialAdmin() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+        configure("admin2", NIFI_IDENTITIES);
+
+        final AccessPolicy readBuckets = policyProvider.getAccessPolicy(
+                ResourceFactory.getBucketsResource().getIdentifier(), RequestAction.READ);
+        assertNotNull(readBuckets);
+        assertEquals(5, readBuckets.getUsers().size());
+        assertTrue(readBuckets.getUsers().contains(ADMIN_USER.getIdentifier()));
+        assertTrue(readBuckets.getUsers().contains(ADMIN_USER2.getIdentifier()));
+        assertTrue(readBuckets.getUsers().contains(NIFI1_USER.getIdentifier()));
+        assertTrue(readBuckets.getUsers().contains(NIFI2_USER.getIdentifier()));
+        assertTrue(readBuckets.getUsers().contains(NIFI3_USER.getIdentifier()));
+    }
+
+    @Test
+    public void testOnConfiguredAppliesIdentityMappings() {
+        // Set up an identity mapping for kerberos principals
+        properties.setProperty("nifi.registry.security.identity.mapping.pattern.kerb", "^(.*?)@(.*?)$");
+        properties.setProperty("nifi.registry.security.identity.mapping.value.kerb", "$1");
+
+        identityMapper = new DefaultIdentityMapper(properties);
+        ((DatabaseAccessPolicyProvider)policyProvider).setIdentityMapper(identityMapper);
+
+        // Call configure with full admin identity, should get mapped to just 'admin' before looking up user
+        configure("admin@HDF.COM", null);
+
+        // verify policies got created for admin and NiFi identities
+        final Set<AccessPolicy> policies = policyProvider.getAccessPolicies();
+        assertNotNull(policies);
+        assertEquals(18, policies.size());
+
+        // verify each policy only has the initial admin
+        policies.forEach(p -> {
+            assertNotNull(p.getUsers());
+            assertEquals(1, p.getUsers().size());
+            assertTrue(p.getUsers().contains(ADMIN_USER.getIdentifier()));
+        });
+    }
+
+    @Test
+    public void testOnConfiguredAppliesGroupMappings() {
+        // Set up an identity mapping for kerberos principals
+        properties.setProperty("nifi.registry.security.group.mapping.pattern.anyGroup", "^(.*)$");
+        properties.setProperty("nifi.registry.security.group.mapping.value.anyGroup", "$1");
+        properties.setProperty("nifi.registry.security.group.mapping.transform.anyGroup", "LOWER");
+
+        identityMapper = new DefaultIdentityMapper(properties);
+        ((DatabaseAccessPolicyProvider)policyProvider).setIdentityMapper(identityMapper);
+
+        // Call configure with NiFi Group in all uppercase, should get mapped to lower case
+        configure(null, null, NIFI_GROUP.getName().toUpperCase());
+
+        // verify policies got created for admin and NiFi identities
+        final Set<AccessPolicy> policies = policyProvider.getAccessPolicies();
+        assertNotNull(policies);
+        assertEquals(4, policies.size());
+
+        // verify each policy only has the initial admin
+        policies.forEach(p -> {
+            assertNotNull(p.getGroups());
+            assertEquals(1, p.getGroups().size());
+            assertTrue(p.getGroups().contains(NIFI_GROUP.getIdentifier()));
+        });
+    }
+
+    @Test(expected = SecurityProviderCreationException.class)
+    public void testOnConfiguredWhenInitialAdminNotFound() {
+        configure("does-not-exist", null);
+    }
+
+    @Test(expected = SecurityProviderCreationException.class)
+    public void testOnConfiguredWhenNiFiIdentityNotFound() {
+        configure(null, Collections.singleton("does-not-exist"));
+    }
+
+    // -- Test AccessPolicy methods
+
+    @Test
+    public void testAddAccessPolicy() {
+        configure();
+        assertEquals(0, getPolicyCount());
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifierGenerateRandom()
+                .resource("/buckets")
+                .action(RequestAction.READ)
+                .addUser("user1")
+                .addGroup("group1")
+                .build();
+
+        final AccessPolicy createdPolicy = policyProvider.addAccessPolicy(policy);
+        assertNotNull(createdPolicy);
+
+        final AccessPolicy retrievedPolicy = policyProvider.getAccessPolicy(policy.getIdentifier());
+        verifyPoliciesEqual(policy, retrievedPolicy);
+    }
+
+    @Test
+    public void testGetPolicyByIdentifierWhenExists() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+
+        final Set<AccessPolicy> policies = policyProvider.getAccessPolicies();
+        assertNotNull(policies);
+        assertTrue(policies.size() > 0);
+
+        final AccessPolicy existingPolicy = policies.stream().findFirst().get();
+        final AccessPolicy retrievedPolicy = policyProvider.getAccessPolicy(existingPolicy.getIdentifier());
+        verifyPoliciesEqual(existingPolicy, retrievedPolicy);
+    }
+
+    @Test
+    public void testGetPolicyByIdentifierWhenDoesNotExist() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+
+        final AccessPolicy retrievedPolicy = policyProvider.getAccessPolicy("does-not-exist");
+        assertNull(retrievedPolicy);
+    }
+
+    @Test
+    public void testGetPolicyByResourceAndActionWhenExists() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+
+        final Set<AccessPolicy> policies = policyProvider.getAccessPolicies();
+        assertNotNull(policies);
+        assertTrue(policies.size() > 0);
+
+        final AccessPolicy existingPolicy = policies.stream().findFirst().get();
+        final AccessPolicy retrievedPolicy = policyProvider.getAccessPolicy(existingPolicy.getResource(), existingPolicy.getAction());
+        verifyPoliciesEqual(existingPolicy, retrievedPolicy);
+    }
+
+    @Test
+    public void testGetPolicyByResourceAndActionWhenDoesNotExist() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+
+        final AccessPolicy retrievedPolicy = policyProvider.getAccessPolicy("does-not-exist", RequestAction.READ);
+        assertNull(retrievedPolicy);
+    }
+
+    @Test
+    public void testUpdatePolicyWhenExists() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+
+        final AccessPolicy readBucketsPolicy = policyProvider.getAccessPolicy(
+                ResourceFactory.getBucketsResource().getIdentifier(), RequestAction.READ);
+        assertNotNull(readBucketsPolicy);
+        assertEquals(4, readBucketsPolicy.getUsers().size());
+        assertEquals(0, readBucketsPolicy.getGroups().size());
+
+        final AccessPolicy updatedPolicy = new AccessPolicy.Builder(readBucketsPolicy)
+                .addUser("user1")
+                .addGroup("group1")
+                .build();
+
+        final AccessPolicy returnedPolicy = policyProvider.updateAccessPolicy(updatedPolicy);
+        assertNotNull(returnedPolicy);
+
+        final AccessPolicy retrievedPolicy = policyProvider.getAccessPolicy(readBucketsPolicy.getIdentifier());
+        verifyPoliciesEqual(updatedPolicy, retrievedPolicy);
+    }
+
+    @Test
+    public void testUpdatePolicyWhenDoesNotExist() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifierGenerateRandom()
+                .resource("/foo")
+                .action(RequestAction.READ)
+                .addUser("user1")
+                .addGroup("group1")
+                .build();
+
+        final AccessPolicy returnedPolicy = policyProvider.updateAccessPolicy(policy);
+        assertNull(returnedPolicy);
+
+        final AccessPolicy retrievedPolicy = policyProvider.getAccessPolicy(policy.getIdentifier());
+        assertNull(retrievedPolicy);
+    }
+
+    @Test
+    public void testDeletePolicyWhenExists() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+
+        final AccessPolicy readBucketsPolicy = policyProvider.getAccessPolicy(
+                ResourceFactory.getBucketsResource().getIdentifier(), RequestAction.READ);
+        assertNotNull(readBucketsPolicy);
+        assertEquals(4, readBucketsPolicy.getUsers().size());
+        assertEquals(0, readBucketsPolicy.getGroups().size());
+
+        final AccessPolicy deletedPolicy = policyProvider.deleteAccessPolicy(readBucketsPolicy);
+        assertNotNull(deletedPolicy);
+
+        final AccessPolicy retrievedPolicy = policyProvider.getAccessPolicy(readBucketsPolicy.getIdentifier());
+        assertNull(retrievedPolicy);
+    }
+
+    @Test
+    public void testDeletePolicyWhenDoesNotExist() {
+        configure(ADMIN_USER.getIdentity(), NIFI_IDENTITIES);
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifierGenerateRandom()
+                .resource("/foo")
+                .action(RequestAction.READ)
+                .addUser("user1")
+                .addGroup("group1")
+                .build();
+
+        final AccessPolicy deletedPolicy = policyProvider.deleteAccessPolicy(policy);
+        assertNull(deletedPolicy);
+    }
+
+    private void verifyPoliciesEqual(final AccessPolicy policy1, final AccessPolicy policy2) {
+        assertNotNull(policy1);
+        assertNotNull(policy2);
+        assertEquals(policy1.getIdentifier(), policy2.getIdentifier());
+        assertEquals(policy1.getResource(), policy2.getResource());
+        assertEquals(policy1.getAction(), policy2.getAction());
+        assertEquals(policy1.getUsers().size(), policy2.getUsers().size());
+        assertEquals(policy1.getGroups().size(), policy2.getGroups().size());
+    }
+
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseUserGroupProvider.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseUserGroupProvider.java
new file mode 100644
index 0000000..0f8d432
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseUserGroupProvider.java
@@ -0,0 +1,595 @@
+/*
+ * 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.security.authorization.database;
+
+import org.apache.nifi.registry.db.DatabaseBaseTest;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.User;
+import org.apache.nifi.registry.security.authorization.UserAndGroups;
+import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext;
+import org.apache.nifi.registry.security.authorization.util.UserGroupProviderUtils;
+import org.apache.nifi.registry.security.identity.DefaultIdentityMapper;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TestDatabaseUserGroupProvider extends DatabaseBaseTest {
+
+    @Autowired
+    private DataSource dataSource;
+    private NiFiRegistryProperties properties;
+    private IdentityMapper identityMapper;
+
+    private ConfigurableUserGroupProvider userGroupProvider;
+
+    @Before
+    public void setup() {
+        properties = new NiFiRegistryProperties();
+        identityMapper = new DefaultIdentityMapper(properties);
+
+        final DatabaseUserGroupProvider databaseUserGroupProvider = new DatabaseUserGroupProvider();
+        databaseUserGroupProvider.setDataSource(dataSource);
+        databaseUserGroupProvider.setIdentityMapper(identityMapper);
+
+        final UserGroupProviderInitializationContext initializationContext = mock(UserGroupProviderInitializationContext.class);
+        databaseUserGroupProvider.initialize(initializationContext);
+
+        userGroupProvider = databaseUserGroupProvider;
+    }
+
+    /**
+     * Helper method to call onConfigured with a configuration context that has initial users.
+     *
+     * @param initialUserIdentities the initial user identities to place in the configuration context
+     */
+    private void configureWithInitialUsers(final String ... initialUserIdentities) {
+        final Map<String,String> configProperties = new HashMap<>();
+
+        for (int i=0; i < initialUserIdentities.length; i++) {
+            final String initialUserIdentity = initialUserIdentities[i];
+            configProperties.put(UserGroupProviderUtils.PROP_INITIAL_USER_IDENTITY_PREFIX + (i+1), initialUserIdentity);
+        }
+
+        final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
+        when(configurationContext.getProperties()).thenReturn(configProperties);
+
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+    /**
+     * Helper method to create a user outside of the provider.
+     *
+     * @param userIdentifier the user identifier
+     * @param userIdentity the user identity
+     */
+    private void createUser(final String userIdentifier, final String userIdentity) {
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+        final String sql = "INSERT INTO UGP_USER(IDENTIFIER, IDENTITY) VALUES (?, ?)";
+        final int updatedRows1 = jdbcTemplate.update(sql, new Object[] {userIdentifier, userIdentity});
+        assertEquals(1, updatedRows1);
+    }
+
+    /**
+     * Helper method to create a group outside of the provider.
+     *
+     * @param groupIdentifier the group identifier
+     * @param groupIdentity the group identity
+     */
+    private void createGroup(final String groupIdentifier, final String groupIdentity) {
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+        final String sql = "INSERT INTO UGP_GROUP(IDENTIFIER, IDENTITY) VALUES (?, ?)";
+        final int updatedRows1 = jdbcTemplate.update(sql, new Object[] {groupIdentifier, groupIdentity});
+        assertEquals(1, updatedRows1);
+    }
+
+    /**
+     * Helper method to add a user to a group outside of the provider
+     *
+     * @param userIdentifier the user identifier
+     * @param groupIdentifier the group identifier
+     */
+    private void addUserToGroup(final String userIdentifier, final String groupIdentifier) {
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+        final String sql = "INSERT INTO UGP_USER_GROUP(USER_IDENTIFIER, GROUP_IDENTIFIER) VALUES (?, ?)";
+        final int updatedRows1 = jdbcTemplate.update(sql, new Object[] {userIdentifier, groupIdentifier});
+        assertEquals(1, updatedRows1);
+    }
+
+    // -- Test onConfigured
+
+    @Test
+    public void testOnConfiguredCreatesInitialUsersWhenNoUsersAndGroups() {
+        final String userIdentity1 = "user1";
+        final String userIdentity2 = "user2";
+        configureWithInitialUsers(userIdentity1, userIdentity2);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(2, users.size());
+        assertNotNull(users.stream().filter(u -> u.getIdentity().equals(userIdentity1)).findFirst().orElse(null));
+        assertNotNull(users.stream().filter(u -> u.getIdentity().equals(userIdentity2)).findFirst().orElse(null));
+    }
+
+    @Test
+    public void testOnConfiguredStillCreatesInitialUsersWhenExistingUsersAndGroups() {
+        // Create a user in the DB before we call onConfigured
+        final String existingUserIdentity= "existingUser";
+        final String existingUserIdentifier = UUID.randomUUID().toString();
+        createUser(existingUserIdentifier, existingUserIdentity);
+
+        // Call onConfigured with initial users
+        final String userIdentity1 = "user1";
+        final String userIdentity2 = "user2";
+        configureWithInitialUsers(userIdentity1, userIdentity2);
+
+        // Verify the initial users were not created
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(3, users.size());
+        assertNotNull(users.stream().filter(u -> u.getIdentity().equals(existingUserIdentity)).findFirst().orElse(null));
+        assertNotNull(users.stream().filter(u -> u.getIdentity().equals(userIdentity1)).findFirst().orElse(null));
+        assertNotNull(users.stream().filter(u -> u.getIdentity().equals(userIdentity2)).findFirst().orElse(null));
+    }
+
+    @Test
+    public void testOnConfiguredWithSameUsers() {
+        // Create a user in the DB before we call onConfigured
+        final String existingUserIdentity= "existingUser";
+        final String existingUserIdentifier = UUID.randomUUID().toString();
+        createUser(existingUserIdentifier, existingUserIdentity);
+
+        // Call onConfigured with same identity that already exists
+        configureWithInitialUsers(existingUserIdentity);
+
+        // Verify there is only one user
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(1, users.size());
+        assertNotNull(users.stream().filter(u -> u.getIdentity().equals(existingUserIdentity)).findFirst().orElse(null));
+    }
+
+    @Test
+    public void testOnConfiguredAppliesIdentityMappingsToInitialUsers() {
+        // Set up an identity mapping for kerberos principals
+        properties.setProperty("nifi.registry.security.identity.mapping.pattern.kerb", "^(.*?)@(.*?)$");
+        properties.setProperty("nifi.registry.security.identity.mapping.value.kerb", "$1");
+
+        identityMapper = new DefaultIdentityMapper(properties);
+        ((DatabaseUserGroupProvider)userGroupProvider).setIdentityMapper(identityMapper);
+
+        // Call onConfigured with two initial users - one kerberos principal, one DN
+        final String userIdentity1 = "user1@NIFI.COM";
+        final String userIdentity2 = "CN=user2, OU=NIFI";
+        configureWithInitialUsers(userIdentity1, userIdentity2);
+
+        // Verify the kerberos principal had the mapping applied
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(2, users.size());
+        assertNotNull(users.stream().filter(u -> u.getIdentity().equals("user1")).findFirst().orElse(null));
+        assertNotNull(users.stream().filter(u -> u.getIdentity().equals(userIdentity2)).findFirst().orElse(null));
+    }
+
+    // -- Test User Methods
+
+    @Test
+    public void testAddUser() {
+        configureWithInitialUsers();
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(0, users.size());
+
+        final User user1 = new User.Builder()
+                .identifier(UUID.randomUUID().toString())
+                .identity("user1")
+                .build();
+
+        final User createdUser1 = userGroupProvider.addUser(user1);
+        assertNotNull(createdUser1);
+
+        final Set<User> usersAfterCreate = userGroupProvider.getUsers();
+        assertEquals(1, usersAfterCreate.size());
+
+        final User retrievedUser1 = usersAfterCreate.stream().findFirst().get();
+        assertEquals(user1.getIdentifier(), retrievedUser1.getIdentifier());
+        assertEquals(user1.getIdentity(), retrievedUser1.getIdentity());
+    }
+
+    @Test
+    public void testGetUserByIdentifierWhenExists() {
+        configureWithInitialUsers();
+
+        final String userIdentifier = UUID.randomUUID().toString();
+        final String userIdentity = "user1";
+        createUser(userIdentifier, userIdentity);
+
+        final User retrievedUser1 = userGroupProvider.getUser(userIdentifier);
+        assertNotNull(retrievedUser1);
+        assertEquals(userIdentifier, retrievedUser1.getIdentifier());
+        assertEquals(userIdentity, retrievedUser1.getIdentity());
+    }
+
+    @Test
+    public void testGetUserByIdentifierWhenDoesNotExist() {
+        configureWithInitialUsers();
+
+        final User retrievedUser1 = userGroupProvider.getUser("does-not-exist");
+        assertNull(retrievedUser1);
+    }
+
+    @Test
+    public void testGetUserByIdentityWhenExists() {
+        configureWithInitialUsers();
+
+        final String userIdentifier = UUID.randomUUID().toString();
+        final String userIdentity = "user1";
+        createUser(userIdentifier, userIdentity);
+
+        final User retrievedUser1 = userGroupProvider.getUserByIdentity(userIdentity);
+        assertNotNull(retrievedUser1);
+        assertEquals(userIdentifier, retrievedUser1.getIdentifier());
+        assertEquals(userIdentity, retrievedUser1.getIdentity());
+    }
+
+    @Test
+    public void testGetUserByIdentityWhenDoesNotExist() {
+        configureWithInitialUsers();
+
+        final User retrievedUser1 = userGroupProvider.getUserByIdentity("does-not-exist");
+        assertNull(retrievedUser1);
+    }
+
+    @Test
+    public void testGetUserAndGroupsWhenExists() {
+        configureWithInitialUsers();
+
+        // Create some users...
+        final User user1 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user1").build();
+        createUser(user1.getIdentifier(), user1.getIdentity());
+
+        final User user2 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user2").build();
+        createUser(user2.getIdentifier(), user2.getIdentity());
+
+        // Create some groups...
+        final Group group1 = new Group.Builder().identifier(UUID.randomUUID().toString()).name("group1").build();
+        createGroup(group1.getIdentifier(), group1.getName());
+
+        final Group group2 = new Group.Builder().identifier(UUID.randomUUID().toString()).name("group2").build();
+        createGroup(group2.getIdentifier(), group2.getName());
+
+        // Add users to groups...
+        addUserToGroup(user1.getIdentifier(), group1.getIdentifier());
+        addUserToGroup(user2.getIdentifier(), group2.getIdentifier());
+
+        // Retrieve UserAndGroups...
+        final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(user1.getIdentity());
+        assertNotNull(user1AndGroups);
+
+        // Verify retrieved user..
+        final User retrievedUser1 = user1AndGroups.getUser();
+        assertNotNull(retrievedUser1);
+        assertEquals(user1.getIdentifier(), retrievedUser1.getIdentifier());
+        assertEquals(user1.getIdentity(), retrievedUser1.getIdentity());
+
+        // Verify retrieved groups..
+        final Set<Group> user1Groups = user1AndGroups.getGroups();
+        assertNotNull(user1Groups);
+        assertEquals(1, user1Groups.size());
+
+        final Group user1Group = user1Groups.stream().findFirst().get();
+        assertEquals(group1.getIdentifier(), user1Group.getIdentifier());
+        assertEquals(group1.getName(), user1Group.getName());
+
+        assertNotNull(user1Group.getUsers());
+        assertEquals(1, user1Group.getUsers().size());
+        assertTrue(user1Group.getUsers().contains(user1.getIdentifier()));
+    }
+
+    @Test
+    public void testGetUserAndGroupsWhenDoesNotExist() {
+        configureWithInitialUsers();
+
+        final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups("does-not-exist");
+        assertNotNull(userAndGroups);
+        assertNull(userAndGroups.getUser());
+        assertNull(userAndGroups.getGroups());
+    }
+
+    @Test
+    public void testUpdateUserWhenExists() {
+        configureWithInitialUsers();
+
+        final User user1 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user1").build();
+        createUser(user1.getIdentifier(), user1.getIdentity());
+
+        final User retrievedUser1 = userGroupProvider.getUser(user1.getIdentifier());
+        assertNotNull(retrievedUser1);
+
+        final User modifiedUser1 = new User.Builder(retrievedUser1).identity("user1 updated").build();
+
+        final User updatedUser1 = userGroupProvider.updateUser(modifiedUser1);
+        assertNotNull(updatedUser1);
+        assertEquals(modifiedUser1.getIdentity(), updatedUser1.getIdentity());
+    }
+
+    @Test
+    public void testUpdateUserWhenDoesNotExist() {
+        configureWithInitialUsers();
+
+        final User user1 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user1").build();
+        final User updatedUser1 = userGroupProvider.updateUser(user1);
+        assertNull(updatedUser1);
+    }
+
+    @Test
+    public void testDeleteUserWhenExists() {
+        final String user1Identity = "user1";
+        configureWithInitialUsers(user1Identity);
+
+        // verify user1 exists
+        final User retrievedUser1 = userGroupProvider.getUserByIdentity(user1Identity);
+        assertNotNull(retrievedUser1);
+        assertEquals(user1Identity, retrievedUser1.getIdentity());
+
+        // add user1 to a group to test deleting group association when deleting a user
+        final Group group1 = new Group.Builder()
+                .identifier(UUID.randomUUID().toString())
+                .name("group1")
+                .addUser(retrievedUser1.getIdentifier())
+                .build();
+
+        final Group createdGroup1 = userGroupProvider.addGroup(group1);
+        assertNotNull(createdGroup1);
+
+        // delete user1
+        final User deletedUser1 = userGroupProvider.deleteUser(retrievedUser1);
+        assertNotNull(deletedUser1);
+
+        // verify user1 no longer exists
+        final User retrievedUser1AfterDelete = userGroupProvider.getUserByIdentity(user1Identity);
+        assertNull(retrievedUser1AfterDelete);
+
+        // verify user1 no longer a member of group1
+        final Group retrievedGroup1 = userGroupProvider.getGroup(group1.getIdentifier());
+        assertNotNull(retrievedGroup1);
+        assertEquals(0, retrievedGroup1.getUsers().size());
+    }
+
+    @Test
+    public void testDeleteUserWhenDoesNotExist() {
+        configureWithInitialUsers();
+
+        final User user1 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user1").build();
+        final User updatedUser1 = userGroupProvider.deleteUser(user1);
+        assertNull(updatedUser1);
+    }
+
+    // -- Test Group Methods
+
+    @Test
+    public void testAddGroupWithoutUsers() {
+        configureWithInitialUsers();
+
+        final Set<Group> groupsBefore = userGroupProvider.getGroups();
+        assertEquals(0, groupsBefore.size());
+
+        final Group group1 = new Group.Builder().identifier(UUID.randomUUID().toString()).name("group1").build();
+
+        final Group createdGroup1 = userGroupProvider.addGroup(group1);
+        assertNotNull(createdGroup1);
+
+        final Set<Group> groupsAfter = userGroupProvider.getGroups();
+        assertEquals(1, groupsAfter.size());
+
+        final Group retrievedGroup1 = groupsAfter.stream().findFirst().get();
+        assertEquals(group1.getIdentifier(), retrievedGroup1.getIdentifier());
+        assertEquals(group1.getName(), retrievedGroup1.getName());
+        assertNotNull(retrievedGroup1.getUsers());
+        assertEquals(0, retrievedGroup1.getUsers().size());
+    }
+
+    @Test
+    public void testAddGroupWithUsers() {
+        configureWithInitialUsers();
+
+        final String user1Identifier = UUID.randomUUID().toString();
+        final String user1Identity = "user1";
+        createUser(user1Identifier, user1Identity);
+
+        final Set<Group> groupsBefore = userGroupProvider.getGroups();
+        assertEquals(0, groupsBefore.size());
+
+        final Group group1 = new Group.Builder()
+                .identifier(UUID.randomUUID().toString())
+                .name("group1")
+                .addUser(user1Identifier)
+                .build();
+
+        final Group createdGroup1 = userGroupProvider.addGroup(group1);
+        assertNotNull(createdGroup1);
+
+        final Set<Group> groupsAfter = userGroupProvider.getGroups();
+        assertEquals(1, groupsAfter.size());
+
+        final Group retrievedGroup1 = groupsAfter.stream().findFirst().get();
+        assertEquals(group1.getIdentifier(), retrievedGroup1.getIdentifier());
+        assertEquals(group1.getName(), retrievedGroup1.getName());
+        assertNotNull(retrievedGroup1.getUsers());
+        assertEquals(1, retrievedGroup1.getUsers().size());
+        assertTrue(retrievedGroup1.getUsers().contains(user1Identifier));
+    }
+
+    @Test
+    public void testGetGroupWhenExists() {
+        configureWithInitialUsers();
+
+        final String group1Identifier = UUID.randomUUID().toString();
+        final String group1Identity = "group1";
+        createGroup(group1Identifier, group1Identity);
+
+        final Group group1 = userGroupProvider.getGroup(group1Identifier);
+        assertNotNull(group1);
+        assertEquals(group1Identifier, group1.getIdentifier());
+        assertEquals(group1Identity, group1.getName());
+
+        assertNotNull(group1.getUsers());
+        assertEquals(0, group1.getUsers().size());
+    }
+
+    @Test
+    public void testGetGroupWhenDoesNotExist() {
+        configureWithInitialUsers();
+
+        final Group group1 = userGroupProvider.getGroup("does-not-exist");
+        assertNull(group1);
+    }
+
+    @Test
+    public void testUpdateGroupWhenExists() {
+        configureWithInitialUsers();
+
+        // Create some users...
+        final User user1 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user1").build();
+        createUser(user1.getIdentifier(), user1.getIdentity());
+
+        final User user2 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user2").build();
+        createUser(user2.getIdentifier(), user2.getIdentity());
+
+        // Create a group and add user1 to it...
+        final Group group1 = new Group.Builder().identifier(UUID.randomUUID().toString()).name("group1").build();
+        createGroup(group1.getIdentifier(), group1.getName());
+        addUserToGroup(user1.getIdentifier(), group1.getIdentifier());
+
+        // Retrieve the created group...
+        final Group retrievedGroup1 = userGroupProvider.getGroup(group1.getIdentifier());
+        assertNotNull(retrievedGroup1);
+        assertEquals(group1.getName(), retrievedGroup1.getName());
+        assertEquals(1, retrievedGroup1.getUsers().size());
+
+        // Modify the name and add a user...
+        final Group modifiedGroup1 = new Group.Builder(retrievedGroup1)
+                .name(retrievedGroup1.getName() + " updated")
+                .addUser(user2.getIdentifier())
+                .build();
+
+        // Perform the update...
+        final Group updatedGroup1 = userGroupProvider.updateGroup(modifiedGroup1);
+        assertNotNull(updatedGroup1);
+
+        // Re-retrieve and verify the updates were made...
+        final Group retrievedGroup1AfterUpdate = userGroupProvider.getGroup(group1.getIdentifier());
+        assertNotNull(retrievedGroup1AfterUpdate);
+        assertEquals(modifiedGroup1.getName(), retrievedGroup1AfterUpdate.getName());
+        assertEquals(2, retrievedGroup1AfterUpdate.getUsers().size());
+    }
+
+    @Test
+    public void testUpdateGroupWhenDoesNotExist() {
+        configureWithInitialUsers();
+        final Group group1 = new Group.Builder().identifier(UUID.randomUUID().toString()).name("group1").build();
+        final Group updatedGroup1 = userGroupProvider.updateGroup(group1);
+        assertNull(updatedGroup1);
+    }
+
+    @Test
+    public void testUpdateGroupRemoveAllUsers() {
+        configureWithInitialUsers();
+
+        // Create some users...
+        final User user1 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user1").build();
+        createUser(user1.getIdentifier(), user1.getIdentity());
+
+        final User user2 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user2").build();
+        createUser(user2.getIdentifier(), user2.getIdentity());
+
+        // Create a group and add user1 to it...
+        final Group group1 = new Group.Builder().identifier(UUID.randomUUID().toString()).name("group1").build();
+        createGroup(group1.getIdentifier(), group1.getName());
+        addUserToGroup(user1.getIdentifier(), group1.getIdentifier());
+
+        // Retrieve the created group...
+        final Group retrievedGroup1 = userGroupProvider.getGroup(group1.getIdentifier());
+        assertNotNull(retrievedGroup1);
+        assertEquals(group1.getName(), retrievedGroup1.getName());
+        assertEquals(1, retrievedGroup1.getUsers().size());
+
+        // Modify the name and add a user...
+        final Group modifiedGroup1 = new Group.Builder(retrievedGroup1)
+                .name(retrievedGroup1.getName() + " updated")
+                .clearUsers()
+                .build();
+
+        // Perform the update...
+        final Group updatedGroup1 = userGroupProvider.updateGroup(modifiedGroup1);
+        assertNotNull(updatedGroup1);
+
+        // Re-retrieve and verify the updates were made...
+        final Group retrievedGroup1AfterUpdate = userGroupProvider.getGroup(group1.getIdentifier());
+        assertNotNull(retrievedGroup1AfterUpdate);
+        assertEquals(modifiedGroup1.getName(), retrievedGroup1AfterUpdate.getName());
+        assertEquals(0, retrievedGroup1AfterUpdate.getUsers().size());
+    }
+
+    @Test
+    public void testDeleteGroupWhenExists() {
+        configureWithInitialUsers();
+
+        // Create some users...
+        final User user1 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user1").build();
+        createUser(user1.getIdentifier(), user1.getIdentity());
+
+        final User user2 = new User.Builder().identifier(UUID.randomUUID().toString()).identity("user2").build();
+        createUser(user2.getIdentifier(), user2.getIdentity());
+
+        // Create a group and add user1 to it...
+        final Group group1 = new Group.Builder().identifier(UUID.randomUUID().toString()).name("group1").build();
+        createGroup(group1.getIdentifier(), group1.getName());
+        addUserToGroup(user1.getIdentifier(), group1.getIdentifier());
+
+        // Retrieve the created group...
+        final Group retrievedGroup1 = userGroupProvider.getGroup(group1.getIdentifier());
+        assertNotNull(retrievedGroup1);
+
+        // Delete the group...
+        final Group deletedGroup1 = userGroupProvider.deleteGroup(retrievedGroup1);
+        assertNotNull(deletedGroup1);
+
+        assertNull(userGroupProvider.getGroup(group1.getIdentifier()));
+    }
+
+    @Test
+    public void testDeleteGroupWhenDoesNotExist() {
+        configureWithInitialUsers();
+        final Group group1 = new Group.Builder().identifier(UUID.randomUUID().toString()).name("group1").build();
+        final Group updatedGroup1 = userGroupProvider.deleteGroup(group1);
+        assertNull(updatedGroup1);
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/ldap/tenants/LdapUserGroupProviderTest.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/ldap/tenants/LdapUserGroupProviderTest.java
index e8ce709..4026121 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/ldap/tenants/LdapUserGroupProviderTest.java
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/ldap/tenants/LdapUserGroupProviderTest.java
@@ -29,6 +29,8 @@
 import org.apache.nifi.registry.security.authorization.UserAndGroups;
 import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext;
 import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.security.identity.DefaultIdentityMapper;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.apache.nifi.registry.security.ldap.LdapAuthenticationStrategy;
 import org.apache.nifi.registry.security.ldap.ReferralStrategy;
 import org.apache.nifi.registry.util.StandardPropertyValue;
@@ -82,14 +84,17 @@
     private static final String GROUP_SEARCH_BASE = "ou=groups,o=nifi";
 
     private LdapUserGroupProvider ldapUserGroupProvider;
+    private IdentityMapper identityMapper;
 
     @Before
     public void setup() {
         final UserGroupProviderInitializationContext initializationContext = mock(UserGroupProviderInitializationContext.class);
         when(initializationContext.getIdentifier()).thenReturn("identifier");
 
+        identityMapper = new DefaultIdentityMapper(getNiFiProperties(new Properties()));
+
         ldapUserGroupProvider = new LdapUserGroupProvider();
-        ldapUserGroupProvider.setNiFiProperties(getNiFiProperties(new Properties()));
+        ldapUserGroupProvider.setIdentityMapper(identityMapper);
         ldapUserGroupProvider.initialize(initializationContext);
     }
 
@@ -508,7 +513,8 @@
         props.setProperty("nifi.registry.security.identity.mapping.value.dn1", "$1");
 
         final NiFiRegistryProperties properties = getNiFiProperties(props);
-        ldapUserGroupProvider.setNiFiProperties(properties);
+        identityMapper = new DefaultIdentityMapper(properties);
+        ldapUserGroupProvider.setIdentityMapper(identityMapper);
 
         final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
         when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)"));
@@ -526,7 +532,8 @@
         props.setProperty("nifi.registry.security.identity.mapping.transform.dn1", "UPPER");
 
         final NiFiRegistryProperties properties = getNiFiProperties(props);
-        ldapUserGroupProvider.setNiFiProperties(properties);
+        identityMapper = new DefaultIdentityMapper(properties);
+        ldapUserGroupProvider.setIdentityMapper(identityMapper);
 
         final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
         when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)"));
@@ -547,7 +554,8 @@
         props.setProperty("nifi.registry.security.group.mapping.transform.dn1", "UPPER");
 
         final NiFiRegistryProperties properties = getNiFiProperties(props);
-        ldapUserGroupProvider.setNiFiProperties(properties);
+        identityMapper = new DefaultIdentityMapper(properties);
+        ldapUserGroupProvider.setIdentityMapper(identityMapper);
 
         final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, GROUP_SEARCH_BASE);
         when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)"));
diff --git a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/authorizers.xml b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/authorizers.xml
index 607814c..9db7aca 100644
--- a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/authorizers.xml
+++ b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/authorizers.xml
@@ -48,6 +48,20 @@
     </userGroupProvider>
 
     <!--
+        The DatabaseUserGroupProvider will provide support for managing users and groups in a relational database. The framework
+        will provide a database connection to this provider using the same database information from nifi-registry.properties.
+
+        - Initial User Identity [unique key] - Same as the Initial User Identity in the FileUserGroupProvider
+    -->
+    <!-- To enable the database-user-group-provider remove 2 lines. This is 1 of 2.
+    <userGroupProvider>
+        <identifier>database-user-group-provider</identifier>
+        <class>org.apache.nifi.registry.security.authorization.database.DatabaseUserGroupProvider</class>
+        <property name="Initial User Identity 1"></property>
+    </userGroupProvider>
+    To enable the database-user-group-provider remove 2 lines. This is 2 of 2. -->
+
+    <!--
         The LdapUserGroupProvider will retrieve users and groups from an LDAP server. The users and groups
         are not configurable.
 
@@ -253,7 +267,8 @@
             NOTE: Any identity mapping rules specified in nifi-registry.properties will also be applied to the nifi identities,
             so the values should be the unmapped identities (i.e. full DN from a certificate). This identity must be found
             in the configured User Group Provider.
-        - NiFi Group Name: The name of the group, whose members are NiFi instance/node identities,
+
+        - NiFi Group Name - The name of the group, whose members are NiFi instance/node identities,
             that will have access to this NiFi Registry and will be able to act as a proxy on behalf of a NiFi Registry end user.
             The members of this group will be granted permission to proxy user requests, as well as read any bucket to perform synchronization checks.
     -->
@@ -269,6 +284,29 @@
     </accessPolicyProvider>
 
     <!--
+        The DatabaseAccessPolicyProvider will provide support for managing access policies in a relational database. The
+        framework will provide a database connection to this provider using the same database information from nifi-registry.properties.
+
+        - User Group Provider - Same as User Group Provider in the FileAccessPolicyProvider
+
+        - Initial Admin Identity - Same as Initial Admin Identity in the FileAccessPolicyProvider
+
+        - NiFi Identity [unique key] - Same as NiFi Identity in the FileAccessPolicyProvider
+
+        - NiFi Group Name - Same as NiFi Group Name in the FileAccessPolicyProvider
+    -->
+    <!-- To enable the database-access-policy-provider remove 2 lines. This is 1 of 2.
+    <accessPolicyProvider>
+        <identifier>database-access-policy-provider</identifier>
+        <class>org.apache.nifi.registry.security.authorization.database.DatabaseAccessPolicyProvider</class>
+        <property name="User Group Provider">database-user-group-provider</property>
+        <property name="Initial Admin Identity"></property>
+        <property name="NiFi Identity 1"></property>
+        <property name="NiFi Group Name"></property>
+    </accessPolicyProvider>
+    To enable the database-access-policy-provider remove 2 lines. This is 2 of 2. -->
+
+    <!--
         The StandardManagedAuthorizer. This authorizer implementation must be configured with the
         Access Policy Provider which it will use to access and manage users, groups, and policies.
         These users, groups, and policies will be used to make all access decisions during authorization
diff --git a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/logback.xml b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/logback.xml
index 2b7c5f5..e05e95f 100644
--- a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/logback.xml
+++ b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/logback.xml
@@ -86,10 +86,8 @@
     
     <logger name="org.apache.nifi.registry" level="INFO"/>
 
-    <!-- To see SQL statements set this to DEBUG -->
-    <logger name="org.hibernate.SQL" level="INFO" />
-    <!-- To see the values in SQL statements set this to TRACE -->
-    <logger name="org.hibernate.type" level="INFO" />
+    <!-- To see SQL statements and parameters set this to TRACE -->
+    <logger name="org.springframework.jdbc" level="INFO"/>
 
     <!--
         Logger for capturing Bootstrap logs and NiFi Registry's standard error and standard out.
diff --git a/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/ConfigurableAccessPolicyProvider.java b/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/ConfigurableAccessPolicyProvider.java
index 1f909a4..37de9fe 100644
--- a/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/ConfigurableAccessPolicyProvider.java
+++ b/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/ConfigurableAccessPolicyProvider.java
@@ -97,12 +97,4 @@
      */
     AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
 
-    /**
-     * Deletes the policy with the specified identifier.
-     *
-     * @param accessPolicyIdentifier the policy to delete
-     * @return the deleted policy, or null if no matching policy was found
-     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
-     */
-    AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException;
 }
diff --git a/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/ConfigurableUserGroupProvider.java b/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/ConfigurableUserGroupProvider.java
index bd52128..8d63387 100644
--- a/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/ConfigurableUserGroupProvider.java
+++ b/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/ConfigurableUserGroupProvider.java
@@ -100,15 +100,6 @@
     User deleteUser(User user) throws AuthorizationAccessException;
 
     /**
-     * Deletes the user for the given ID.
-     *
-     * @param userIdentifier the user to delete
-     * @return the user that was deleted, or null if no matching user was found
-     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
-     */
-    User deleteUser(String userIdentifier) throws AuthorizationAccessException;
-
-    /**
      * Adds a new group.
      *
      * @param group the Group to add
@@ -152,12 +143,4 @@
      */
     Group deleteGroup(Group group) throws AuthorizationAccessException;
 
-    /**
-     * Deletes the given group.
-     *
-     * @param groupIdentifier the group to delete
-     * @return the deleted group, or null if no matching group was found
-     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
-     */
-    Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException;
 }
diff --git a/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/User.java b/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/User.java
index 8879afe..e118c2b 100644
--- a/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/User.java
+++ b/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/User.java
@@ -40,7 +40,6 @@
         if (identity == null || identity.trim().isEmpty()) {
             throw new IllegalArgumentException("Identity can not be null or empty");
         }
-
     }
 
     /**
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
index 5bbaa8d..e2cf0e0 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
@@ -16,9 +16,9 @@
  */
 package org.apache.nifi.registry.web.security;
 
-import org.apache.nifi.registry.properties.NiFiRegistryProperties;
 import org.apache.nifi.registry.security.authorization.Authorizer;
 import org.apache.nifi.registry.security.authorization.resource.ResourceType;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.apache.nifi.registry.service.AuthorizationService;
 import org.apache.nifi.registry.web.security.authentication.AnonymousIdentityFilter;
 import org.apache.nifi.registry.web.security.authentication.IdentityAuthenticationProvider;
@@ -59,7 +59,7 @@
     private static final Logger logger = LoggerFactory.getLogger(NiFiRegistrySecurityConfig.class);
 
     @Autowired
-    private NiFiRegistryProperties properties;
+    private IdentityMapper identityMapper;
 
     @Autowired
     private AuthorizationService authorizationService;
@@ -146,7 +146,7 @@
 
     private IdentityAuthenticationProvider x509AuthenticationProvider() {
         if (x509AuthenticationProvider == null) {
-            x509AuthenticationProvider = new X509IdentityAuthenticationProvider(properties, authorizer, x509IdentityProvider);
+            x509AuthenticationProvider = new X509IdentityAuthenticationProvider(authorizer, x509IdentityProvider, identityMapper);
         }
         return x509AuthenticationProvider;
     }
@@ -160,7 +160,7 @@
 
     private IdentityAuthenticationProvider jwtAuthenticationProvider() {
         if (jwtAuthenticationProvider == null) {
-            jwtAuthenticationProvider = new IdentityAuthenticationProvider(properties, authorizer, jwtIdentityProvider);
+            jwtAuthenticationProvider = new IdentityAuthenticationProvider(authorizer, jwtIdentityProvider, identityMapper);
         }
         return jwtAuthenticationProvider;
     }
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationProvider.java
index ff6a218..b552a1a 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationProvider.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationProvider.java
@@ -16,9 +16,6 @@
  */
 package org.apache.nifi.registry.web.security.authentication;
 
-import org.apache.nifi.registry.properties.NiFiRegistryProperties;
-import org.apache.nifi.registry.properties.util.IdentityMapping;
-import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
 import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
 import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
 import org.apache.nifi.registry.security.authentication.IdentityProvider;
@@ -30,6 +27,7 @@
 import org.apache.nifi.registry.security.authorization.UserGroupProvider;
 import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails;
 import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.authentication.AuthenticationProvider;
@@ -38,7 +36,6 @@
 import org.springframework.security.core.AuthenticationException;
 
 import java.util.Collections;
-import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -46,19 +43,17 @@
 
     private static final Logger LOGGER = LoggerFactory.getLogger(IdentityAuthenticationProvider.class);
 
-    protected NiFiRegistryProperties properties;
     protected Authorizer authorizer;
     protected final IdentityProvider identityProvider;
-    private List<IdentityMapping> mappings;
+    protected final IdentityMapper identityMapper;
 
     public IdentityAuthenticationProvider(
-            NiFiRegistryProperties properties,
             Authorizer authorizer,
-            IdentityProvider identityProvider) {
-        this.properties = properties;
+            IdentityProvider identityProvider,
+            IdentityMapper identityMapper) {
         this.authorizer = authorizer;
         this.identityProvider = identityProvider;
-        this.mappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+        this.identityMapper = identityMapper;
     }
 
     @Override
@@ -114,7 +109,7 @@
     }
 
     protected String mapIdentity(final String identity) {
-        return IdentityMappingUtil.mapIdentity(identity, mappings);
+        return identityMapper.mapUser(identity);
     }
 
     protected Set<String> getUserGroups(final String identity) {
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
index 6571177..04e4673 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
@@ -17,7 +17,6 @@
 package org.apache.nifi.registry.web.security.authentication.x509;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.registry.properties.NiFiRegistryProperties;
 import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
 import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
 import org.apache.nifi.registry.security.authentication.IdentityProvider;
@@ -25,6 +24,7 @@
 import org.apache.nifi.registry.security.authorization.user.NiFiUser;
 import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails;
 import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
+import org.apache.nifi.registry.security.identity.IdentityMapper;
 import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils;
 import org.apache.nifi.registry.web.security.authentication.AuthenticationRequestToken;
 import org.apache.nifi.registry.web.security.authentication.AuthenticationSuccessToken;
@@ -36,8 +36,8 @@
 
 public class X509IdentityAuthenticationProvider extends IdentityAuthenticationProvider {
 
-    public X509IdentityAuthenticationProvider(NiFiRegistryProperties properties, Authorizer authorizer, IdentityProvider identityProvider) {
-        super(properties, authorizer, identityProvider);
+    public X509IdentityAuthenticationProvider(Authorizer authorizer, IdentityProvider identityProvider, IdentityMapper identityMapper) {
+        super(authorizer, identityProvider, identityMapper);
     }
 
     @Override
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 7490d3e..7b0446d 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
@@ -33,6 +33,7 @@
 import org.apache.nifi.registry.security.authorization.AuthorizerFactory;
 import org.apache.nifi.registry.security.crypto.BootstrapFileCryptoKeyProvider;
 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.Before;
@@ -52,6 +53,7 @@
 import org.springframework.test.context.jdbc.Sql;
 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;
@@ -110,9 +112,18 @@
         public static Authorizer getAuthorizer(
                 @Autowired NiFiRegistryProperties properties,
                 ExtensionManager extensionManager,
-                RegistryService registryService) throws Exception {
+                RegistryService registryService,
+                DataSource dataSource,
+                IdentityMapper identityMapper) throws Exception {
+
             if (authorizerFactory == null) {
-                authorizerFactory = new AuthorizerFactory(properties, extensionManager, sensitivePropertyProvider(), registryService);
+                authorizerFactory = new AuthorizerFactory(
+                        properties,
+                        extensionManager,
+                        sensitivePropertyProvider(),
+                        registryService,
+                        dataSource,
+                        identityMapper);
             }
             return authorizerFactory.getAuthorizer();
         }