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

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


The following commit(s) were added to refs/heads/main by this push:
     new d846a74  NIFI-7568 - Applied Kerberos mappings to authentication 
requests. Kerberos mappings should now be applied correctly in H2 database for 
username/password based login. Added tests. Logout now deletes signing key by 
key ID rather than identity. Validate token expiration now uses mapped identity 
instead, which allows logging of the mapped identity. Updated delete key to 
expect only 0 or 1 keys deleted.
d846a74 is described below

commit d846a74730edb4d142e0a3051c60e9fec33f1f09
Author: Nathan Gough <thena...@gmail.com>
AuthorDate: Mon Jun 29 19:18:22 2020 -0400

    NIFI-7568 - Applied Kerberos mappings to authentication requests. Kerberos 
mappings should now be applied correctly in H2 database for username/password 
based login. Added tests.
    Logout now deletes signing key by key ID rather than identity.
    Validate token expiration now uses mapped identity instead, which allows 
logging of the mapped identity.
    Updated delete key to expect only 0 or 1 keys deleted.
    
    This closes #4416.
    
    Signed-off-by: Andy LoPresto <alopre...@apache.org>
---
 .../java/org/apache/nifi/admin/dao/KeyDAO.java     |   6 +-
 .../apache/nifi/admin/dao/impl/StandardKeyDAO.java |   8 +-
 .../org/apache/nifi/admin/service/KeyService.java  |   4 +-
 ...{DeleteKeysAction.java => DeleteKeyAction.java} |  17 +-
 .../admin/service/impl/StandardKeyService.java     |  26 +--
 .../src/main/java/org/apache/nifi/key/Key.java     |   9 +
 .../org/apache/nifi/web/api/AccessResource.java    |  19 +-
 .../accesscontrol/ITAccessTokenEndpoint.java       |   9 +-
 .../nifi/integration/util/NiFiTestAuthorizer.java  |   4 +-
 .../util/NiFiTestLoginIdentityProvider.java        |   1 +
 .../apache/nifi/integration/util/NiFiTestUser.java |   8 +
 .../nifi-mapped-identities.properties              | 144 +++++++++++++++
 .../web/security/jwt/JwtAuthenticationFilter.java  |   2 +-
 .../apache/nifi/web/security/jwt/JwtService.java   |  27 ++-
 .../jwt/JwtAuthenticationProviderTest.java         | 132 ++++++++++++++
 .../nifi/web/security/jwt/JwtServiceTest.java      | 202 ++++++++++++++++++---
 .../nifi/web/security/jwt/TestKeyService.java      |  73 ++++++++
 17 files changed, 613 insertions(+), 78 deletions(-)

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 9626445..3cfaf2f 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
@@ -48,9 +48,9 @@ public interface KeyDAO {
     Key createKey(String identity);
 
     /**
-     * Deletes all keys for the specified user identity.
+     * Deletes a key using the key ID.
      *
-     * @param identity The user identity
+     * @param keyId The key ID
      */
-    void deleteKeys(String identity);
+    Integer deleteKey(Integer keyId);
 }
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 44d9716..28d090d 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
@@ -47,7 +47,7 @@ public class StandardKeyDAO implements KeyDAO {
             + ")";
 
     private static final String DELETE_KEYS = "DELETE FROM KEY "
-            + "WHERE IDENTITY = ?";
+            + "WHERE ID = ?";
 
     private final Connection connection;
 
@@ -156,13 +156,13 @@ public class StandardKeyDAO implements KeyDAO {
     }
 
     @Override
-    public void deleteKeys(String identity) {
+    public Integer deleteKey(Integer keyId) {
         PreparedStatement statement = null;
         try {
             // add each authority for the specified user
             statement = connection.prepareStatement(DELETE_KEYS);
-            statement.setString(1, identity);
-            statement.executeUpdate();
+            statement.setInt(1, keyId);
+            return statement.executeUpdate();
         } catch (SQLException sqle) {
             throw new DataAccessException(sqle);
         } catch (DataAccessException dae) {
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 4543475..5ac10cb 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
@@ -43,7 +43,7 @@ public interface KeyService {
     /**
      * Deletes keys for the specified identity.
      *
-     * @param identity The user identity
+     * @param keyId The user's key ID
      */
-    void deleteKey(String identity);
+    void deleteKey(Integer keyId);
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeysAction.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeyAction.java
similarity index 72%
rename from 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeysAction.java
rename to 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeyAction.java
index 6b8a2d5..c72d2c3 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeysAction.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeyAction.java
@@ -23,23 +23,22 @@ import org.apache.nifi.admin.dao.KeyDAO;
 /**
  *
  */
-public class DeleteKeysAction implements AdministrationAction<Void> {
+public class DeleteKeyAction implements AdministrationAction<Integer> {
 
-    private final String identity;
+    private final Integer keyId;
 
     /**
-     * Creates a new transactions for deleting keys for specified user.
+     * Creates a new transactions for deleting keys for a specified user based 
on their keyId.
      *
-     * @param identity user identity
+     * @param keyId user identity
      */
-    public DeleteKeysAction(String identity) {
-        this.identity = identity;
+    public DeleteKeyAction(Integer keyId) {
+        this.keyId = keyId;
     }
 
     @Override
-    public Void execute(DAOFactory daoFactory) throws DataAccessException {
+    public Integer execute(DAOFactory daoFactory) throws DataAccessException {
         final KeyDAO keyDao = daoFactory.getKeyDAO();
-        keyDao.deleteKeys(identity);
-        return null;
+        return keyDao.deleteKey(keyId);
     }
 }
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 7a7f62d..8f4198a 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.DeleteKeysAction;
+import org.apache.nifi.admin.service.action.DeleteKeyAction;
 import org.apache.nifi.admin.service.action.GetKeyByIdAction;
 import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
 import org.apache.nifi.admin.service.transaction.Transaction;
@@ -35,7 +35,7 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
- *
+ * This key service manages user JWT signing keys in the H2 user database.
  */
 public class StandardKeyService implements KeyService {
 
@@ -51,7 +51,7 @@ public class StandardKeyService implements KeyService {
     @Override
     public Key getKey(int id) {
         Transaction transaction = null;
-        Key key = null;
+        Key key;
 
         readLock.lock();
         try {
@@ -81,7 +81,7 @@ public class StandardKeyService implements KeyService {
     @Override
     public Key getOrCreateKey(String identity) {
         Transaction transaction = null;
-        Key key = null;
+        Key key;
 
         writeLock.lock();
         try {
@@ -109,7 +109,7 @@ public class StandardKeyService implements KeyService {
     }
 
     @Override
-    public void deleteKey(String identity) {
+    public void deleteKey(Integer keyId) {
         Transaction transaction = null;
 
         writeLock.lock();
@@ -118,11 +118,16 @@ public class StandardKeyService implements KeyService {
             transaction = transactionBuilder.start();
 
             // delete the keys
-            DeleteKeysAction deleteKeys = new DeleteKeysAction(identity);
-            transaction.execute(deleteKeys);
-
-            // commit the transaction
-            transaction.commit();
+            DeleteKeyAction deleteKey = new DeleteKeyAction(keyId);
+            Integer rowsDeleted = transaction.execute(deleteKey);
+
+            // commit the transaction if we found one and only one matching 
keyId/user identity
+            if (rowsDeleted == 1) {
+                transaction.commit();
+            } else {
+                rollback(transaction);
+                throw new AdministrationException("Unable to find user key for 
key ID " + keyId + " to remove token.");
+            }
         } catch (TransactionException | DataAccessException te) {
             rollback(transaction);
             throw new AdministrationException(te);
@@ -157,5 +162,4 @@ public class StandardKeyService implements KeyService {
     public void setProperties(NiFiProperties properties) {
         this.properties = properties;
     }
-
 }
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
index 9ce7a9a..ce1c6d5 100644
--- 
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
@@ -66,4 +66,13 @@ public class Key implements Serializable {
         this.key = key;
     }
 
+    public Key(int id, String identity, String key) {
+        this.id = id;
+        this.identity = identity;
+        this.key = key;
+    }
+
+    public Key() {
+    }
+
 }
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 0e3e70f..5ee98cd 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
@@ -60,6 +60,7 @@ import org.apache.nifi.authorization.AccessDeniedException;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
 import org.apache.nifi.util.FormatUtils;
 import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
 import org.apache.nifi.web.api.dto.AccessStatusDTO;
@@ -659,11 +660,12 @@ public class AccessResource extends ApplicationResource {
 
                 final String expirationFromProperties = 
properties.getKerberosAuthenticationExpiration();
                 long expiration = 
FormatUtils.getTimeDuration(expirationFromProperties, TimeUnit.MILLISECONDS);
-                final String identity = authentication.getName();
-                expiration = validateTokenExpiration(expiration, identity);
+                final String rawIdentity = authentication.getName();
+                String mappedIdentity = 
IdentityMappingUtil.mapIdentity(rawIdentity, 
IdentityMappingUtil.getIdentityMappings(properties));
+                expiration = validateTokenExpiration(expiration, 
mappedIdentity);
 
                 // create the authentication token
-                final LoginAuthenticationToken loginAuthenticationToken = new 
LoginAuthenticationToken(identity, expiration, "KerberosService");
+                final LoginAuthenticationToken loginAuthenticationToken = new 
LoginAuthenticationToken(mappedIdentity, expiration, "KerberosService");
 
                 // generate JWT for response
                 final String token = 
jwtService.generateSignedToken(loginAuthenticationToken);
@@ -729,10 +731,12 @@ public class AccessResource extends ApplicationResource {
         try {
             // attempt to authenticate
             final AuthenticationResponse authenticationResponse = 
loginIdentityProvider.authenticate(new LoginCredentials(username, password));
-            long expiration = 
validateTokenExpiration(authenticationResponse.getExpiration(), 
authenticationResponse.getIdentity());
+            final String rawIdentity = authenticationResponse.getIdentity();
+            String mappedIdentity = 
IdentityMappingUtil.mapIdentity(rawIdentity, 
IdentityMappingUtil.getIdentityMappings(properties));
+            long expiration = 
validateTokenExpiration(authenticationResponse.getExpiration(), mappedIdentity);
 
             // create the authentication token
-            loginAuthenticationToken = new 
LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, 
authenticationResponse.getIssuer());
+            loginAuthenticationToken = new 
LoginAuthenticationToken(mappedIdentity, expiration, 
authenticationResponse.getIssuer());
         } catch (final InvalidLoginCredentialsException ilce) {
             throw new IllegalArgumentException("The supplied username and 
password are not valid.", ilce);
         } catch (final IdentityAccessException iae) {
@@ -769,10 +773,11 @@ public class AccessResource extends ApplicationResource {
 
         String userIdentity = NiFiUserUtils.getNiFiUserIdentity();
 
-        if(userIdentity != null && !userIdentity.isEmpty()) {
+        if (userIdentity != null && !userIdentity.isEmpty()) {
             try {
                 logger.info("Logging out user " + userIdentity);
-                jwtService.logOut(userIdentity);
+                
jwtService.logOutUsingAuthHeader(httpServletRequest.getHeader(JwtAuthenticationFilter.AUTHORIZATION));
+                logger.info("Successfully logged out user" + userIdentity);
                 return generateOkResponse().build();
             } catch (final JwtException e) {
                 logger.error("Logout of user " + userIdentity + " failed due 
to: " + e.getMessage());
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
index 9f1ae29..a97a744 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
@@ -44,13 +44,13 @@ public class ITAccessTokenEndpoint {
 
     private static OneWaySslAccessControlHelper helper;
 
-    private final String user = "unregistered-user@nifi";
+    private final String user = "nifiad...@nifi.apache.org";
     private final String password = "password";
     private static final String CLIENT_ID = "token-endpoint-id";
 
     @BeforeClass
     public static void setup() throws Exception {
-        helper = new OneWaySslAccessControlHelper();
+        helper = new 
OneWaySslAccessControlHelper("src/test/resources/access-control/nifi-mapped-identities.properties");
     }
 
     // -----------
@@ -92,7 +92,7 @@ public class ITAccessTokenEndpoint {
     public void testCreateProcessorUsingToken() throws Exception {
         String url = helper.getBaseUrl() + "/access/token";
 
-        Response response = helper.getUser().testCreateToken(url, "user@nifi", 
"whatever");
+        Response response = helper.getUser().testCreateToken(url, user, 
password);
 
         // ensure the request is successful
         Assert.assertEquals(201, response.getStatus());
@@ -154,7 +154,7 @@ public class ITAccessTokenEndpoint {
 
         Response response = helper.getUser().testCreateToken(url, "user@nifi", 
"not a real password");
 
-        // ensure the request is successful
+        // ensure the request is not successful
         Assert.assertEquals(400, response.getStatus());
     }
 
@@ -262,7 +262,6 @@ public class ITAccessTokenEndpoint {
         // verify unregistered
         Assert.assertEquals("ACTIVE", accessStatus.getStatus());
 
-
         // log out
         response = helper.getUser().testDeleteWithHeaders(logoutUrl, headers);
         Assert.assertEquals(200, response.getStatus());
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java
index 3d56591..956eeec 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java
@@ -44,7 +44,7 @@ public class NiFiTestAuthorizer implements Authorizer {
     public static final String PRIVILEGED_USER_DN = "privileged@nifi";
     public static final String EXECUTED_CODE_USER_DN = "executecode@nifi";
 
-    public static final String TOKEN_USER = "user@nifi";
+    public static final String MAPPED_TOKEN_USER = "nifiadmin";
 
     /**
      * Creates a new FileAuthorizationProvider.
@@ -83,7 +83,7 @@ public class NiFiTestAuthorizer implements Authorizer {
         }
 
         // allow the token user
-        if (TOKEN_USER.equals(request.getIdentity())) {
+        if (MAPPED_TOKEN_USER.equals(request.getIdentity())) {
             return AuthorizationResult.approved();
         }
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
index 508a0d1..a244c19 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
@@ -43,6 +43,7 @@ public class NiFiTestLoginIdentityProvider implements 
LoginIdentityProvider {
         users = new HashMap<>();
         users.put("user@nifi", "whatever");
         users.put("unregistered-user@nifi", "password");
+        users.put("nifiad...@nifi.apache.org", "password");
     }
 
     private void checkUser(final String user, final String password) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
index 72d59ba..c99895a 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
@@ -17,6 +17,8 @@
 package org.apache.nifi.integration.util;
 
 import org.apache.nifi.web.security.ProxiedEntitiesUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.Entity;
@@ -25,6 +27,7 @@ import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.Response;
+import java.util.Arrays;
 import java.util.Map;
 
 /**
@@ -34,8 +37,11 @@ public class NiFiTestUser {
 
     private final Client client;
     private final String proxyDn;
+    private final Logger logger;
 
     public NiFiTestUser(Client client, String proxyDn) {
+        logger = LoggerFactory.getLogger(NiFiTestUser.class);
+
         this.client = client;
         if (proxyDn != null) {
             this.proxyDn = ProxiedEntitiesUtils.formatProxyDn(proxyDn);
@@ -156,6 +162,8 @@ public class NiFiTestUser {
             }
         }
 
+        logger.info("POST Request to URL: " + url + " with headers: " + 
Arrays.toString(headers.entrySet().toArray()));
+
         // perform the request
         return resourceBuilder.post(Entity.json(entity));
     }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-mapped-identities.properties
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-mapped-identities.properties
new file mode 100644
index 0000000..858ea8e
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-mapped-identities.properties
@@ -0,0 +1,144 @@
+# 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.
+
+
+
+# Core Properties #
+nifi.flow.configuration.file=target/test-classes/access-control/flow.xml.gz
+nifi.flow.configuration.archive.dir=target/archive
+nifi.flowcontroller.autoResumeState=true
+nifi.flowcontroller.graceful.shutdown.period=10 sec
+nifi.flowservice.writedelay.interval=2 sec
+
+# Mapped Identities #
+nifi.security.identity.mapping.pattern.kerb=^(.*?)@(.*?)$
+nifi.security.identity.mapping.value.kerb=$1
+
+nifi.authorizer.configuration.file=target/test-classes/access-control/authorizers.xml
+nifi.login.identity.provider.configuration.file=target/test-classes/access-control/login-identity-providers.xml
+nifi.templates.directory=target/test-classes/access-control/templates
+nifi.ui.banner.text=TEST BANNER
+nifi.ui.autorefresh.interval=30 sec
+nifi.nar.library.directory=target/test-classes/access-control/lib
+nifi.nar.working.directory=target/test-classes/access-control/nar
+
+nifi.state.management.configuration.file=target/test-classes/access-control/state-management.xml
+nifi.state.management.embedded.zookeeper.start=false
+nifi.state.management.embedded.zookeeper.properties=
+nifi.state.management.embedded.zookeeper.max.instances=3
+nifi.state.management.provider.local=local-provider
+nifi.state.management.provider.cluster=
+
+# H2 Settings
+nifi.database.directory=target/test-classes/database_repository
+nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# FlowFile Repository
+nifi.provenance.repository.implementation=org.apache.nifi.provenance.VolatileProvenanceRepository
+nifi.flowfile.repository.directory=target/test-classes/flowfile_repository
+nifi.flowfile.repository.partitions=256
+nifi.flowfile.repository.checkpoint.interval=2 mins
+nifi.queue.swap.threshold=20000
+nifi.swap.storage.directory=target/test-classes/flowfile_repository/swap
+nifi.swap.in.period=5 sec
+nifi.swap.in.threads=1
+nifi.swap.out.period=5 sec
+nifi.swap.out.threads=4
+
+# Content Repository
+nifi.content.claim.max.appendable.size=10 MB
+nifi.content.claim.max.flow.files=100
+nifi.content.repository.directory.default=target/test-classes/content_repository
+nifi.content.repository.archive.enabled=false
+
+# Provenance Repository Properties
+nifi.provenance.repository.directory.default=./target/provenance_repository
+nifi.provenance.repository.query.threads=2
+nifi.provenance.repository.max.storage.time=24 hours
+nifi.provenance.repository.max.storage.size=1 GB
+nifi.provenance.repository.rollover.time=30 secs
+nifi.provenance.repository.rollover.size=100 MB
+
+# Component Status Repository
+nifi.components.status.repository.implementation=org.apache.nifi.controller.status.history.VolatileComponentStatusRepository
+nifi.components.status.repository.buffer.size=288
+nifi.components.status.snapshot.frequency=10 secs
+
+# Site to Site properties
+#For testing purposes. Default value should actually be empty!
+nifi.remote.input.host=
+nifi.remote.input.socket.port=
+nifi.remote.input.secure=false
+
+# web properties #
+nifi.web.war.directory=target/test-classes/lib
+nifi.web.http.host=
+nifi.web.http.port=
+nifi.web.https.host=
+nifi.web.https.port=8443
+nifi.web.jetty.working.directory=target/test-classes/access-control/jetty
+
+# security properties #
+nifi.sensitive.props.key=REPLACE_ME
+nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
+nifi.sensitive.props.provider=BC
+
+nifi.security.keystore=target/test-classes/access-control/keystore.jks
+nifi.security.keystoreType=JKS
+nifi.security.keystorePasswd=passwordpassword
+nifi.security.keyPasswd=
+nifi.security.truststore=target/test-classes/access-control/truststore.jks
+nifi.security.truststoreType=JKS
+nifi.security.truststorePasswd=passwordpassword
+nifi.security.user.login.identity.provider=test-provider
+nifi.security.user.authorizer=test-provider
+
+# cluster common properties (cluster manager and nodes must have same values) #
+nifi.cluster.protocol.heartbeat.interval=5 sec
+nifi.cluster.protocol.is.secure=false
+nifi.cluster.protocol.socket.timeout=30 sec
+nifi.cluster.protocol.connection.handshake.timeout=45 sec
+# if multicast is used, then nifi.cluster.protocol.multicast.xxx properties 
must be configured #
+nifi.cluster.protocol.use.multicast=false
+nifi.cluster.protocol.multicast.address=
+nifi.cluster.protocol.multicast.port=
+nifi.cluster.protocol.multicast.service.broadcast.delay=500 ms
+nifi.cluster.protocol.multicast.service.locator.attempts=3
+nifi.cluster.protocol.multicast.service.locator.attempts.delay=1 sec
+
+# cluster node properties (only configure for cluster nodes) #
+nifi.cluster.is.node=false
+nifi.cluster.node.address=
+nifi.cluster.node.protocol.port=
+nifi.cluster.node.protocol.threads=2
+# if multicast is not used, nifi.cluster.node.unicast.xxx must have same 
values as nifi.cluster.manager.xxx #
+nifi.cluster.node.unicast.manager.address=
+nifi.cluster.node.unicast.manager.protocol.port=
+nifi.cluster.node.unicast.manager.authority.provider.port=
+
+# cluster manager properties (only configure for cluster manager) #
+nifi.cluster.is.manager=false
+nifi.cluster.manager.address=
+nifi.cluster.manager.protocol.port=
+nifi.cluster.manager.authority.provider.port=
+nifi.cluster.manager.authority.provider.threads=10
+nifi.cluster.manager.node.firewall.file=
+nifi.cluster.manager.node.event.history.size=10
+nifi.cluster.manager.node.api.connection.timeout=30 sec
+nifi.cluster.manager.node.api.read.timeout=30 sec
+nifi.cluster.manager.node.api.request.threads=10
+nifi.cluster.manager.flow.retrieval.delay=5 sec
+nifi.cluster.manager.protocol.threads=10
+nifi.cluster.manager.safemode.duration=0 sec
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 0d21a8a..9a25ff3 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
@@ -63,7 +63,7 @@ public class JwtAuthenticationFilter extends 
NiFiAuthenticationFilter {
         return matcher.matches();
     }
 
-    private String getTokenFromHeader(String authenticationHeader) {
+    public static String getTokenFromHeader(String authenticationHeader) {
         Matcher matcher = tokenPattern.matcher(authenticationHeader);
         if(matcher.matches()) {
             return matcher.group(1);
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 ecbfc67..9569631 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
@@ -169,16 +169,33 @@ public class JwtService {
         }
     }
 
-    public void logOut(String userIdentity) {
-        if (userIdentity == null || userIdentity.isEmpty()) {
-            throw new JwtException("Log out failed: The user identity was not 
present in the request token to log out user.");
+    /**
+     * Log out the authenticated user using the 'kid' (Key ID) claim from the 
base64 encoded JWT
+     *
+     * @param token a signed, base64 encoded, JSON Web Token in form 
HEADER.PAYLOAD.SIGNATURE
+     * @throws JwtException if there is a problem with the token input
+     * @throws Exception if there is an issue logging the user out
+     */
+    public void logOut(String token) {
+        Jws<Claims> claims = parseTokenFromBase64EncodedString(token);
+
+        // Get the key ID from the claims
+        final Integer keyId = claims.getBody().get(KEY_ID_CLAIM, 
Integer.class);
+
+        if (keyId == null) {
+            throw new JwtException("The key claim (kid) was not present in the 
request token to log out user.");
         }
 
         try {
-            keyService.deleteKey(userIdentity);
+            keyService.deleteKey(keyId);
         } catch (Exception e) {
-            logger.error("Unable to log out user: " + userIdentity + ". Failed 
to remove their token from database.");
+            logger.error("The key with key ID: " + keyId + " failed to be 
removed from the user database.");
             throw e;
         }
     }
+
+    public void logOutUsingAuthHeader(String authorizationHeader) {
+        String base64EncodedToken = 
JwtAuthenticationFilter.getTokenFromHeader(authorizationHeader);
+        logOut(base64EncodedToken);
+    }
 }
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/JwtAuthenticationProviderTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java
new file mode 100644
index 0000000..8a1dbd4
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.jwt;
+
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.properties.StandardNiFiProperties;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class JwtAuthenticationProviderTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    private final static int EXPIRATION_MILLIS = 60000;
+    private final static String CLIENT_ADDRESS = "127.0.0.1";
+    private final static String ADMIN_IDENTITY = "nifiadmin";
+    private final static String REALMED_ADMIN_KERBEROS_IDENTITY = 
"nifiad...@nifi.apache.org";
+
+    private final static String UNKNOWN_TOKEN = "eyJhbGciOiJIUzI1NiJ9" +
+            ".eyJzdWIiOiJ1bmtub3duX3Rva2VuIiwiaXNzIjoiS2VyYmVyb3NQcm9" +
+            "2aWRlciIsImF1ZCI6IktlcmJlcm9zUHJvdmlkZXIiLCJwcmVmZXJyZWR" +
+            "fdXNlcm5hbWUiOiJ1bmtub3duX3Rva2VuIiwia2lkIjoxLCJleHAiOjE" +
+            "2OTI0NTQ2NjcsImlhdCI6MTU5MjQxMTQ2N30.PpOGx3Ul5ydokOOuzKd" +
+            "aRKv1kxy6Q4jGy7rBPU8PqxY";
+
+    private NiFiProperties properties;
+
+
+    private JwtService jwtService;
+    private JwtAuthenticationProvider jwtAuthenticationProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        TestKeyService keyService = new TestKeyService();
+        jwtService = new JwtService(keyService);
+
+        // Set up Kerberos identity mappings
+        Properties props = new Properties();
+        props.put(properties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX, 
"^(.*?)@(.*?)$");
+        props.put(properties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX, "$1");
+        properties = new StandardNiFiProperties(props);
+
+        jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtService, 
properties, mock(Authorizer.class));
+    }
+
+    @Test
+    public void testAdminIdentityAndTokenIsValid() throws Exception {
+        // Arrange
+        LoginAuthenticationToken loginAuthenticationToken =
+                new LoginAuthenticationToken(ADMIN_IDENTITY,
+                                             EXPIRATION_MILLIS,
+                                      "MockIdentityProvider");
+        String token = 
jwtService.generateSignedToken(loginAuthenticationToken);
+        final JwtAuthenticationRequestToken request = new 
JwtAuthenticationRequestToken(token, CLIENT_ADDRESS);
+
+        // Act
+        final NiFiAuthenticationToken result = (NiFiAuthenticationToken) 
jwtAuthenticationProvider.authenticate(request);
+        final NiFiUserDetails details = (NiFiUserDetails) 
result.getPrincipal();
+
+        // Assert
+        assertEquals(ADMIN_IDENTITY, details.getUsername());
+    }
+
+    @Test
+    public void testKerberosRealmedIdentityAndTokenIsValid() throws Exception {
+        // Arrange
+        LoginAuthenticationToken loginAuthenticationToken =
+                new LoginAuthenticationToken(REALMED_ADMIN_KERBEROS_IDENTITY,
+                        EXPIRATION_MILLIS,
+                        "MockIdentityProvider");
+        String token = 
jwtService.generateSignedToken(loginAuthenticationToken);
+        final JwtAuthenticationRequestToken request = new 
JwtAuthenticationRequestToken(token, CLIENT_ADDRESS);
+
+        // Act
+        final NiFiAuthenticationToken result = (NiFiAuthenticationToken) 
jwtAuthenticationProvider.authenticate(request);
+        final NiFiUserDetails details = (NiFiUserDetails) 
result.getPrincipal();
+
+        // Assert
+        // Check we now have the mapped identity
+        assertEquals(ADMIN_IDENTITY, details.getUsername());
+    }
+
+    @Test
+    public void testFailToAuthenticateWithUnknownToken() throws Exception {
+        // Arrange
+        expectedException.expect(InvalidAuthenticationException.class);
+        expectedException.expectMessage("Unable to validate the access 
token.");
+
+        // Generate a token with a known token
+        LoginAuthenticationToken loginAuthenticationToken =
+                new LoginAuthenticationToken(ADMIN_IDENTITY,
+                        EXPIRATION_MILLIS,
+                        "MockIdentityProvider");
+        jwtService.generateSignedToken(loginAuthenticationToken);
+
+        // Act
+        // Try to  authenticate with an unknown token
+        final JwtAuthenticationRequestToken request = new 
JwtAuthenticationRequestToken(UNKNOWN_TOKEN, CLIENT_ADDRESS);
+        final NiFiAuthenticationToken result = (NiFiAuthenticationToken) 
jwtAuthenticationProvider.authenticate(request);
+
+        // Assert
+        // Expect exception
+    }
+
+}
\ No newline at end of file
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 0727ccf..70f9a6d 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
@@ -16,6 +16,8 @@
  */
 package org.apache.nifi.web.security.jwt;
 
+import static 
org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX;
+import static 
org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -28,8 +30,11 @@ import io.jsonwebtoken.JwtException;
 import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 import org.apache.commons.codec.binary.Base64;
@@ -37,7 +42,10 @@ import org.apache.nifi.admin.service.AdministrationException;
 import org.apache.nifi.admin.service.KeyService;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
 import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
 import org.apache.nifi.key.Key;
+import org.apache.nifi.properties.StandardNiFiProperties;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.codehaus.jettison.json.JSONObject;
 import org.junit.After;
@@ -58,6 +66,9 @@ public class JwtServiceTest {
 
     private static final Logger logger = 
LoggerFactory.getLogger(JwtServiceTest.class);
 
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
     /**
      * These constant strings were generated using the tool at http://jwt.io
      */
@@ -132,17 +143,30 @@ public class JwtServiceTest {
             + "6MSwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.6kDjDanA"
             + "g0NQDb3C8FmgbBAYDoIfMAEkF4WMVALsbJA";
 
+    private static final String KERBEROS_PROVIDER_TOKEN = 
"eyJhbGciOiJIUzI1NiJ9" +
+            
".eyJzdWIiOiJuaWZpYWRtaW5AbmlmaS5hcGFjaGUub3JnIiwiaXNzIjoiS2VyYmVyb" +
+            
"3NQcm92aWRlciIsImF1ZCI6IktlcmJlcm9zUHJvdmlkZXIiLCJwcmVmZXJyZWRfdXN" +
+            
"lcm5hbWUiOiJuaWZpYWRtaW5AbmlmaS5hcGFjaGUub3JnIiwia2lkIjo2LCJleHAiO" +
+            
"jE2OTI0NTQ2NjcsImlhdCI6MTU5MjQxMTQ2N30.Mmnx6ssdjQ5_5VVRiyPWU60Oegc" +
+            "NdhWezaKKNK48Mew";
+
     private static final String DEFAULT_HEADER = "{\"alg\":\"HS256\"}";
     private static final String DEFAULT_IDENTITY = "alopresto";
+    private static final String REALMED_KERBEROS_IDENTITY = 
"nifiad...@nifi.apache.org";
+    private static final String KERBEROS_IDENTITY = "nifiadmin";
 
     private static final String TOKEN_DELIMITER = ".";
 
     private static final String HMAC_SECRET = "test_hmac_shared_secret";
 
+    private static List<IdentityMapping> identityMappings;
+
     private KeyService mockKeyService;
+    private KeyService testKeyService;
 
     // Class under test
     private JwtService jwtService;
+    private JwtService jwtServiceUsingTestKeyService;
 
     public static String generateHS256Token(String rawHeader, String 
rawPayload, boolean isValid, boolean isSigned) {
         return generateHS256Token(rawHeader, rawPayload, HMAC_SECRET, isValid, 
isSigned);
@@ -189,7 +213,7 @@ public class JwtServiceTest {
             Key answerKey = key;
             @Override
             public Key answer(InvocationOnMock invocation) throws Throwable {
-                
if(invocation.getMethod().equals(KeyService.class.getMethod("deleteKey", 
String.class))) {
+                
if(invocation.getMethod().equals(KeyService.class.getMethod("deleteKey", 
Integer.class))) {
                     answerKey = null;
                 }
                 return answerKey;
@@ -210,8 +234,15 @@ public class JwtServiceTest {
         mockKeyService = mock(KeyService.class);
         when(mockKeyService.getKey(anyInt())).thenAnswer(keyAnswer);
         when(mockKeyService.getOrCreateKey(anyString())).thenReturn(key);
-        doAnswer(keyAnswer).when(mockKeyService).deleteKey(anyString());
+        doAnswer(keyAnswer).when(mockKeyService).deleteKey(anyInt());
+
         jwtService = new JwtService(mockKeyService);
+        jwtServiceUsingTestKeyService = new JwtService(new TestKeyService());
+
+        Properties props = new Properties();
+        props.setProperty(SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX+"kerb",  
"^(.*?)@(.*?)$");
+        props.setProperty(SECURITY_IDENTITY_MAPPING_VALUE_PREFIX+"kerb", "$1");
+        identityMappings = IdentityMappingUtil.getIdentityMappings(new 
StandardNiFiProperties(props));
     }
 
     @After
@@ -226,12 +257,25 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         assertEquals("Identity", DEFAULT_IDENTITY, identity);
     }
 
+    @Test
+    public void testShouldGetAuthenticationForValidKerberosToken() throws 
Exception {
+        // Arrange
+        String token = KERBEROS_PROVIDER_TOKEN;
+
+        // Act
+        String identity = jwtService.getAuthenticationFromToken(token);
+        logger.info("Extracted identity: " + identity);
+
+        // Assert
+        assertEquals("Identity", REALMED_KERBEROS_IDENTITY, identity);
+    }
+
     @Test(expected = JwtException.class)
     public void testShouldNotGetAuthenticationForInvalidToken() throws 
Exception {
         // Arrange
@@ -239,7 +283,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -252,7 +296,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -265,7 +309,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -278,7 +322,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -291,7 +335,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -304,7 +348,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -318,7 +362,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -331,7 +375,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -344,7 +388,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -357,7 +401,7 @@ public class JwtServiceTest {
 
         // Act
         String identity = jwtService.getAuthenticationFromToken(token);
-        logger.debug("Extracted identity: " + identity);
+        logger.info("Extracted identity: " + identity);
 
         // Assert
         // Should fail
@@ -372,7 +416,7 @@ public class JwtServiceTest {
         LoginAuthenticationToken loginAuthenticationToken = new 
LoginAuthenticationToken("alopresto",
                 EXPIRATION_MILLIS,
                 "MockIdentityProvider");
-        logger.debug("Generating token for " + loginAuthenticationToken);
+        logger.info("Generating token for " + loginAuthenticationToken);
 
         final String EXPECTED_HEADER = DEFAULT_HEADER;
 
@@ -381,7 +425,7 @@ public class JwtServiceTest {
 
         // Act
         String token = 
jwtService.generateSignedToken(loginAuthenticationToken);
-        logger.debug("Generated JWT: " + token);
+        logger.info("Generated JWT: " + token);
 
         // Run after the SUT generates the token to ensure the same issued at 
time
         // Split the token, decode the middle section, and form a new String
@@ -390,7 +434,7 @@ public class JwtServiceTest {
                 DECODED_PAYLOAD.length() - 1));
         logger.trace("Actual token was issued at " + ISSUED_AT_SEC);
 
-        // Always use LinkedHashMap to enforce order of the keys because the 
signature depends on order
+        // Always use LinkedHashMap to enforce order of the signingKeys 
because the signature depends on order
         Map<String, Object> claims = new LinkedHashMap<>();
         claims.put("sub", "alopresto");
         claims.put("iss", "MockIdentityProvider");
@@ -403,7 +447,7 @@ public class JwtServiceTest {
 
         final String EXPECTED_PAYLOAD = new JSONObject(claims).toString();
         final String EXPECTED_TOKEN_STRING = 
generateHS256Token(EXPECTED_HEADER, EXPECTED_PAYLOAD, true, true);
-        logger.debug("Expected JWT: " + EXPECTED_TOKEN_STRING);
+        logger.info("Expected JWT: " + EXPECTED_TOKEN_STRING);
 
         // Assert
         assertEquals("JWT token", EXPECTED_TOKEN_STRING, token);
@@ -413,7 +457,7 @@ public class JwtServiceTest {
     public void testShouldNotGenerateTokenWithNullAuthenticationToken() throws 
Exception {
         // Arrange
         LoginAuthenticationToken nullLoginAuthenticationToken = null;
-        logger.debug("Generating token for " + nullLoginAuthenticationToken);
+        logger.info("Generating token for " + nullLoginAuthenticationToken);
 
         // Act
         jwtService.generateSignedToken(nullLoginAuthenticationToken);
@@ -428,7 +472,7 @@ public class JwtServiceTest {
         final int EXPIRATION_MILLIS = 60000;
         LoginAuthenticationToken emptyIdentityLoginAuthenticationToken = new 
LoginAuthenticationToken("",
                 EXPIRATION_MILLIS, "MockIdentityProvider");
-        logger.debug("Generating token for " + 
emptyIdentityLoginAuthenticationToken);
+        logger.info("Generating token for " + 
emptyIdentityLoginAuthenticationToken);
 
         // Act
         jwtService.generateSignedToken(emptyIdentityLoginAuthenticationToken);
@@ -443,7 +487,7 @@ public class JwtServiceTest {
         final int EXPIRATION_MILLIS = 60000;
         LoginAuthenticationToken nullIdentityLoginAuthenticationToken = new 
LoginAuthenticationToken(null,
                 EXPIRATION_MILLIS, "MockIdentityProvider");
-        logger.debug("Generating token for " + 
nullIdentityLoginAuthenticationToken);
+        logger.info("Generating token for " + 
nullIdentityLoginAuthenticationToken);
 
         // Act
         jwtService.generateSignedToken(nullIdentityLoginAuthenticationToken);
@@ -459,7 +503,7 @@ public class JwtServiceTest {
         LoginAuthenticationToken loginAuthenticationToken = new 
LoginAuthenticationToken(DEFAULT_IDENTITY,
                 EXPIRATION_MILLIS,
                 "MockIdentityProvider");
-        logger.debug("Generating token for " + loginAuthenticationToken);
+        logger.info("Generating token for " + loginAuthenticationToken);
 
         // Set up the bad key service
         KeyService missingKeyService = mock(KeyService.class);
@@ -474,9 +518,6 @@ public class JwtServiceTest {
         // Should throw exception
     }
 
-    @Rule
-    public ExpectedException expectedException = ExpectedException.none();
-
     @Test
     public void testShouldLogOutUser() throws Exception {
         // Arrange
@@ -488,16 +529,51 @@ public class JwtServiceTest {
         LoginAuthenticationToken loginAuthenticationToken = new 
LoginAuthenticationToken(DEFAULT_IDENTITY,
                 EXPIRATION_MILLIS,
                 "MockIdentityProvider");
-        logger.debug("Generating token for " + loginAuthenticationToken);
+        logger.info("Generating token for " + loginAuthenticationToken);
 
         // Act
         String token = 
jwtService.generateSignedToken(loginAuthenticationToken);
-        logger.debug("Generated JWT: " + token);
+        logger.info("Generated JWT: " + token);
+        logger.info("Validating token...");
         String authID = jwtService.getAuthenticationFromToken(token);
         assertEquals(DEFAULT_IDENTITY, authID);
-        logger.debug("Logging out user: " + DEFAULT_IDENTITY);
+        logger.info("Token was valid");
+        logger.info("Logging out user: " + authID);
         jwtService.logOut(token);
-        logger.debug("Logged out user: " + DEFAULT_IDENTITY);
+        logger.info("Logged out user: " + authID);
+        logger.info("Checking that token is now invalid...");
+        jwtService.getAuthenticationFromToken(token);
+
+        // Assert
+        // Should throw exception when user is not found
+    }
+
+    @Test
+    public void testShouldLogOutUserUsingAuthHeader() throws Exception {
+        // Arrange
+        expectedException.expect(JwtException.class);
+        expectedException.expectMessage("Unable to validate the access 
token.");
+
+        // Token expires in 60 seconds
+        final int EXPIRATION_MILLIS = 60000;
+        LoginAuthenticationToken loginAuthenticationToken = new 
LoginAuthenticationToken(DEFAULT_IDENTITY,
+                EXPIRATION_MILLIS,
+                "MockIdentityProvider");
+        logger.info("Generating token for " + loginAuthenticationToken);
+
+        // Act
+        String token = 
jwtService.generateSignedToken(loginAuthenticationToken);
+
+        logger.info("Generated JWT: " + token);
+        logger.info("Validating token...");
+        String authID = jwtService.getAuthenticationFromToken(token);
+        assertEquals(DEFAULT_IDENTITY, authID);
+        logger.info("Token was valid");
+        logger.info("Logging out user: " + authID);
+        String header = "Bearer " + token;
+        jwtService.logOutUsingAuthHeader(header);
+        logger.info("Logged out user: " + authID);
+        logger.info("Checking that token is now invalid...");
         jwtService.getAuthenticationFromToken(token);
 
         // Assert
@@ -508,7 +584,7 @@ public class JwtServiceTest {
     public void testLogoutWhenAuthTokenIsEmptyShouldThrowError() throws 
Exception {
         // Arrange
         expectedException.expect(JwtException.class);
-        expectedException.expectMessage("Log out failed: The user identity was 
not present in the request token to log out user.");
+        expectedException.expectMessage("Unable to validate the access 
token.");
 
         // Act
         jwtService.logOut(null);
@@ -517,4 +593,72 @@ public class JwtServiceTest {
         // Should throw exception when authorization header is null
     }
 
+    @Test
+    public void testShouldLogOutKerberosUser() throws Exception {
+        // Arrange
+
+        expectedException.expect(JwtException.class);
+        expectedException.expectMessage("Unable to validate the access 
token.");
+
+        // Token expires in 60 seconds
+        final int EXPIRATION_MILLIS = 60000;
+        LoginAuthenticationToken loginAuthenticationToken = new 
LoginAuthenticationToken(KERBEROS_IDENTITY,
+                EXPIRATION_MILLIS,
+                "MockIdentityProvider");
+        logger.info("Generating token for " + loginAuthenticationToken);
+
+        // Act
+        String token = 
jwtServiceUsingTestKeyService.generateSignedToken(loginAuthenticationToken);
+        logger.info("Generated JWT: " + token);
+        logger.info("Validating token...");
+        String authID = 
jwtServiceUsingTestKeyService.getAuthenticationFromToken(token);
+        logger.info("Token was valid, unmapped user identity was: " + authID);
+        assertEquals(KERBEROS_IDENTITY, authID);
+        logger.info("Using identity mappings " + 
Arrays.toString(identityMappings.toArray()) + " to map identity: " + authID);
+        String mappedIdentity = IdentityMappingUtil.mapIdentity(authID, 
identityMappings);
+        logger.info("Logging out user with mapped identity: " + 
mappedIdentity);
+        jwtServiceUsingTestKeyService.logOut(mappedIdentity);
+        logger.info("Logged out user with mapped identity: " + mappedIdentity);
+        logger.info("Checking that token for " + mappedIdentity + " is now 
invalid...");
+        jwtServiceUsingTestKeyService.getAuthenticationFromToken(token);
+
+        // Assert
+        // Should throw exception when user is not found
+    }
+
+    @Test
+    public void testShouldLogOutRealmedKerberosUser() throws Exception {
+        // Arrange
+
+        expectedException.expect(JwtException.class);
+        expectedException.expectMessage("Unable to validate the access 
token.");
+
+        // Token expires in 60 seconds
+        final int EXPIRATION_MILLIS = 60000;
+        // map the kerberos identity before we create our token, just as is 
done in AccessResource
+        final String mappedIdentity = 
IdentityMappingUtil.mapIdentity(REALMED_KERBEROS_IDENTITY, identityMappings);
+
+        LoginAuthenticationToken loginAuthenticationToken = new 
LoginAuthenticationToken(mappedIdentity,
+                EXPIRATION_MILLIS,
+                "MockIdentityProvider");
+        logger.info("Generating token for " + loginAuthenticationToken);
+
+        // Act
+        String token = 
jwtServiceUsingTestKeyService.generateSignedToken(loginAuthenticationToken);
+        logger.info("Generated JWT: " + token);
+        logger.info("Validating token...");
+        String authID = 
jwtServiceUsingTestKeyService.getAuthenticationFromToken(token);
+        logger.info("Token was valid, unmapped user identity was: " + authID);
+        assertEquals(KERBEROS_IDENTITY, authID);
+        logger.info("Using identity mappings " + 
Arrays.toString(identityMappings.toArray()) + " to map identity: " + authID);
+        logger.info("Logging out user with mapped identity: " + authID);
+        jwtServiceUsingTestKeyService.logOut(authID);
+        logger.info("Logged out user with mapped identity: " + authID);
+        logger.info("Checking that token for " + authID + " is now 
invalid...");
+        jwtServiceUsingTestKeyService.getAuthenticationFromToken(token);
+
+        // Assert
+        // Should throw exception when user is not found
+    }
+
 }
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/TestKeyService.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/TestKeyService.java
new file mode 100644
index 0000000..4d2f415
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/TestKeyService.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.web.security.jwt;
+
+import org.apache.nifi.admin.service.KeyService;
+import org.apache.nifi.key.Key;
+import org.h2.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+public class TestKeyService implements KeyService {
+
+    ArrayList<Key> signingKeys = new ArrayList<Key>();
+
+    public TestKeyService() {
+
+    }
+
+    @Override
+    public Key getKey(int id) {
+        Key key = null;
+        for(Key k : signingKeys) {
+            if(k.getId() == id) {
+                key = k;
+            }
+        }
+        return key;
+    }
+
+    @Override
+    public Key getOrCreateKey(String identity) {
+        for(Key k : signingKeys) {
+            if(StringUtils.equals(k.getIdentity(), identity)) {
+                return k;
+            }
+        }
+
+        Key key = generateKey(identity);
+        signingKeys.add(key);
+        return key;
+    }
+
+    @Override
+    public void deleteKey(Integer keyId) {
+        Key keyToRemove = null;
+        for(Key k : signingKeys) {
+            if(k.getId() == keyId) {
+                keyToRemove = k;
+            }
+        }
+        signingKeys.remove(keyToRemove);
+    }
+
+    private Key generateKey(String identity) {
+        Integer keyId = signingKeys.size();
+        return new Key(keyId, identity, UUID.randomUUID().toString());
+    }
+}

Reply via email to