http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
index 97ad0c1..b7a0217 100644
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
@@ -21,10 +21,9 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.authorization.Authorizer;
 import org.apache.nifi.registry.flow.VersionedFlow;
-import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
-import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+import org.apache.nifi.registry.service.AuthorizationService;
 import org.apache.nifi.registry.service.RegistryService;
 import org.apache.nifi.registry.service.params.QueryParameters;
 import org.apache.nifi.registry.service.params.SortParameter;
@@ -36,22 +35,17 @@ import 
org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
-import java.util.SortedSet;
 
 @Component
 @Path("/flows")
@@ -59,7 +53,7 @@ import java.util.SortedSet;
         value = "/flows",
         description = "Create named flows that can be versioned. Search for 
and retrieve existing flows."
 )
-public class FlowResource {
+public class FlowResource extends AuthorizableApplicationResource {
 
     private static final Logger logger = 
LoggerFactory.getLogger(FlowResource.class);
 
@@ -71,7 +65,12 @@ public class FlowResource {
     private final RegistryService registryService;
 
     @Autowired
-    public FlowResource(final RegistryService registryService, final 
LinkService linkService) {
+    public FlowResource(
+            final RegistryService registryService,
+            final LinkService linkService,
+            final AuthorizationService authorizationService,
+            final Authorizer authorizer) {
+        super(authorizer, authorizationService);
         this.registryService = registryService;
         this.linkService = linkService;
     }
@@ -85,206 +84,34 @@ public class FlowResource {
             response = VersionedFlow.class,
             responseContainer = "List"
     )
-    public Response getFlows(
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403) 
})
+    public Response getAuthorizedFlows(
             @ApiParam(value = SortParameter.API_PARAM_DESCRIPTION, format = 
"field:order", allowMultiple = true, example = "name:ASC")
             @QueryParam("sort")
             final List<String> sortParameters) {
 
+        Set<String> authorizedBucketIds = getAuthorizedBucketIds();
+
+        if (authorizedBucketIds == null || authorizedBucketIds.isEmpty()) {
+            // not authorized for any bucket, return empty list of items
+            return Response.status(Response.Status.OK).entity(new 
ArrayList<VersionedFlow>()).build();
+        }
+
         final QueryParameters.Builder paramsBuilder = new 
QueryParameters.Builder();
         for (String sortParam : sortParameters) {
             paramsBuilder.addSort(SortParameter.fromString(sortParam));
         }
 
-        final List<VersionedFlow> flows = 
registryService.getFlows(paramsBuilder.build());
+        final List<VersionedFlow> flows = 
registryService.getFlows(paramsBuilder.build(), authorizedBucketIds);
         linkService.populateFlowLinks(flows);
 
         return Response.status(Response.Status.OK).entity(flows).build();
     }
 
     @GET
-    @Path("/{flowId}")
-    @Consumes(MediaType.WILDCARD)
-    @Produces(MediaType.APPLICATION_JSON)
-    @ApiOperation(
-            value = "Get metadata for an existing flow the registry has 
stored. If verbose is true, then the metadata " +
-                    "about all snapshots for the flow will also be returned.",
-            response = VersionedFlow.class
-    )
-    @ApiResponses(
-            value = {
-                    @ApiResponse(code = 404, message = "The specified resource 
could not be found."),
-            }
-    )
-    public Response getFlow(@PathParam("flowId") final String flowId,
-                            @QueryParam("verbose") @DefaultValue("false") 
boolean verbose) {
-
-        final VersionedFlow flow = registryService.getFlow(flowId, verbose);
-        linkService.populateFlowLinks(flow);
-
-        if (flow.getSnapshotMetadata() != null) {
-            linkService.populateSnapshotLinks(flow.getSnapshotMetadata());
-        }
-
-        return Response.status(Response.Status.OK).entity(flow).build();
-    }
-
-    @PUT
-    @Path("/{flowId}")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    @ApiOperation(
-            value = "Update an existing flow the registry has stored.",
-            response = VersionedFlow.class
-    )
-    @ApiResponses(
-            value = {
-                    @ApiResponse(code = 404, message = "The specified resource 
could not be found."),
-            }
-    )
-    public Response updateFlow(@PathParam("flowId") final String flowId, final 
VersionedFlow flow) {
-        if (StringUtils.isBlank(flowId)) {
-            throw new IllegalArgumentException("Flow Id cannot be blank");
-        }
-
-        if (flow == null) {
-            throw new IllegalArgumentException("Flow cannot be null");
-        }
-
-        if (flow.getIdentifier() != null && 
!flowId.equals(flow.getIdentifier())) {
-            throw new IllegalArgumentException("Flow id in path param must 
match flow id in body");
-        }
-
-        if (flow.getIdentifier() == null) {
-            flow.setIdentifier(flowId);
-        }
-
-        final VersionedFlow updatedFlow = registryService.updateFlow(flow);
-        return Response.status(Response.Status.OK).entity(updatedFlow).build();
-    }
-
-    @DELETE
-    @Path("/{flowId}")
-    @Consumes(MediaType.WILDCARD)
-    @Produces(MediaType.APPLICATION_JSON)
-    @ApiOperation(
-            value = "Delete an existing flow the registry has stored.",
-            response = VersionedFlow.class
-    )
-    @ApiResponses(
-            value = {
-                    @ApiResponse(code = 404, message = "The specified resource 
could not be found."),
-            }
-    )
-    public Response deleteFlow(@PathParam("flowId") final String flowId) {
-        final VersionedFlow deletedFlow = registryService.deleteFlow(flowId);
-        return Response.status(Response.Status.OK).entity(deletedFlow).build();
-    }
-
-    @POST
-    @Path("/{flowId}/versions")
-    @Consumes(MediaType.WILDCARD)
-    @Produces(MediaType.APPLICATION_JSON)
-    @ApiOperation(
-            value = "Create the next version of a given flow ID. " +
-            "The version number is created by the server and a location URI 
for the created version resource is returned.",
-            response = VersionedFlowSnapshot.class
-    )
-    public Response createFlowVersion(@PathParam("flowId") final String 
flowId, final VersionedFlowSnapshot snapshot) {
-        if (StringUtils.isBlank(flowId)) {
-            throw new IllegalArgumentException("Flow Id cannot be blank");
-        }
-
-        if (snapshot == null) {
-            throw new IllegalArgumentException("VersionedFlowSnapshot cannot 
be null");
-        }
-
-        if (snapshot.getSnapshotMetadata() != null && 
snapshot.getSnapshotMetadata().getFlowIdentifier() != null
-                && 
!flowId.equals(snapshot.getSnapshotMetadata().getFlowIdentifier())) {
-            throw new IllegalArgumentException("Flow id in path param must 
match flow id in body");
-        }
-
-        if (snapshot.getSnapshotMetadata() != null && 
snapshot.getSnapshotMetadata().getFlowIdentifier() != null) {
-            snapshot.getSnapshotMetadata().setFlowIdentifier(flowId);
-        }
-
-        final VersionedFlowSnapshot createdSnapshot = 
registryService.createFlowSnapshot(snapshot);
-        return 
Response.status(Response.Status.OK).entity(createdSnapshot).build();
-    }
-
-    @GET
-    @Path("/{flowId}/versions")
-    @Consumes(MediaType.WILDCARD)
-    @Produces(MediaType.APPLICATION_JSON)
-    @ApiOperation(
-            value = "Get summary of all versions of a flow for a given flow 
ID.",
-            response = VersionedFlowSnapshotMetadata.class,
-            responseContainer = "List"
-    )
-    @ApiResponses(
-            value = {
-                    @ApiResponse(code = 404, message = "The specified resource 
could not be found."),
-            }
-    )
-    public Response getFlowVersions(@PathParam("flowId") final String flowId) {
-        final VersionedFlow flow = registryService.getFlow(flowId, true);
-
-        if (flow.getSnapshotMetadata() != null) {
-            linkService.populateSnapshotLinks(flow.getSnapshotMetadata());
-        }
-
-        return 
Response.status(Response.Status.OK).entity(flow.getSnapshotMetadata()).build();
-    }
-
-    @GET
-    @Path("/{flowId}/versions/latest")
-    @Consumes(MediaType.WILDCARD)
-    @Produces(MediaType.APPLICATION_JSON)
-    @ApiOperation(
-            value = "Get the latest version of a flow for a given flow ID",
-            response = VersionedFlowSnapshot.class
-    )
-    @ApiResponses(
-            value = {
-                    @ApiResponse(code = 404, message = "The specified resource 
could not be found."),
-            }
-    )
-    public Response getLatestFlowVersion(@PathParam("flowId") final String 
flowId) {
-        final VersionedFlow flow = registryService.getFlow(flowId, true);
-
-        final SortedSet<VersionedFlowSnapshotMetadata> snapshots = 
flow.getSnapshotMetadata();
-        if (snapshots == null || snapshots.size() == 0) {
-            return Response.status(Response.Status.NOT_FOUND).build();
-        }
-
-        final VersionedFlowSnapshotMetadata lastSnapshotMetadata = 
snapshots.last();
-
-        final VersionedFlowSnapshot lastSnapshot = 
registryService.getFlowSnapshot(
-                lastSnapshotMetadata.getFlowIdentifier(), 
lastSnapshotMetadata.getVersion());
-
-        return 
Response.status(Response.Status.OK).entity(lastSnapshot).build();
-    }
-
-    @GET
-    @Path("/{flowId}/versions/{versionNumber: \\d+}")
-    @Consumes(MediaType.WILDCARD)
-    @Produces(MediaType.APPLICATION_JSON)
-    @ApiOperation(
-            value = "Get a given version of a flow for a given flow ID",
-            response = VersionedFlowSnapshot.class
-    )
-    @ApiResponses(
-            value = {
-                    @ApiResponse(code = 404, message = "The specified resource 
could not be found."),
-            }
-    )
-    public Response getFlowVersion(
-            @PathParam("flowId") final String flowId,
-            @PathParam("versionNumber") final Integer versionNumber) {
-        final VersionedFlowSnapshot snapshot = 
registryService.getFlowSnapshot(flowId, versionNumber);
-        return Response.status(Response.Status.OK).entity(snapshot).build();
-    }
-
-    @GET
     @Path("fields")
     @Consumes(MediaType.WILDCARD)
     @Produces(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/HttpStatusMessages.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/HttpStatusMessages.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/HttpStatusMessages.java
new file mode 100644
index 0000000..a3ba939
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/HttpStatusMessages.java
@@ -0,0 +1,30 @@
+/*
+ * 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.web.api;
+
+class HttpStatusMessages {
+
+    /* 4xx messages */
+    static final String MESSAGE_400 = "NiFi Registry was unable to complete 
the request because it was invalid. The request should not be retried without 
modification.";
+    static final String MESSAGE_401 = "Client could not be authenticated.";
+    static final String MESSAGE_403 = "Client is not authorized to make this 
request.";
+    static final String MESSAGE_404 = "The specified resource could not be 
found.";
+    static final String MESSAGE_409 = "NiFi Registry was unable to complete 
the request because it assumes a server state that is not valid.";
+
+    /* 5xx messages */
+    static final String MESSAGE_500 = "NiFi Registry was unable to complete 
the request because an unexpected error occurred.";
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java
index cb99a1d..c6df1bf 100644
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java
@@ -19,7 +19,10 @@ package org.apache.nifi.registry.web.api;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.RequestAction;
 import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.service.AuthorizationService;
 import org.apache.nifi.registry.service.RegistryService;
 import org.apache.nifi.registry.service.params.QueryParameters;
 import org.apache.nifi.registry.service.params.SortParameter;
@@ -40,6 +43,7 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -49,7 +53,7 @@ import java.util.Set;
         value = "/items",
         description = "Retrieve items across all buckets for which the user is 
authorized."
 )
-public class ItemResource {
+public class ItemResource extends AuthorizableApplicationResource {
 
     private static final Logger LOGGER = 
LoggerFactory.getLogger(ItemResource.class);
 
@@ -57,11 +61,15 @@ public class ItemResource {
     UriInfo uriInfo;
 
     private final LinkService linkService;
-
     private final RegistryService registryService;
 
     @Autowired
-    public ItemResource(final RegistryService registryService, final 
LinkService linkService) {
+    public ItemResource(
+            final RegistryService registryService,
+            final LinkService linkService,
+            final AuthorizationService authorizationService,
+            final Authorizer authorizer) {
+        super(authorizer, authorizationService);
         this.registryService = registryService;
         this.linkService = linkService;
     }
@@ -80,12 +88,19 @@ public class ItemResource {
             @QueryParam("sort")
             final List<String> sortParameters) {
 
+        Set<String> authorizedBucketIds = getAuthorizedBucketIds();
+
+        if (authorizedBucketIds == null || authorizedBucketIds.isEmpty()) {
+            // not authorized for any bucket, return empty list of items
+            return Response.status(Response.Status.OK).entity(new 
ArrayList<BucketItem>()).build();
+        }
+
         final QueryParameters.Builder paramsBuilder = new 
QueryParameters.Builder();
         for (String sortParam : sortParameters) {
             paramsBuilder.addSort(SortParameter.fromString(sortParam));
         }
 
-        final List<BucketItem> items = 
registryService.getBucketItems(paramsBuilder.build());
+        final List<BucketItem> items = 
registryService.getBucketItems(paramsBuilder.build(), authorizedBucketIds);
         linkService.populateItemLinks(items);
 
         return Response.status(Response.Status.OK).entity(items).build();
@@ -108,6 +123,7 @@ public class ItemResource {
             @QueryParam("sort")
             final List<String> sortParameters) {
 
+        authorizeBucketAccess(RequestAction.READ, bucketId);
         final QueryParameters.Builder paramsBuilder = new 
QueryParameters.Builder();
         for (String sortParam : sortParameters) {
             paramsBuilder.addSort(SortParameter.fromString(sortParam));

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ResourceResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ResourceResource.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ResourceResource.java
new file mode 100644
index 0000000..bcef565
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ResourceResource.java
@@ -0,0 +1,91 @@
+/*
+ * 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.web.api;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.RequestAction;
+import org.apache.nifi.registry.authorization.resource.Authorizable;
+import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
+import org.apache.nifi.registry.service.AuthorizationService;
+import org.apache.nifi.registry.model.authorization.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * RESTful endpoint for retrieving system diagnostics.
+ */
+@Component
+@Path("/resources")
+@Api(
+        value = "/resources",
+        description = "Provides the resources in this NiFi that can have 
access/authorization policies."
+)
+public class ResourceResource extends AuthorizableApplicationResource {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ResourceResource.class);
+
+    @Autowired
+    public ResourceResource(AuthorizationService authorizationService, 
Authorizer authorizer) {
+        super(authorizer, authorizationService);
+    }
+
+    private void authorizeResource() {
+        authorizationService.authorizeAccess(lookup -> {
+            final Authorizable resource = lookup.getResourcesAuthorizable();
+            resource.authorize(authorizer, RequestAction.READ, 
NiFiUserUtils.getNiFiUser());
+        });
+    }
+
+    /**
+     * Gets the available resources that support access/authorization policies.
+     *
+     * @return A resourcesEntity.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            value = "Gets the available resources that support 
access/authorization policies",
+            response = Resource.class,
+            responseContainer = "List"
+    )
+    @ApiResponses({
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403) 
})
+    public Response getResources() {
+        authorizeResource();
+
+        final List<Resource> resources = authorizationService.getResources();
+
+        return generateOkResponse(resources).build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java
new file mode 100644
index 0000000..6b02938
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java
@@ -0,0 +1,486 @@
+/*
+ * 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.web.api;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.AuthorizerCapabilityDetection;
+import org.apache.nifi.registry.authorization.RequestAction;
+import org.apache.nifi.registry.authorization.resource.Authorizable;
+import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
+import org.apache.nifi.registry.model.authorization.User;
+import org.apache.nifi.registry.model.authorization.UserGroup;
+import org.apache.nifi.registry.service.AuthorizationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * RESTful endpoints for managing tenants, ie, users and user groups.
+ */
+@Component
+@Path("tenants")
+@Api(
+        value = "tenants",
+        description = "Endpoint for managing users and user groups."
+)
+public class TenantResource extends AuthorizableApplicationResource {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(TenantResource.class);
+
+    @Autowired
+    public TenantResource(
+            Authorizer authorizer,
+            AuthorizationService authorizationService) {
+        super(authorizer, authorizationService);
+    }
+
+
+    // ---------- User endpoints 
--------------------------------------------------------------------------------------
+
+    /**
+     * Creates a new user.
+     *
+     * @param httpServletRequest request
+     * @param requestUser the user to create
+     * @return the user that was created
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users")
+    @ApiOperation(
+            value = "Creates a user",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = User.class
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response createUser(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(value = "The user configuration details.", required = 
true)
+            final User requestUser) {
+
+        verifyAuthorizerSupportsConfigurableUserGroups();
+
+        if (requestUser == null) {
+            throw new IllegalArgumentException("User details must be specified 
when creating a new user.");
+        }
+        if (requestUser.getIdentifier() != null) {
+            throw new IllegalArgumentException("User identifier cannot be 
specified when creating a new user.");
+        }
+        if (StringUtils.isBlank(requestUser.getIdentity())) {
+            throw new IllegalArgumentException("User identity must be 
specified when creating a new user.");
+        }
+
+        authorizeAccess(RequestAction.WRITE);
+
+        User createdUser = authorizationService.createUser(requestUser);
+
+        String locationUri = generateUserUri(createdUser);
+        return generateCreatedResponse(URI.create(locationUri), 
createdUser).build();
+    }
+
+    /**
+     * Retrieves all the of users in this NiFi.
+     *
+     * @return a list of users
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users")
+    @ApiOperation(
+            value = "Gets all users",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = User.class,
+            responseContainer = "List"
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getUsers() {
+        verifyAuthorizerIsManaged();
+
+        authorizeAccess(RequestAction.READ);
+
+        // get all the users
+        final List<User> users = authorizationService.getUsers();
+
+        // generate the response
+        return generateOkResponse(users).build();
+    }
+
+    /**
+     * Retrieves the specified user.
+     *
+     * @param identifier The id of the user to retrieve
+     * @return An userEntity.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users/{id}")
+    @ApiOperation(
+            value = "Gets a user",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = User.class
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getUser(
+            @ApiParam(value = "The user id.", required = true)
+            @PathParam("id") final String identifier) {
+        verifyAuthorizerIsManaged();
+        authorizeAccess(RequestAction.READ);
+
+        final User user = authorizationService.getUser(identifier);
+        return generateOkResponse(user).build();
+    }
+
+    /**
+     * Updates a user.
+     *
+     * @param httpServletRequest request
+     * @param identifier The id of the user to update
+     * @param requestUser The user with updated fields.
+     * @return The updated user
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users/{id}")
+    @ApiOperation(
+            value = "Updates a user",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = User.class
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response updateUser(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(value = "The user id.", required = true)
+            @PathParam("id") final String identifier,
+            @ApiParam(value = "The user configuration details.", required = 
true)
+            final User requestUser) {
+
+        verifyAuthorizerSupportsConfigurableUserGroups();
+
+        if (requestUser == null) {
+            throw new IllegalArgumentException("User details must be specified 
when updating a user.");
+        }
+        if (!identifier.equals(requestUser.getIdentifier())) {
+            throw new IllegalArgumentException(String.format("The user id in 
the request body (%s) does not equal the "
+                    + "user id of the requested resource (%s).", 
requestUser.getIdentifier(), identifier));
+        }
+
+        authorizeAccess(RequestAction.WRITE);
+
+        final User updatedUser = authorizationService.updateUser(requestUser);
+        return generateOkResponse(updatedUser).build();
+    }
+
+    /**
+     * Removes the specified user.
+     *
+     * @param httpServletRequest request
+     * @param identifier         The id of the user to remove.
+     * @return A entity containing the client id and an updated revision.
+     */
+    @DELETE
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users/{id}")
+    @ApiOperation(
+            value = "Deletes a user",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = User.class
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response removeUser(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(value = "The user id.", required = true)
+            @PathParam("id") final String identifier) {
+
+        verifyAuthorizerSupportsConfigurableUserGroups();
+        authorizeAccess(RequestAction.WRITE);
+
+        final User user = authorizationService.deleteUser(identifier);
+        return generateOkResponse(user).build();
+    }
+
+
+    // ---------- User Group endpoints 
--------------------------------------------------------------------------------
+
+    /**
+     * Creates a new user group.
+     *
+     * @param httpServletRequest request
+     * @param requestUserGroup the user group to create
+     * @return the created user group
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups")
+    @ApiOperation(
+            value = "Creates a user group",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = UserGroup.class
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response createUserGroup(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The user group configuration details.",
+                    required = true
+            ) final UserGroup requestUserGroup) {
+
+        verifyAuthorizerSupportsConfigurableUserGroups();
+
+        if (requestUserGroup == null) {
+            throw new IllegalArgumentException("User group details must be 
specified when creating a new group.");
+        }
+        if (requestUserGroup.getIdentifier() != null) {
+            throw new IllegalArgumentException("User group ID cannot be 
specified when creating a new group.");
+        }
+        if (StringUtils.isBlank(requestUserGroup.getIdentity())) {
+            throw new IllegalArgumentException("User group identity must be 
specified when creating a new group.");
+        }
+
+        authorizeAccess(RequestAction.WRITE);
+
+        UserGroup createdGroup = 
authorizationService.createUserGroup(requestUserGroup);
+        String locationUri = generateUserGroupUri(createdGroup);
+
+        return generateCreatedResponse(URI.create(locationUri), 
createdGroup).build();
+    }
+
+    /**
+     * Retrieves all the of user groups in this NiFi.
+     *
+     * @return a list of all user groups in this NiFi.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups")
+    @ApiOperation(
+            value = "Gets all user groups",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = UserGroup.class,
+            responseContainer = "List"
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getUserGroups() {
+        verifyAuthorizerIsManaged();
+        authorizeAccess(RequestAction.READ);
+
+        final List<UserGroup> userGroups = 
authorizationService.getUserGroups();
+        return generateOkResponse(userGroups).build();
+    }
+
+    /**
+     * Retrieves the specified user group.
+     *
+     * @param identifier The id of the user group to retrieve
+     * @return An userGroupEntity.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups/{id}")
+    @ApiOperation(
+            value = "Gets a user group",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = UserGroup.class
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getUserGroup(
+            @ApiParam(value = "The user group id.", required = true)
+            @PathParam("id") final String identifier) {
+        verifyAuthorizerIsManaged();
+        authorizeAccess(RequestAction.READ);
+
+        final UserGroup userGroup = 
authorizationService.getUserGroup(identifier);
+        return generateOkResponse(userGroup).build();
+    }
+
+    /**
+     * Updates a user group.
+     *
+     * @param httpServletRequest request
+     * @param identifier The id of the user group to update.
+     * @param requestUserGroup The user group with updated fields.
+     * @return The resulting, updated user group.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups/{id}")
+    @ApiOperation(
+            value = "Updates a user group",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = UserGroup.class
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response updateUserGroup(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(value = "The user group id.", required = true)
+            @PathParam("id") final String identifier,
+            @ApiParam(value = "The user group configuration details.", 
required = true)
+            final UserGroup requestUserGroup) {
+
+        verifyAuthorizerSupportsConfigurableUserGroups();
+
+        if (requestUserGroup == null) {
+            throw new IllegalArgumentException("User group details must be 
specified to update a user group.");
+        }
+        if (!identifier.equals(requestUserGroup.getIdentifier())) {
+            throw new IllegalArgumentException(String.format("The user group 
id in the request body (%s) does not equal the "
+                    + "user group id of the requested resource (%s).", 
requestUserGroup.getIdentifier(), identifier));
+        }
+
+        authorizeAccess(RequestAction.WRITE);
+
+        UserGroup updatedUserGroup = 
authorizationService.updateUserGroup(requestUserGroup);
+        return generateOkResponse(updatedUserGroup).build();
+    }
+
+    /**
+     * Removes the specified user group.
+     *
+     * @param httpServletRequest request
+     * @param identifier                 The id of the user group to remove.
+     * @return The deleted user group.
+     */
+    @DELETE
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups/{id}")
+    @ApiOperation(
+            value = "Deletes a user group",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = UserGroup.class
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response removeUserGroup(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(value = "The user group id.", required = true)
+            @PathParam("id")
+            final String identifier) {
+        verifyAuthorizerSupportsConfigurableUserGroups();
+        authorizeAccess(RequestAction.WRITE);
+
+        final UserGroup userGroup = 
authorizationService.deleteUserGroup(identifier);
+        return generateOkResponse(userGroup).build();
+    }
+
+
+    private void verifyAuthorizerIsManaged() {
+        if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            throw new 
IllegalStateException(AuthorizationService.MSG_NON_MANAGED_AUTHORIZER);
+        }
+    }
+
+    private void verifyAuthorizerSupportsConfigurableUserGroups() {
+        if 
(!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
+            throw new 
IllegalStateException(AuthorizationService.MSG_NON_CONFIGURABLE_USERS);
+        }
+    }
+
+    private void authorizeAccess(RequestAction actionType) {
+        authorizationService.authorizeAccess(lookup -> {
+            final Authorizable tenantsAuthorizable = 
lookup.getTenantsAuthorizable();
+            tenantsAuthorizable.authorize(authorizer, actionType, 
NiFiUserUtils.getNiFiUser());
+        });
+    }
+
+    private String generateUserUri(final User user) {
+        return generateResourceUri("tenants", "users", user.getIdentifier());
+    }
+
+    private String generateUserGroupUri(final UserGroup userGroup) {
+        return generateResourceUri("tenants", "user-groups", 
userGroup.getIdentifier());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowLinkBuilder.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowLinkBuilder.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowLinkBuilder.java
index e18d583..38d3d0e 100644
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowLinkBuilder.java
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowLinkBuilder.java
@@ -27,7 +27,7 @@ import java.net.URI;
  */
 public class VersionedFlowLinkBuilder implements LinkBuilder<VersionedFlow> {
 
-    private static final String PATH = "flows/{id}";
+    private static final String PATH = "buckets/{bucketId}/flows/{flowId}";
 
     @Override
     public Link createLink(final VersionedFlow versionedFlow) {
@@ -36,7 +36,8 @@ public class VersionedFlowLinkBuilder implements 
LinkBuilder<VersionedFlow> {
         }
 
         final URI uri = UriBuilder.fromPath(PATH)
-                .resolveTemplate("id", versionedFlow.getIdentifier())
+                .resolveTemplate("bucketId", 
versionedFlow.getBucketIdentifier())
+                .resolveTemplate("flowId", versionedFlow.getIdentifier())
                 .build();
 
         return Link.fromUri(uri).rel("self").build();

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowSnapshotLinkBuilder.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowSnapshotLinkBuilder.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowSnapshotLinkBuilder.java
index 47eb15f..4085c6d 100644
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowSnapshotLinkBuilder.java
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/link/builder/VersionedFlowSnapshotLinkBuilder.java
@@ -27,7 +27,7 @@ import java.net.URI;
  */
 public class VersionedFlowSnapshotLinkBuilder implements 
LinkBuilder<VersionedFlowSnapshotMetadata> {
 
-    private static final String PATH = 
"flows/{flowId}/versions/{versionNumber}";
+    private static final String PATH = 
"buckets/{bucketId}/flows/{flowId}/versions/{versionNumber}";
 
     @Override
     public Link createLink(final VersionedFlowSnapshotMetadata 
snapshotMetadata) {
@@ -36,6 +36,7 @@ public class VersionedFlowSnapshotLinkBuilder implements 
LinkBuilder<VersionedFl
         }
 
         final URI uri = UriBuilder.fromPath(PATH)
+                .resolveTemplate("bucketId", 
snapshotMetadata.getBucketIdentifier())
                 .resolveTemplate("flowId", 
snapshotMetadata.getFlowIdentifier())
                 .resolveTemplate("versionNumber", 
snapshotMetadata.getVersion())
                 .build();

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AccessDeniedExceptionMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AccessDeniedExceptionMapper.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AccessDeniedExceptionMapper.java
new file mode 100644
index 0000000..68acb77
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AccessDeniedExceptionMapper.java
@@ -0,0 +1,73 @@
+/*
+ * 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.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.authorization.user.NiFiUser;
+import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps access denied exceptions into a client response.
+ */
+@Provider
+public class AccessDeniedExceptionMapper implements 
ExceptionMapper<AccessDeniedException> {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(AccessDeniedExceptionMapper.class);
+
+    @Override
+    public Response toResponse(AccessDeniedException exception) {
+        // get the current user
+        NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+        // if the user was authenticated - forbidden, otherwise 
unauthorized... the user may be null if the
+        // AccessDeniedException was thrown from a /access endpoint that isn't 
subject to the security
+        // filter chain. for instance, one that performs kerberos negotiation
+        final Status status;
+        if (user == null || user.isAnonymous()) {
+            status = Status.UNAUTHORIZED;
+        } else {
+            status = Status.FORBIDDEN;
+        }
+
+        final String identity;
+        if (user == null) {
+            identity = "<no user found>";
+        } else {
+            identity = user.toString();
+        }
+
+        logger.info(String.format("%s does not have permission to access the 
requested resource. %s Returning %s response.", identity, 
exception.getMessage(), status));
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(StringUtils.EMPTY, exception);
+        }
+
+        return Response.status(status)
+                .entity(String.format("%s Contact the system administrator.", 
exception.getMessage()))
+                .type("text/plain")
+                .build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AdministrationExceptionMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AdministrationExceptionMapper.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AdministrationExceptionMapper.java
new file mode 100644
index 0000000..69f7862
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AdministrationExceptionMapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.web.mapper;
+
+import org.apache.nifi.registry.exception.AdministrationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps administration exceptions into client responses.
+ */
+@Provider
+public class AdministrationExceptionMapper implements 
ExceptionMapper<AdministrationException> {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(AdministrationExceptionMapper.class);
+
+    @Override
+    public Response toResponse(AdministrationException exception) {
+        // log the error
+        logger.error(String.format("%s. Returning %s response.", exception, 
Response.Status.INTERNAL_SERVER_ERROR), exception);
+
+        // generate the response
+        return 
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception.getMessage()).type("text/plain").build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AuthenticationCredentialsNotFoundExceptionMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AuthenticationCredentialsNotFoundExceptionMapper.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AuthenticationCredentialsNotFoundExceptionMapper.java
new file mode 100644
index 0000000..a94b462
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AuthenticationCredentialsNotFoundExceptionMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import 
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps exceptions that occur because no valid credentials were found into the 
corresponding response.
+ */
+@Provider
+public class AuthenticationCredentialsNotFoundExceptionMapper implements 
ExceptionMapper<AuthenticationCredentialsNotFoundException> {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(AuthenticationCredentialsNotFoundExceptionMapper.class);
+
+    @Override
+    public Response toResponse(AuthenticationCredentialsNotFoundException 
exception) {
+        // log the error
+        logger.info(String.format("No valid credentials were found in the 
request: %s. Returning %s response.", exception, Response.Status.FORBIDDEN));
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(StringUtils.EMPTY, exception);
+        }
+
+        return Response.status(Response.Status.FORBIDDEN).entity("Access is 
denied.").type("text/plain").build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AuthorizationAccessExceptionMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AuthorizationAccessExceptionMapper.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AuthorizationAccessExceptionMapper.java
new file mode 100644
index 0000000..4148cf5
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/AuthorizationAccessExceptionMapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.web.mapper;
+
+import 
org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps authorization access exceptions into client responses.
+ */
+@Provider
+public class AuthorizationAccessExceptionMapper implements 
ExceptionMapper<AuthorizationAccessException> {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(AuthorizationAccessExceptionMapper.class);
+
+    @Override
+    public Response toResponse(AuthorizationAccessException e) {
+        // log the error
+        logger.error(String.format("%s. Returning %s response.", e, 
Response.Status.INTERNAL_SERVER_ERROR), e);
+
+        // generate the response
+        return 
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).type("text/plain").build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/InvalidAuthenticationExceptionMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/InvalidAuthenticationExceptionMapper.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/InvalidAuthenticationExceptionMapper.java
new file mode 100644
index 0000000..1216a0c
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/InvalidAuthenticationExceptionMapper.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.web.security.InvalidAuthenticationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps access denied exceptions into a client response.
+ */
+@Provider
+public class InvalidAuthenticationExceptionMapper implements 
ExceptionMapper<InvalidAuthenticationException> {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(InvalidAuthenticationExceptionMapper.class);
+
+    @Override
+    public Response toResponse(InvalidAuthenticationException exception) {
+        if (logger.isDebugEnabled()) {
+            logger.debug(StringUtils.EMPTY, exception);
+        }
+
+        return 
Response.status(Response.Status.UNAUTHORIZED).entity(exception.getMessage()).type("text/plain").build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotFoundExceptionMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotFoundExceptionMapper.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotFoundExceptionMapper.java
new file mode 100644
index 0000000..0a6797d
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotFoundExceptionMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Maps not found exceptions into client responses.
+ */
+@Provider
+public class NotFoundExceptionMapper implements 
ExceptionMapper<NotFoundException> {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(NotFoundExceptionMapper.class);
+
+    @Override
+    public Response toResponse(NotFoundException exception) {
+        // log the error
+        logger.info(String.format("%s. Returning %s response.", exception, 
Response.Status.NOT_FOUND));
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(StringUtils.EMPTY, exception);
+        }
+
+        return 
Response.status(Response.Status.NOT_FOUND).entity(exception.getMessage()).type("text/plain").build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/response/AuthenticationResponse.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/response/AuthenticationResponse.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/response/AuthenticationResponse.java
new file mode 100644
index 0000000..a0b87b5
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/response/AuthenticationResponse.java
@@ -0,0 +1,65 @@
+/*
+ * 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.web.response;
+
+/**
+ * Authentication response for a user login attempt.
+ */
+public class AuthenticationResponse {
+
+    private final String identity;
+    private final String username;
+    private final long expiration;
+    private final String issuer;
+
+    /**
+     * Creates an authentication response. The username and how long the 
authentication is valid in milliseconds
+     *
+     * @param identity The user identity
+     * @param username The username
+     * @param expiration The expiration in milliseconds
+     * @param issuer The issuer of the token
+     */
+    public AuthenticationResponse(final String identity, final String 
username, final long expiration, final String issuer) {
+        this.identity = identity;
+        this.username = username;
+        this.expiration = expiration;
+        this.issuer = issuer;
+    }
+
+    public String getIdentity() {
+        return identity;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    /**
+     * Returns the expiration of a given authentication in milliseconds.
+     *
+     * @return The expiration in milliseconds
+     */
+    public long getExpiration() {
+        return expiration;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/InvalidAuthenticationException.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/InvalidAuthenticationException.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/InvalidAuthenticationException.java
new file mode 100644
index 0000000..c7a1aea
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/InvalidAuthenticationException.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.web.security;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * Thrown if the authentication of a given request is invalid. For instance,
+ * an expired certificate or token.
+ */
+public class InvalidAuthenticationException extends AuthenticationException {
+
+    public InvalidAuthenticationException(String msg) {
+        super(msg);
+    }
+
+    public InvalidAuthenticationException(String msg, Throwable t) {
+        super(msg, t);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAnonymousUserFilter.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAnonymousUserFilter.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAnonymousUserFilter.java
new file mode 100644
index 0000000..3715bc7
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAnonymousUserFilter.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.web.security;
+
+import org.apache.nifi.registry.authorization.user.NiFiUserDetails;
+import org.apache.nifi.registry.authorization.user.StandardNiFiUser;
+import org.apache.nifi.registry.web.security.token.NiFiAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter {
+
+    private static final String ANONYMOUS_KEY = "anonymousNifiKey";
+
+    public NiFiAnonymousUserFilter() {
+        super(ANONYMOUS_KEY);
+    }
+
+    @Override
+    protected Authentication createAuthentication(HttpServletRequest request) {
+        return new NiFiAuthenticationToken(new 
NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationFilter.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationFilter.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationFilter.java
new file mode 100644
index 0000000..7dfc3dc
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationFilter.java
@@ -0,0 +1,154 @@
+/*
+ * 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.web.security;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationManager;
+import 
org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.GenericFilterBean;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ *
+ */
+public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
+
+    private static final Logger log = 
LoggerFactory.getLogger(NiFiAuthenticationFilter.class);
+
+    private AuthenticationManager authenticationManager;
+    private NiFiRegistryProperties properties;
+
+    @Override
+    public void doFilter(final ServletRequest request, final ServletResponse 
response, final FilterChain chain) throws IOException, ServletException {
+        final Authentication authentication = 
SecurityContextHolder.getContext().getAuthentication();
+        if (log.isDebugEnabled()) {
+            log.debug("Checking secure context token: " + authentication);
+        }
+
+        if (requiresAuthentication((HttpServletRequest) request)) {
+            authenticate((HttpServletRequest) request, (HttpServletResponse) 
response, chain);
+        } else {
+            chain.doFilter(request, response);
+        }
+
+    }
+
+    private boolean requiresAuthentication(final HttpServletRequest request) {
+        return NiFiUserUtils.getNiFiUser() == null;
+    }
+
+    private void authenticate(final HttpServletRequest request, final 
HttpServletResponse response, final FilterChain chain) throws IOException, 
ServletException {
+        String dnChain = null;
+        try {
+            final Authentication authenticationRequest = 
attemptAuthentication(request);
+            if (authenticationRequest != null) {
+                // log the request attempt - response details will be logged 
later
+                log.info(String.format("Attempting request for (%s) %s %s 
(source ip: %s)", authenticationRequest.toString(), request.getMethod(),
+                        request.getRequestURL().toString(), 
request.getRemoteAddr()));
+
+                // attempt to authorize the user
+                final Authentication authenticated = 
authenticationManager.authenticate(authenticationRequest);
+                successfulAuthorization(request, response, authenticated);
+            }
+
+            // continue
+            chain.doFilter(request, response);
+        } catch (final AuthenticationException ae) {
+            // invalid authentication - always error out
+            unsuccessfulAuthorization(request, response, ae);
+        }
+    }
+
+    /**
+     * Attempt to extract an authentication attempt from the specified request.
+     *
+     * @param request The request
+     * @return The authentication attempt or null if none is found int he 
request
+     */
+    public abstract Authentication attemptAuthentication(HttpServletRequest 
request);
+
+    protected void successfulAuthorization(HttpServletRequest request, 
HttpServletResponse response, Authentication authResult) {
+        log.info("Authentication success for " + authResult);
+
+        SecurityContextHolder.getContext().setAuthentication(authResult);
+        ProxiedEntitiesUtils.successfulAuthorization(request, response, 
authResult);
+    }
+
+    protected void unsuccessfulAuthorization(HttpServletRequest request, 
HttpServletResponse response, AuthenticationException ae) throws IOException {
+        // populate the response
+        ProxiedEntitiesUtils.unsuccessfulAuthorization(request, response, ae);
+
+        // set the response status
+        response.setContentType("text/plain");
+
+        // write the response message
+        PrintWriter out = response.getWriter();
+
+        // use the type of authentication exception to determine the response 
code
+        if (ae instanceof InvalidAuthenticationException) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            out.println(ae.getMessage());
+        } else if (ae instanceof UntrustedProxyException) {
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            out.println(ae.getMessage());
+        } else if (ae instanceof AuthenticationServiceException) {
+            log.error(String.format("Unable to authorize: %s", 
ae.getMessage()), ae);
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            out.println(String.format("Unable to authorize: %s", 
ae.getMessage()));
+        } else {
+            log.error(String.format("Unable to authorize: %s", 
ae.getMessage()), ae);
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            out.println("Access is denied.");
+        }
+
+        // log the failure
+        log.warn(String.format("Rejecting access to web api: %s", 
ae.getMessage()));
+
+        // optionally log the stack trace
+        if (log.isDebugEnabled()) {
+            log.debug(StringUtils.EMPTY, ae);
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+    public void setAuthenticationManager(AuthenticationManager 
authenticationManager) {
+        this.authenticationManager = authenticationManager;
+    }
+
+    public void setProperties(NiFiRegistryProperties properties) {
+        this.properties = properties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationProvider.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationProvider.java
new file mode 100644
index 0000000..6bc052b
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationProvider.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.web.security;
+
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.Group;
+import org.apache.nifi.registry.authorization.ManagedAuthorizer;
+import org.apache.nifi.registry.authorization.UserAndGroups;
+import org.apache.nifi.registry.authorization.UserGroupProvider;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationProvider;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Base AuthenticationProvider that provides common functionality to mapping 
identities.
+ */
+public abstract class NiFiAuthenticationProvider implements 
AuthenticationProvider {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(NiFiAuthenticationProvider.class);
+
+    private NiFiRegistryProperties properties;
+    private Authorizer authorizer;
+    private List<IdentityMapping> mappings;
+
+    /**
+     * @param properties the NiFiProperties instance
+     */
+    public NiFiAuthenticationProvider(final NiFiRegistryProperties properties, 
final Authorizer authorizer) {
+        this.properties = properties;
+        this.mappings = 
Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+        this.authorizer = authorizer;
+    }
+
+    public List<IdentityMapping> getMappings() {
+        return mappings;
+    }
+
+    protected String mapIdentity(final String identity) {
+        return IdentityMappingUtil.mapIdentity(identity, mappings);
+    }
+
+    protected Set<String> getUserGroups(final String identity) {
+        return getUserGroups(authorizer, identity);
+    }
+
+    protected static Set<String> getUserGroups(final Authorizer authorizer, 
final String userIdentity) {
+        if (authorizer instanceof ManagedAuthorizer) {
+            final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) 
authorizer;
+            final UserGroupProvider userGroupProvider = 
managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
+            final UserAndGroups userAndGroups = 
userGroupProvider.getUserAndGroups(userIdentity);
+            final Set<Group> userGroups = userAndGroups.getGroups();
+
+            if (userGroups == null || userGroups.isEmpty()) {
+                return Collections.EMPTY_SET;
+            } else {
+                return userAndGroups.getGroups().stream().map(group -> 
group.getName()).collect(Collectors.toSet());
+            }
+        } else {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationRequestToken.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationRequestToken.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationRequestToken.java
new file mode 100644
index 0000000..c1f44ef
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiAuthenticationRequestToken.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+/**
+ * Base class for authentication request tokens in NiFI.
+ */
+public abstract class NiFiAuthenticationRequestToken extends 
AbstractAuthenticationToken {
+
+    private final String clientAddress;
+
+    /**
+     * @param clientAddress   The address of the client making the request
+     */
+    public NiFiAuthenticationRequestToken(final String clientAddress) {
+        super(null);
+        setAuthenticated(false);
+        this.clientAddress = clientAddress;
+    }
+
+    public String getClientAddress() {
+        return clientAddress;
+    }
+
+}

Reply via email to