You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/11/18 20:02:03 UTC

[6/6] nifi git commit: NIFI-655: - Refactoring key service to expose the key id. - Handling client side expiration better. - Removing specialized active directory provider and abstract ldap provider.

NIFI-655:
- Refactoring key service to expose the key id.
- Handling client side expiration better.
- Removing specialized active directory provider and abstract ldap provider.

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

Branch: refs/heads/NIFI-655
Commit: c94d0271d990df6d6cd9a1dfee7b1706f451483c
Parents: 7d04dfe
Author: Matt Gilman <ma...@gmail.com>
Authored: Wed Nov 18 14:01:45 2015 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Nov 18 14:01:45 2015 -0500

----------------------------------------------------------------------
 .../nifi/security/util/CertificateUtils.java    |  31 ++---
 .../java/org/apache/nifi/admin/dao/KeyDAO.java  |  18 ++-
 .../nifi/admin/dao/impl/StandardKeyDAO.java     |  67 ++++++++--
 .../apache/nifi/admin/service/KeyService.java   |   8 +-
 .../nifi/admin/service/action/GetKeyAction.java |  41 -------
 .../admin/service/action/GetKeyByIdAction.java  |  42 +++++++
 .../service/action/GetKeyByIdentityAction.java  |  42 +++++++
 .../service/action/GetOrCreateKeyAction.java    |   7 +-
 .../admin/service/impl/StandardKeyService.java  |  19 ++-
 .../src/main/java/org/apache/nifi/key/Key.java  |  69 +++++++++++
 .../org/apache/nifi/web/api/AccessResource.java |  82 ++++++-------
 .../InvalidAuthenticationExceptionMapper.java   |  44 +++++++
 .../src/main/resources/nifi-web-api-context.xml |   1 +
 .../InvalidAuthenticationException.java         |  35 ++++++
 .../web/security/NiFiAuthenticationFilter.java  |   5 +-
 .../security/jwt/JwtAuthenticationFilter.java   |   9 +-
 .../nifi/web/security/jwt/JwtService.java       |  20 ++-
 .../security/x509/X509AuthenticationFilter.java |   4 +-
 .../nifi/web/security/jwt/JwtServiceTest.java   |  11 +-
 .../webapp/js/nf/canvas/nf-canvas-header.js     |   2 +-
 .../src/main/webapp/js/nf/canvas/nf-canvas.js   |  14 +--
 .../src/main/webapp/js/nf/login/nf-login.js     |  12 +-
 .../src/main/webapp/js/nf/nf-common.js          | 112 ++++++++++-------
 .../src/main/webapp/js/nf/nf-dialog.js          |  23 ++--
 .../src/main/webapp/js/nf/nf-storage.js         |  75 ++++++++----
 .../apache/nifi/ldap/AbstractLdapProvider.java  | 106 ----------------
 .../nifi/ldap/ActiveDirectoryProvider.java      |  51 --------
 .../java/org/apache/nifi/ldap/LdapProvider.java | 122 ++++++++++++++-----
 ...he.nifi.authentication.LoginIdentityProvider |   3 +-
 29 files changed, 627 insertions(+), 448 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
index 5126933..ea3a6c6 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
@@ -87,30 +87,21 @@ public final class CertificateUtils {
      */
     public static String extractUsername(String dn) {
         String username = dn;
-        String cn = "";
 
         // ensure the dn is specified
         if (StringUtils.isNotBlank(dn)) {
-
-            // attempt to locate the cn
-            if (dn.startsWith("CN=")) {
-                cn = StringUtils.substringBetween(dn, "CN=", ",");
-            } else if (dn.startsWith("/CN=")) {
-                cn = StringUtils.substringBetween(dn, "CN=", "/");
-            } else if (dn.startsWith("C=") || dn.startsWith("/C=")) {
-                cn = StringUtils.substringAfter(dn, "CN=");
-            } else if (dn.startsWith("/") && StringUtils.contains(dn, "CN=")) {
-                cn = StringUtils.substringAfter(dn, "CN=");
-            }
-
-            // attempt to get the username from the cn
-            if (StringUtils.isNotBlank(cn)) {
-                if (cn.endsWith(")")) {
-                    username = StringUtils.substringBetween(cn, "(", ")");
-                } else if (cn.contains(" ")) {
-                    username = StringUtils.substringAfterLast(cn, " ");
+            // determine the separate
+            final String separator = StringUtils.indexOfIgnoreCase(dn, "/cn=") > 0 ? "/" : ",";
+            
+            // attempt to locate the cd
+            final String cnPattern = "cn=";
+            final int cnIndex = StringUtils.indexOfIgnoreCase(dn, cnPattern);
+            if (cnIndex >= 0) {
+                int separatorIndex = StringUtils.indexOf(dn, separator, cnIndex);
+                if (separatorIndex > 0) {
+                    username = StringUtils.substring(dn, cnIndex + cnPattern.length(), separatorIndex);
                 } else {
-                    username = cn;
+                    username = StringUtils.substring(dn, cnIndex + cnPattern.length());
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
index 0bc6e99..2a24e0b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
@@ -16,18 +16,28 @@
  */
 package org.apache.nifi.admin.dao;
 
+import org.apache.nifi.key.Key;
+
 /**
  * Key data access.
  */
 public interface KeyDAO {
 
     /**
-     * Gets the key for the specified user identity. Returns null if no key exists for the user identity.
+     * Gets the key for the specified user identity. Returns null if no key exists for the key id.
      *
-     * @param identity The user identity
+     * @param id The key id
+     * @return The key or null
+     */
+    Key findKeyById(int id);
+
+    /**
+     * Gets the latest key for the specified identity. Returns null if no key exists for the user identity.
+     *
+     * @param identity The identity
      * @return The key or null
      */
-    String getKey(String identity);
+    Key findLatestKeyByIdentity(String identity);
 
     /**
      * Creates a key for the specified user identity.
@@ -35,5 +45,5 @@ public interface KeyDAO {
      * @param identity The user identity
      * @return The key
      */
-    String createKey(String identity);
+    Key createKey(String identity);
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
index 994e95b..f4bdc1d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
@@ -20,17 +20,23 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.util.UUID;
 import org.apache.nifi.admin.RepositoryUtils;
 import org.apache.nifi.admin.dao.DataAccessException;
 import org.apache.nifi.admin.dao.KeyDAO;
+import org.apache.nifi.key.Key;
 
 /**
  *
  */
 public class StandardKeyDAO implements KeyDAO {
 
-    private static final String SELECT_KEY_FOR_USER = "SELECT KEY "
+    private static final String SELECT_KEY_FOR_USER_BY_ID = "SELECT ID, IDENTITY, KEY "
+            + "FROM KEY "
+            + "WHERE ID = ?";
+
+    private static final String SELECT_KEY_FOR_USER_BY_IDENTITY = "SELECT ID, IDENTITY, KEY "
             + "FROM KEY "
             + "WHERE IDENTITY = ?";
 
@@ -47,18 +53,49 @@ public class StandardKeyDAO implements KeyDAO {
     }
 
     @Override
-    public String getKey(String identity) {
+    public Key findKeyById(int id) {
+        Key key = null;
+
+        PreparedStatement statement = null;
+        ResultSet rs = null;
+        try {
+            // add each authority for the specified user
+            statement = connection.prepareStatement(SELECT_KEY_FOR_USER_BY_ID);
+            statement.setInt(1, id);
+
+            // execute the query
+            rs = statement.executeQuery();
+
+            // if the key was found, add it
+            if (rs.next()) {
+                key = new Key();
+                key.setId(rs.getInt("ID"));
+                key.setIdentity(rs.getString("IDENTITY"));
+                key.setKey(rs.getString("KEY"));
+            }
+        } catch (SQLException sqle) {
+            throw new DataAccessException(sqle);
+        } finally {
+            RepositoryUtils.closeQuietly(rs);
+            RepositoryUtils.closeQuietly(statement);
+        }
+
+        return key;
+    }
+
+    @Override
+    public Key findLatestKeyByIdentity(String identity) {
         if (identity == null) {
             throw new IllegalArgumentException("Specified identity cannot be null.");
         }
 
-        String key = null;
+        Key key = null;
 
         PreparedStatement statement = null;
         ResultSet rs = null;
         try {
             // add each authority for the specified user
-            statement = connection.prepareStatement(SELECT_KEY_FOR_USER);
+            statement = connection.prepareStatement(SELECT_KEY_FOR_USER_BY_IDENTITY);
             statement.setString(1, identity);
 
             // execute the query
@@ -66,7 +103,10 @@ public class StandardKeyDAO implements KeyDAO {
 
             // if the key was found, add it
             if (rs.next()) {
-                key = rs.getString("KEY");
+                key = new Key();
+                key.setId(rs.getInt("ID"));
+                key.setIdentity(rs.getString("IDENTITY"));
+                key.setKey(rs.getString("KEY"));
             }
         } catch (SQLException sqle) {
             throw new DataAccessException(sqle);
@@ -79,20 +119,27 @@ public class StandardKeyDAO implements KeyDAO {
     }
 
     @Override
-    public String createKey(final String identity) {
+    public Key createKey(final String identity) {
         PreparedStatement statement = null;
         ResultSet rs = null;
         try {
-            final String key = UUID.randomUUID().toString();
+            final String keyValue = UUID.randomUUID().toString();
 
             // add each authority for the specified user
-            statement = connection.prepareStatement(INSERT_KEY);
+            statement = connection.prepareStatement(INSERT_KEY, Statement.RETURN_GENERATED_KEYS);
             statement.setString(1, identity);
-            statement.setString(2, key);
+            statement.setString(2, keyValue);
 
             // insert the key
             int updateCount = statement.executeUpdate();
-            if (updateCount == 1) {
+            rs = statement.getGeneratedKeys();
+
+            // verify the results
+            if (updateCount == 1 && rs.next()) {
+                final Key key = new Key();
+                key.setId(rs.getInt(1));
+                key.setIdentity(identity);
+                key.setKey(keyValue);
                 return key;
             } else {
                 throw new DataAccessException("Unable to add key for user.");

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
index 9346625..ae64c41 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
@@ -16,6 +16,8 @@
  */
 package org.apache.nifi.admin.service;
 
+import org.apache.nifi.key.Key;
+
 /**
  * Supports retrieving and issues keys for signing user tokens.
  */
@@ -24,10 +26,10 @@ public interface KeyService {
     /**
      * Gets a key for the specified user identity. Returns null if the user has not had a key issued
      *
-     * @param identity The user identity
+     * @param id The key id
      * @return The key or null
      */
-    String getKey(String identity);
+    Key getKey(int id);
 
     /**
      * Gets a key for the specified user identity. If a key does not exist, one will be created.
@@ -36,5 +38,5 @@ public interface KeyService {
      * @return The key
      * @throws AdministrationException if it failed to get/create the key
      */
-    String getOrCreateKey(String identity);
+    Key getOrCreateKey(String identity);
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyAction.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyAction.java
deleted file mode 100644
index f12b1ef..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyAction.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.admin.service.action;
-
-import org.apache.nifi.admin.dao.DAOFactory;
-import org.apache.nifi.authorization.AuthorityProvider;
-
-import org.apache.nifi.admin.dao.KeyDAO;
-
-/**
- * Gets a key for the specified user identity.
- */
-public class GetKeyAction implements AdministrationAction<String> {
-
-    private final String identity;
-
-    public GetKeyAction(String identity) {
-        this.identity = identity;
-    }
-
-    @Override
-    public String execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
-        final KeyDAO keyDao = daoFactory.getKeyDAO();
-        return keyDao.getKey(identity);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java
new file mode 100644
index 0000000..8763b9d
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.admin.service.action;
+
+import org.apache.nifi.admin.dao.DAOFactory;
+import org.apache.nifi.authorization.AuthorityProvider;
+
+import org.apache.nifi.admin.dao.KeyDAO;
+import org.apache.nifi.key.Key;
+
+/**
+ * Gets a key for the specified key id.
+ */
+public class GetKeyByIdAction implements AdministrationAction<Key> {
+
+    private final int id;
+
+    public GetKeyByIdAction(int id) {
+        this.id = id;
+    }
+
+    @Override
+    public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
+        final KeyDAO keyDao = daoFactory.getKeyDAO();
+        return keyDao.findKeyById(id);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java
new file mode 100644
index 0000000..9bcb0b3
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.admin.service.action;
+
+import org.apache.nifi.admin.dao.DAOFactory;
+import org.apache.nifi.authorization.AuthorityProvider;
+
+import org.apache.nifi.admin.dao.KeyDAO;
+import org.apache.nifi.key.Key;
+
+/**
+ * Gets a key for the specified key id.
+ */
+public class GetKeyByIdentityAction implements AdministrationAction<Key> {
+
+    private final String identity;
+
+    public GetKeyByIdentityAction(String identity) {
+        this.identity = identity;
+    }
+
+    @Override
+    public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
+        final KeyDAO keyDao = daoFactory.getKeyDAO();
+        return keyDao.findLatestKeyByIdentity(identity);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java
index 209cbcd..bb85b6f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java
@@ -20,11 +20,12 @@ import org.apache.nifi.admin.dao.DAOFactory;
 import org.apache.nifi.authorization.AuthorityProvider;
 
 import org.apache.nifi.admin.dao.KeyDAO;
+import org.apache.nifi.key.Key;
 
 /**
  * Gets a key for the specified user identity.
  */
-public class GetOrCreateKeyAction implements AdministrationAction<String> {
+public class GetOrCreateKeyAction implements AdministrationAction<Key> {
 
     private final String identity;
 
@@ -33,10 +34,10 @@ public class GetOrCreateKeyAction implements AdministrationAction<String> {
     }
 
     @Override
-    public String execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
+    public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
         final KeyDAO keyDao = daoFactory.getKeyDAO();
 
-        String key = keyDao.getKey(identity);
+        Key key = keyDao.findLatestKeyByIdentity(identity);
         if (key == null) {
             key = keyDao.createKey(identity);
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
index 7dff9d8..ca0a124 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
@@ -19,7 +19,7 @@ package org.apache.nifi.admin.service.impl;
 import org.apache.nifi.admin.dao.DataAccessException;
 import org.apache.nifi.admin.service.AdministrationException;
 import org.apache.nifi.admin.service.KeyService;
-import org.apache.nifi.admin.service.action.GetKeyAction;
+import org.apache.nifi.admin.service.action.GetKeyByIdAction;
 import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
 import org.apache.nifi.admin.service.transaction.Transaction;
 import org.apache.nifi.admin.service.transaction.TransactionBuilder;
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import org.apache.nifi.key.Key;
 
 /**
  *
@@ -44,19 +45,17 @@ public class StandardKeyService implements KeyService {
     private TransactionBuilder transactionBuilder;
 
     @Override
-    public String getKey(String identity) {
-        // TODO: Change this service to look up by "key ID" instead of identity
-        // TODO: Change the return type to a Key POJO to support key rotation
+    public Key getKey(int id) {
         Transaction transaction = null;
-        String key = null;
+        Key key = null;
 
         readLock.lock();
         try {
             // start the transaction
             transaction = transactionBuilder.start();
 
-            // seed the accounts
-            GetKeyAction addActions = new GetKeyAction(identity);
+            // get the key
+            GetKeyByIdAction addActions = new GetKeyByIdAction(id);
             key = transaction.execute(addActions);
 
             // commit the transaction
@@ -76,11 +75,9 @@ public class StandardKeyService implements KeyService {
     }
 
     @Override
-    public String getOrCreateKey(String identity) {
-        // TODO: Change this service to look up by "key ID" instead of identity
-        // TODO: Change the return type to a Key POJO to support key rotation
+    public Key getOrCreateKey(String identity) {
         Transaction transaction = null;
-        String key = null;
+        Key key = null;
 
         writeLock.lock();
         try {

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
new file mode 100644
index 0000000..b7158c2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
@@ -0,0 +1,69 @@
+/*
+ * 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.key;
+
+import java.io.Serializable;
+
+/**
+ * An signing key for a NiFi user.
+ */
+public class Key implements Serializable {
+
+    private int id;
+    private String identity;
+    private String key;
+
+    /**
+     * The key id.
+     * 
+     * @return the id
+     */
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    /**
+     * The identity of the user this key is associated with.
+     * 
+     * @return the identity
+     */
+    public String getIdentity() {
+        return identity;
+    }
+
+    public void setIdentity(String identity) {
+        this.identity = identity;
+    }
+
+    /**
+     * The signing key.
+     * 
+     * @return the signing key
+     */
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index 2e1c44e..27d7d29 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -55,6 +55,7 @@ import org.apache.nifi.web.api.dto.RevisionDTO;
 import org.apache.nifi.web.api.entity.AccessStatusEntity;
 import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
 import org.apache.nifi.web.security.ProxiedEntitiesUtils;
 import org.apache.nifi.web.security.UntrustedProxyException;
 import org.apache.nifi.web.security.jwt.JwtService;
@@ -185,55 +186,52 @@ public class AccessResource extends ApplicationResource {
                     accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
                     accessStatus.setMessage("No credentials supplied, unknown user.");
                 } else {
-                    // Extract the Base64 encoded token from the Authorization header
-                    final String token = StringUtils.substringAfterLast(authorization, " ");
-
                     try {
+                        // Extract the Base64 encoded token from the Authorization header
+                        final String token = StringUtils.substringAfterLast(authorization, " ");
                         final String principal = jwtService.getAuthenticationFromToken(token);
 
-                        // ensure we have something we can work with (certificate or credentials)
-                        if (principal == null) {
-                            throw new IllegalArgumentException("The specific token is not valid.");
-                        } else {
-                            // set the user identity
-                            accessStatus.setIdentity(principal);
-                            accessStatus.setUsername(CertificateUtils.extractUsername(principal));
+                        // set the user identity
+                        accessStatus.setIdentity(principal);
+                        accessStatus.setUsername(CertificateUtils.extractUsername(principal));
 
-                            // without a certificate, this is not a proxied request
-                            final List<String> chain = Arrays.asList(principal);
+                        // without a certificate, this is not a proxied request
+                        final List<String> chain = Arrays.asList(principal);
 
-                            // check authorization for this user
-                            checkAuthorization(chain);
+                        // check authorization for this user
+                        checkAuthorization(chain);
 
-                            // no issues with authorization
-                            accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
-                            accessStatus.setMessage("Account is active and authorized");
-                        }
+                        // no issues with authorization
+                        accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
+                        accessStatus.setMessage("Account is active and authorized");
                     } catch (JwtException e) {
-                        // TODO: Handle the exception from a failed JWT verification
-                        throw new AccessDeniedException("The JWT could not be verified", e);
+                        throw new InvalidAuthenticationException(e.getMessage(), e);
                     }
                 }
             } else {
-                final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates);
-
-                // get the proxy chain and ensure its populated
-                final List<String> proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(httpServletRequest, authenticationResponse.getIdentity());
-                if (proxyChain.isEmpty()) {
-                    logger.error(String.format("Unable to parse the proxy chain %s from the incoming request.", authenticationResponse.getIdentity()));
-                    throw new IllegalArgumentException("Unable to determine the user from the incoming request.");
-                }
+                try {
+                    final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates);
+
+                    // get the proxy chain and ensure its populated
+                    final List<String> proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(httpServletRequest, authenticationResponse.getIdentity());
+                    if (proxyChain.isEmpty()) {
+                        logger.error(String.format("Unable to parse the proxy chain %s from the incoming request.", authenticationResponse.getIdentity()));
+                        throw new IllegalArgumentException("Unable to determine the user from the incoming request.");
+                    }
 
-                // ensure the proxy chain is authorized
-                checkAuthorization(proxyChain);
+                    // set the user identity
+                    accessStatus.setIdentity(proxyChain.get(0));
+                    accessStatus.setUsername(CertificateUtils.extractUsername(proxyChain.get(0)));
 
-                // set the user identity
-                accessStatus.setIdentity(proxyChain.get(0));
-                accessStatus.setUsername(CertificateUtils.extractUsername(proxyChain.get(0)));
+                    // ensure the proxy chain is authorized
+                    checkAuthorization(proxyChain);
 
-                // no issues with authorization
-                accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
-                accessStatus.setMessage("Account is active and authorized");
+                    // no issues with authorization
+                    accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
+                    accessStatus.setMessage("Account is active and authorized");
+                } catch (final IllegalArgumentException iae) {
+                    throw new InvalidAuthenticationException(iae.getMessage(), iae);
+                }
             }
         } catch (final UsernameNotFoundException unfe) {
             accessStatus.setStatus(AccessStatusDTO.Status.UNREGISTERED.name());
@@ -323,20 +321,20 @@ public class AccessResource extends ApplicationResource {
                 final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(new LoginCredentials(username, password));
                 final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);
                 final long minExpiration = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-                
+
                 long expiration = authenticationResponse.getExpiration();
                 if (expiration > maxExpiration) {
                     expiration = maxExpiration;
-                    
-                    logger.warn(String.format("Max token expiration exceeded. Setting expiration to %s from %s for %s", expiration, 
+
+                    logger.warn(String.format("Max token expiration exceeded. Setting expiration to %s from %s for %s", expiration,
                             authenticationResponse.getExpiration(), authenticationResponse.getIdentity()));
                 } else if (expiration < minExpiration) {
                     expiration = minExpiration;
-                    
-                    logger.warn(String.format("Min token expiration not met. Setting expiration to %s from %s for %s", expiration, 
+
+                    logger.warn(String.format("Min token expiration not met. Setting expiration to %s from %s for %s", expiration,
                             authenticationResponse.getExpiration(), authenticationResponse.getIdentity()));
                 }
-                
+
                 // create the authentication token
                 // TODO: Some Spring beans return "" for getClass().getSimpleName(). Using getName() temporarily
                 loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, loginIdentityProvider.getClass().getName());

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.java
new file mode 100644
index 0000000..14d5139
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.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.web.api.config;
+
+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.apache.nifi.web.security.InvalidAuthenticationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index 73929d8..9f3d2f5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -255,6 +255,7 @@
 
     <!-- exception mapping -->
     <bean class="org.apache.nifi.web.api.config.AccessDeniedExceptionMapper" scope="singleton"/>
+    <bean class="org.apache.nifi.web.api.config.InvalidAuthenticationExceptionMapper" scope="singleton"/>
     <bean class="org.apache.nifi.web.api.config.AuthenticationCredentialsNotFoundExceptionMapper" scope="singleton"/>
     <bean class="org.apache.nifi.web.api.config.AccountNotFoundExceptionMapper" scope="singleton"/>
     <bean class="org.apache.nifi.web.api.config.AdministrationExceptionMapper" scope="singleton"/>

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java
new file mode 100644
index 0000000..1065152
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/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.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/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
index ec34ace..f09d610 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
@@ -36,7 +36,6 @@ import org.slf4j.LoggerFactory;
 import org.springframework.security.authentication.AccountStatusException;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationServiceException;
-import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -134,8 +133,8 @@ public abstract class NiFiAuthenticationFilter implements Filter {
                 response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                 out.println("Access is denied.");
             }
-        } else if (ae instanceof BadCredentialsException) {
-            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+        } else if (ae instanceof InvalidAuthenticationException) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
             out.println(ae.getMessage());
         } else if (ae instanceof AccountStatusException) {
             response.setStatus(HttpServletResponse.SC_FORBIDDEN);

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
index dea5bba..20675fb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
@@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.util.Arrays;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
 
 /**
  */
@@ -49,7 +50,6 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
         // TODO: Refactor request header extraction logic to shared utility as it is duplicated in AccessResource
 
         // get the principal out of the user token
-        // look for an authorization token
         final String authorization = request.getHeader(AUTHORIZATION);
 
         // if there is no authorization header, we don't know the user
@@ -61,9 +61,6 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
 
             try {
                 final String jwtPrincipal = jwtService.getAuthenticationFromToken(token);
-                if (jwtPrincipal == null) {
-                    return null;
-                }
 
                 if (isNewAccountRequest(request)) {
                     return new NewAccountAuthenticationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
@@ -71,9 +68,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
                     return new NiFiAuthenticationRequestToken(Arrays.asList(jwtPrincipal));
                 }
             } catch (JwtException e) {
-                // TODO: Is this the correct way to handle an unverified token?
-                logger.error("Could not verify JWT", e);
-                return null;
+                throw new InvalidAuthenticationException(e.getMessage(), e);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
index f006e5b..9635354 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
@@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Calendar;
+import org.apache.nifi.key.Key;
 
 /**
  *
@@ -88,17 +89,16 @@ public class JwtService {
                 public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
                     final String identity = claims.getSubject();
 
-                    // TODO: Currently the kid field is identical to identity, but will be a unique key ID when key rotation is implemented
-                    final String keyId = claims.get(KEY_ID_CLAIM, String.class);
-                    // The key is unique per identity and should be retrieved from the key service
-                    final String key = keyService.getKey(keyId);
+                    // Get the key based on the key id in the claims
+                    final Integer keyId = claims.get(KEY_ID_CLAIM, Integer.class);
+                    final Key key = keyService.getKey(keyId);
 
                     // Ensure we were able to find a key that was previously issued by this key service for this user
-                    if (key == null) {
+                    if (key == null || key.getKey() == null) {
                         throw new UnsupportedJwtException("Unable to determine signing key for " + identity + " [kid: " + keyId + "]");
                     }
 
-                    return key.getBytes(StandardCharsets.UTF_8);
+                    return key.getKey().getBytes(StandardCharsets.UTF_8);
                 }
             }).parseClaimsJws(base64EncodedToken);
         } catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException | AdministrationException e) {
@@ -137,21 +137,19 @@ public class JwtService {
 
         try {
             // Get/create the key for this user
-            final String key = keyService.getOrCreateKey(identity);
-            final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
+            final Key key = keyService.getOrCreateKey(identity);
+            final byte[] keyBytes = key.getKey().getBytes(StandardCharsets.UTF_8);
 
             logger.trace("Generating JWT for " + authenticationToken);
 
             // TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
 
-            // TODO: Change kid field to key ID when KeyService is refactored
-
             // Build the token
             return Jwts.builder().setSubject(identity)
                     .setIssuer(authenticationToken.getIssuer())
                     .setAudience(authenticationToken.getIssuer())
                     .claim(USERNAME_CLAIM, username)
-                    .claim(KEY_ID_CLAIM, identity)
+                    .claim(KEY_ID_CLAIM, key.getId())
                     .setExpiration(expiration.getTime())
                     .setIssuedAt(Calendar.getInstance().getTime())
                     .signWith(SIGNATURE_ALGORITHM, keyBytes).compact();

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
index e626f74..dd7d47e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
@@ -21,6 +21,7 @@ import java.util.List;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.apache.nifi.authentication.AuthenticationResponse;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
 import org.apache.nifi.web.security.NiFiAuthenticationFilter;
 import org.apache.nifi.web.security.ProxiedEntitiesUtils;
 import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken;
@@ -28,7 +29,6 @@ import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
 import org.apache.nifi.web.security.user.NewAccountRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.security.authentication.BadCredentialsException;
 
 /**
  * Custom X509 filter that will inspect the HTTP headers for a proxied user before extracting the user details from the client certificate.
@@ -58,7 +58,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
         try {
             authenticationResponse = certificateIdentityProvider.authenticate(certificates);
         } catch (final IllegalArgumentException iae) {
-            throw new BadCredentialsException(iae.getMessage(), iae);
+            throw new InvalidAuthenticationException(iae.getMessage(), iae);
         }
 
         final List<String> proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(request, authenticationResponse.getIdentity());

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
index c9107b9..ea3e122 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
@@ -22,10 +22,12 @@ import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import org.apache.nifi.key.Key;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.when;
 
 /**
@@ -117,9 +119,14 @@ public class JwtServiceTest {
 
     @Before
     public void setUp() throws Exception {
+        final Key key = new Key();
+        key.setId(0);
+        key.setIdentity(HMAC_SECRET);
+        key.setKey(HMAC_SECRET);
+        
         mockKeyService = Mockito.mock(KeyService.class);
-        when(mockKeyService.getKey(anyString())).thenReturn(HMAC_SECRET);
-        when(mockKeyService.getOrCreateKey(anyString())).thenReturn(HMAC_SECRET);
+        when(mockKeyService.getKey(anyInt())).thenReturn(key);
+        when(mockKeyService.getOrCreateKey(anyString())).thenReturn(key);
         jwtService = new JwtService(mockKeyService);
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
index eab5297..7d63534 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
@@ -142,7 +142,7 @@ nf.CanvasHeader = (function () {
             });
 
             // show the login link if supported and user is currently anonymous
-            var isAnonymous = $('#current-user').text() === nf.Canvas.ANONYMOUS_USER_TEXT;
+            var isAnonymous = $('#current-user').text() === nf.Common.ANONYMOUS_USER_TEXT;
             if (supportsLogin === true && isAnonymous) {
                 // login link
                 $('#login-link').click(function () {

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index 09a8212..c9498fe 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -936,7 +936,6 @@ nf.Canvas = (function () {
     };
 
     return {
-        ANONYMOUS_USER_TEXT: 'Anonymous user',
         CANVAS_OFFSET: 0,
         /**
          * Determines if the current broswer supports SVG.
@@ -1056,17 +1055,8 @@ nf.Canvas = (function () {
                             $('#logout-link-container').show();
                         }
                     } else {
-                        // alert user's of anonymous access
-                        $('#anonymous-user-alert').show().qtip($.extend({}, nf.Common.config.tooltipConfig, {
-                            content: 'You are accessing with limited authority. Log in or request an account to access with additional authority granted to you by an administrator.',
-                            position: {
-                                my: 'top right',
-                                at: 'bottom left'
-                            }
-                        }));
-
-                        // render the anonymous user text
-                        $('#current-user').text(nf.Canvas.ANONYMOUS_USER_TEXT).show();
+                        // set the anonymous user label
+                        nf.Common.setAnonymousUserLabel();
                     }
                     deferred.resolve();
                 }).fail(function (xhr, status, error) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
index 89db6ce..bd39a44 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
@@ -96,8 +96,10 @@ nf.Login = (function () {
                 'password': $('#password').val()
             }
         }).done(function (jwt) {
-            // store the jwt and reload the page
-            nf.Storage.setItem('jwt', jwt, nf.Common.getJwtExpiration(jwt));
+            // get the payload and store the token with the appropirate expiration
+            var token = nf.Common.getJwtPayload(jwt);
+            var expiration = parseInt(token['exp'], 10) * nf.Common.MILLIS_PER_SECOND;
+            nf.Storage.setItem('jwt', jwt, expiration);
 
             // check to see if they actually have access now
             $.ajax({
@@ -112,8 +114,7 @@ nf.Login = (function () {
                     nf.Common.scheduleTokenRefresh();
             
                     // show the user
-                    var user = nf.Common.getJwtSubject(jwt);
-                    $('#nifi-user-submit-justification').text(user);
+                    $('#nifi-user-submit-justification').text(token['preferred_username']);
 
                     // show the registration form
                     initializeNiFiRegistration();
@@ -133,8 +134,7 @@ nf.Login = (function () {
                 nf.Common.scheduleTokenRefresh();
 
                 // show the user
-                var user = nf.Common.getJwtSubject(jwt);
-                $('#nifi-user-submit-justification').text(user);
+                $('#nifi-user-submit-justification').text(token['preferred_username']);
 
                 if (xhr.status === 401) {
                     initializeNiFiRegistration();

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 3be6b91..9202819 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -54,9 +54,17 @@ $(document).ready(function () {
     // include jwt when possible
     $.ajaxSetup({
         'beforeSend': function(xhr) {
+            var hadToken = nf.Storage.hasItem('jwt');
+            
+            // get the token to include in all requests
             var token = nf.Storage.getItem('jwt');
-            if (token) {
+            if (token !== null) {
                 xhr.setRequestHeader('Authorization', 'Bearer ' + token);
+            } else {
+                // if the current user was logged in with a token and the token just expired, reload
+                if (hadToken === true) {
+                    return false;
+                }
             }
         }
     });
@@ -83,6 +91,8 @@ nf.Common = (function () {
     var tokenRefreshInterval = null;
     
     return {
+        ANONYMOUS_USER_TEXT: 'Anonymous user',
+        
         config: {
             sensitiveText: 'Sensitive value set',
             tooltipConfig: {
@@ -100,9 +110,6 @@ nf.Common = (function () {
                     at: 'top right',
                     my: 'bottom left'
                 }
-            },
-            urls: {
-                token: '../nifi-api/access/token'
             }
         },
 
@@ -148,7 +155,7 @@ nf.Common = (function () {
             }
             
             // set the interval to one hour
-            var interval = nf.Common.MILLIS_PER_HOUR;
+            var interval = 10 * nf.Common.MILLIS_PER_MINUTE;
             
             var checkExpiration = function () {
                 var expiration = nf.Storage.getItemExpiration('jwt');
@@ -161,13 +168,16 @@ nf.Common = (function () {
                     // get the time remainging plus a little bonus time to reload the token
                     var timeRemaining = expirationDate.valueOf() - now.valueOf() - nf.Common.MILLIS_PER_MINUTE;
                     if (timeRemaining < interval) {
-                        // if the token will expire before the next interval minus some bonus time, refresh now
-                        $.ajax({
-                            type: 'POST',
-                            url: nf.Common.config.urls.token
-                        }).done(function (jwt) {
-                            nf.Storage.setItem('jwt', jwt, nf.Common.getJwtExpiration(jwt));
-                        });
+                        if ($('#current-user').text() !== nf.Common.ANONYMOUS_USER_TEXT && !$('#anonymous-user-alert').is(':visible')) {
+                            // if the token will expire before the next interval minus some bonus time, notify the user to re-login
+                            $('#anonymous-user-alert').show().qtip($.extend({}, nf.Common.config.tooltipConfig, {
+                                content: 'Your session will expire soon. Please log in again to avoid being automatically logged out.',
+                                position: {
+                                    my: 'top right',
+                                    at: 'bottom left'
+                                }
+                            }));
+                        }
                     }
                 }
             };
@@ -180,56 +190,46 @@ nf.Common = (function () {
         },
 
         /**
-         * Extracts the subject from the specified jwt. If the jwt is not as expected
-         * an empty string is returned.
-         * 
-         * @param {string} jwt
-         * @returns {string}
+         * Sets the anonymous user label.
          */
-        getJwtSubject: function (jwt) {
-            if (nf.Common.isDefinedAndNotNull(jwt)) {
-                var segments = jwt.split(/\./);
-                if (segments.length !== 3) {
-                    return '';
-                }
-
-                var rawPayload = $.base64.atob(segments[1]);
-                var payload = JSON.parse(rawPayload);
-
-                if (nf.Common.isDefinedAndNotNull(payload['preferred_username'])) {
-                    return payload['preferred_username'];
-                } else {
-                    '';
-                }
+        setAnonymousUserLabel: function () {
+            var anonymousUserAlert = $('#anonymous-user-alert');
+            if (anonymousUserAlert.data('qtip')) {
+                anonymousUserAlert.qtip('api').destroy(true);
             }
+                        
+            // alert user's of anonymous access
+            anonymousUserAlert.show().qtip($.extend({}, nf.Common.config.tooltipConfig, {
+                content: 'You are accessing with limited authority. Log in or request an account to access with additional authority granted to you by an administrator.',
+                position: {
+                    my: 'top right',
+                    at: 'bottom left'
+                }
+            }));
 
-            return '';
+            // render the anonymous user text
+            $('#current-user').text(nf.Common.ANONYMOUS_USER_TEXT).show();  
         },
 
         /**
-         * Extracts the expiration from the specified jwt. If the jwt is not as expected
-         * a null value is returned.
+         * Extracts the subject from the specified jwt. If the jwt is not as expected
+         * an empty string is returned.
          * 
          * @param {string} jwt
-         * @returns {integer}
+         * @returns {string}
          */
-        getJwtExpiration: function (jwt) {
+        getJwtPayload: function (jwt) {
             if (nf.Common.isDefinedAndNotNull(jwt)) {
                 var segments = jwt.split(/\./);
                 if (segments.length !== 3) {
-                    return null;
+                    return '';
                 }
 
                 var rawPayload = $.base64.atob(segments[1]);
                 var payload = JSON.parse(rawPayload);
 
-                if (nf.Common.isDefinedAndNotNull(payload['exp'])) {
-                    try {
-                        // jwt exp is in seconds
-                        return parseInt(payload['exp'], 10) * nf.Common.MILLIS_PER_SECOND;
-                    } catch (e) {
-                        return null;
-                    }
+                if (nf.Common.isDefinedAndNotNull(payload)) {
+                    return payload;
                 } else {
                     return null;
                 }
@@ -313,6 +313,28 @@ nf.Common = (function () {
          * @argument {string} error     The error
          */
         handleAjaxError: function (xhr, status, error) {
+            if (status === 'canceled') {
+                if ($('#splash').is(':visible')) {
+                    $('#message-title').text('Session Expired');
+                    $('#message-content').text('Your session has expired. Please reload to log in again.');
+
+                    // show the error pane
+                    $('#message-pane').show();
+
+                    // close the canvas
+                    nf.Common.closeCanvas();
+                } else {
+                    nf.Dialog.showOkDialog({
+                        dialogContent: 'Your session has expired. Please press Ok to log in again.',
+                        overlayBackground: false,
+                        okHandler: function () {
+                            window.location = '/nifi';
+                        }
+                    });
+                }
+                return;
+            }
+            
             // if an error occurs while the splash screen is visible close the canvas show the error message
             if ($('#splash').is(':visible')) {
                 if (xhr.status === 401) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js
index 2d1183f..d305e4d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js
@@ -23,15 +23,6 @@ $(document).ready(function () {
 
     // configure the ok dialog
     $('#nf-ok-dialog').modal({
-        buttons: [{
-                buttonText: 'Ok',
-                handler: {
-                    click: function () {
-                        // close the dialog
-                        $('#nf-ok-dialog').modal('hide');
-                    }
-                }
-            }],
         handler: {
             close: function () {
                 // clear the content
@@ -77,6 +68,20 @@ nf.Dialog = (function () {
             var content = $('<p></p>').append(options.dialogContent);
             $('#nf-ok-dialog-content').append(content);
 
+            // update the button model
+            $('#nf-ok-dialog').modal('setButtonModel', [{
+                buttonText: 'Ok',
+                handler: {
+                    click: function () {
+                        // close the dialog
+                        $('#nf-ok-dialog').modal('hide');
+                        if (typeof options.okHandler === 'function') {
+                            options.okHandler.call(this);
+                        }
+                    }
+                }
+            }]);
+
             // show the dialog
             $('#nf-ok-dialog').modal('setHeaderText', options.headerText).modal('setOverlayBackground', options.overlayBackground).modal('show');
         },

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
index cdb2e9e..7b0c3f6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
@@ -42,32 +42,18 @@ nf.Storage = (function () {
     };
     
     /**
-     * If the item at key is not expired, the value of field is returned. Otherwise, null.
+     * Gets an enty for the key. The entry expiration is not checked.
      * 
      * @param {string} key
-     * @param {string} field
-     * @return {object} the value
      */
-    var getEntryField = function (key, field) {
+    var getEntry = function (key) {
         try {
             // parse the entry
             var entry = JSON.parse(localStorage.getItem(key));
 
             // ensure the entry and item are present
             if (nf.Common.isDefinedAndNotNull(entry)) {
-                
-                // if the entry is expired, drop it and return null
-                if (checkExpiration(entry)) {
-                    nf.Storage.removeItem(key);
-                    return null;
-                }
-
-                // if the entry has the specified field return its value
-                if (nf.Common.isDefinedAndNotNull(entry[field])) {
-                    return entry[field];
-                } else {
-                    return null;
-                }
+                return entry;
             } else {
                 return null;
             }
@@ -75,7 +61,7 @@ nf.Storage = (function () {
             return null;
         }
     };
-
+    
     return {
         /**
          * Initializes the storage. Items will be persisted for two days. Once the scripts runs
@@ -86,11 +72,9 @@ nf.Storage = (function () {
                 try {
                     // get the next item
                     var key = localStorage.key(i);
-                    var entry = JSON.parse(localStorage.getItem(key));
-
-                    if (checkExpiration(entry)) {
-                        nf.Storage.removeItem(key);
-                    }
+                    
+                    // attempt to get the item which will expire if necessary
+                    nf.Storage.getItem(key);
                 } catch (e) {
                 }
             }
@@ -118,6 +102,17 @@ nf.Storage = (function () {
         },
         
         /**
+         * Returns whether there is an entry for this key. This will not check the expiration. If
+         * the entry is expired, it will return null on a subsequent getItem invocation.
+         * 
+         * @param {string} key
+         * @returns {boolean}
+         */
+        hasItem: function (key) {
+            return getEntry(key) !== null;
+        },
+        
+        /**
          * Gets the item with the specified key. If an item with this key does
          * not exist, null is returned. If an item exists but cannot be parsed
          * or is malformed/unrecognized, null is returned.
@@ -125,18 +120,44 @@ nf.Storage = (function () {
          * @param {type} key
          */
         getItem: function (key) {
-            return getEntryField(key, 'item');
+            var entry = getEntry(key);
+            if (entry === null) {
+                return null;
+            }
+
+            // if the entry is expired, drop it and return null
+            if (checkExpiration(entry)) {
+                nf.Storage.removeItem(key);
+                return null;
+            }
+
+            // if the entry has the specified field return its value
+            if (nf.Common.isDefinedAndNotNull(entry['item'])) {
+                return entry['item'];
+            } else {
+                return null;
+            }
         },
         
         /**
-         * Gets the expiration for the specified item. If the item does not exists our could 
-         * not be parsed, returns null.
+         * Gets the expiration for the specified item. This will not check the expiration. If
+         * the entry is expired, it will return null on a subsequent getItem invocation.
          * 
          * @param {string} key
          * @returns {integer}
          */
         getItemExpiration: function (key) {
-            return getEntryField(key, 'expires');
+            var entry = getEntry(key);
+            if (entry === null) {
+                return null;
+            }
+
+            // if the entry has the specified field return its value
+            if (nf.Common.isDefinedAndNotNull(entry['expires'])) {
+                return entry['expires'];
+            } else {
+                return null;
+            }
         },
         
         /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/AbstractLdapProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/AbstractLdapProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/AbstractLdapProvider.java
deleted file mode 100644
index 64c48bf..0000000
--- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/AbstractLdapProvider.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.ldap;
-
-import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authentication.AuthenticationResponse;
-import org.apache.nifi.authentication.LoginCredentials;
-import org.apache.nifi.authentication.LoginIdentityProvider;
-import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext;
-import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext;
-import org.apache.nifi.authentication.exception.IdentityAccessException;
-import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
-import org.apache.nifi.authorization.exception.ProviderCreationException;
-import org.apache.nifi.authorization.exception.ProviderDestructionException;
-import org.apache.nifi.util.FormatUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.ldap.CommunicationException;
-import org.springframework.security.authentication.AuthenticationServiceException;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
-import org.springframework.security.ldap.userdetails.LdapUserDetails;
-
-/**
- * Abstract LDAP based implementation of a login identity provider.
- */
-public abstract class AbstractLdapProvider implements LoginIdentityProvider {
-
-    private static final Logger logger = LoggerFactory.getLogger(AbstractLdapProvider.class);
-
-    private AbstractLdapAuthenticationProvider provider;
-    private long expiration;
-
-    @Override
-    public final void initialize(final LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException {
-    }
-
-    @Override
-    public final void onConfigured(final LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
-        final String rawExpiration = configurationContext.getProperty("Expiration Duration");
-        if (StringUtils.isBlank(rawExpiration)) {
-            throw new ProviderCreationException("The Expiration Duration must be specified.");
-        }
-
-        try {
-            expiration = FormatUtils.getTimeDuration(rawExpiration, TimeUnit.MILLISECONDS);
-        } catch (final IllegalArgumentException iae) {
-            throw new ProviderCreationException(String.format("The Expiration Duration '%s' is not a valid time duration", rawExpiration));
-        }
-
-        provider = getLdapAuthenticationProvider(configurationContext);
-    }
-
-    protected abstract AbstractLdapAuthenticationProvider getLdapAuthenticationProvider(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException;
-
-    @Override
-    public final AuthenticationResponse authenticate(final LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException {
-        if (provider == null) {
-            throw new IdentityAccessException("The LDAP authentication provider is not initialized.");
-        }
-
-        try {
-            // perform the authentication
-            final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
-            final Authentication authentication = provider.authenticate(token);
-
-            // attempt to get the ldap user details to get the DN
-            if (authentication.getPrincipal() instanceof LdapUserDetails) {
-                final LdapUserDetails userDetails = (LdapUserDetails) authentication.getPrincipal();
-                return new AuthenticationResponse(userDetails.getDn(), credentials.getUsername(), expiration);
-            } else {
-                return new AuthenticationResponse(authentication.getName(), credentials.getUsername(), expiration);
-            }
-        } catch (final CommunicationException | AuthenticationServiceException e) {
-            logger.error(e.getMessage());
-            if (logger.isDebugEnabled()) {
-                logger.debug(StringUtils.EMPTY, e);
-            }
-            throw new IdentityAccessException("Unable to query the configured directory server. See the logs for additional details.", e);
-        } catch (final BadCredentialsException bce) {
-            throw new InvalidLoginCredentialsException(bce.getMessage(), bce);
-        }
-    }
-
-    @Override
-    public final void preDestruction() throws ProviderDestructionException {
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ActiveDirectoryProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ActiveDirectoryProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ActiveDirectoryProvider.java
deleted file mode 100644
index e43c1ee..0000000
--- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ActiveDirectoryProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.ldap;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext;
-import org.apache.nifi.authorization.exception.ProviderCreationException;
-import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
-import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
-
-/**
- * Active Directory based implementation of a login identity provider.
- */
-public class ActiveDirectoryProvider extends AbstractLdapProvider {
-
-    @Override
-    protected AbstractLdapAuthenticationProvider getLdapAuthenticationProvider(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
-
-        final String url = configurationContext.getProperty("Url");
-        if (StringUtils.isBlank(url)) {
-            throw new ProviderCreationException("The Active Directory 'Url' must be specified.");
-        }
-
-        final String domain = configurationContext.getProperty("Domain");
-        final String userSearchBase = configurationContext.getProperty("User Search Base");
-
-        final ActiveDirectoryLdapAuthenticationProvider activeDirectoryAuthenticationProvider
-                = new ActiveDirectoryLdapAuthenticationProvider(StringUtils.isBlank(domain) ? null : domain, url, StringUtils.isBlank(userSearchBase) ? null : userSearchBase);
-
-        final String userSearchFilter = configurationContext.getProperty("User Search Filter");
-        if (StringUtils.isNotBlank(userSearchFilter)) {
-            activeDirectoryAuthenticationProvider.setSearchFilter(userSearchFilter);
-        }
-
-        return activeDirectoryAuthenticationProvider;
-    }
-}