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/12/01 17:43:42 UTC

[04/51] [abbrv] nifi git commit: NIFI-655: - Refactoring certificate extraction and validation. - Refactoring how expiration is specified in the login identity providers. - Adding unit tests for the access endpoints. - Code clean up.

NIFI-655:
- Refactoring certificate extraction and validation.
- Refactoring how expiration is specified in the login identity providers.
- Adding unit tests for the access endpoints.
- Code clean up.

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

Branch: refs/heads/master
Commit: a196207725a37aecaccc8c7858cec3a4d1c4ff55
Parents: 7529694
Author: Matt Gilman <ma...@gmail.com>
Authored: Tue Nov 17 17:02:41 2015 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Tue Nov 17 17:02:41 2015 -0500

----------------------------------------------------------------------
 .../authentication/AuthenticationResponse.java  |  14 +-
 .../authentication/LoginIdentityProvider.java   |   7 -
 .../web/NiFiWebApiSecurityConfiguration.java    |  25 +-
 .../org/apache/nifi/web/api/AccessResource.java | 188 ++++++-----
 .../nifi/web/api/ApplicationResource.java       |   8 +-
 .../nifi/web/controller/ControllerFacade.java   |   2 +-
 .../src/main/resources/nifi-web-api-context.xml |   3 +-
 .../accesscontrol/AccessTokenEndpointTest.java  | 292 +++++++++++++++++
 .../util/NiFiTestAuthorizationProvider.java     |   3 +-
 .../util/NiFiTestLoginIdentityProvider.java     |  75 +++++
 .../nifi/integration/util/NiFiTestServer.java   |   9 +-
 .../nifi/integration/util/NiFiTestUser.java     | 229 ++++++++++++--
 ...he.nifi.authentication.LoginIdentityProvider |  15 +
 .../access-control/controller-services.xml      |  18 --
 .../access-control/login-identity-providers.xml |  24 ++
 .../resources/access-control/nifi.properties    |   9 +-
 .../access-control/reporting-tasks.xml          |  17 -
 .../web/security/NiFiAuthenticationFilter.java  |  14 +-
 .../security/NiFiAuthenticationProvider.java    |   8 +-
 .../nifi/web/security/ProxiedEntitiesUtils.java | 117 +++----
 .../authorization/NiFiAuthorizationService.java |  15 +-
 .../security/jwt/JwtAuthenticationFilter.java   |   3 +-
 .../nifi/web/security/jwt/JwtService.java       |   2 +-
 .../security/node/NodeAuthorizedUserFilter.java |  68 ++--
 .../LoginIdentityProviderFactoryBean.java       |   7 -
 .../security/x509/X509AuthenticationFilter.java |  63 +---
 .../x509/X509AuthenticationFilterOld.java       | 317 -------------------
 .../security/x509/X509CertificateExtractor.java |   4 +-
 .../security/x509/X509CertificateValidator.java |  10 +-
 .../web/security/x509/X509IdentityProvider.java |  92 ++++++
 .../x509/ocsp/OcspCertificateValidator.java     |  20 +-
 .../resources/nifi-web-security-context.xml     |   6 +
 .../NiFiAuthorizationServiceTest.java           |   2 +-
 .../src/main/webapp/js/nf/login/nf-login.js     |   2 -
 .../apache/nifi/ldap/AbstractLdapProvider.java  |   9 +-
 35 files changed, 985 insertions(+), 712 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java b/nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java
index 8f57810..a64947b 100644
--- a/nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java
+++ b/nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java
@@ -23,16 +23,19 @@ public class AuthenticationResponse {
 
     private final String identity;
     private final String username;
+    private final long expiration;
 
     /**
      * Creates an authentication response. The username and how long the authentication is valid in milliseconds
      *
      * @param identity The user identity
      * @param username The username
+     * @param expiration The expiration in milliseconds
      */
-    public AuthenticationResponse(final String identity, final String username) {
+    public AuthenticationResponse(final String identity, final String username, final long expiration) {
         this.identity = identity;
         this.username = username;
+        this.expiration = expiration;
     }
 
     public String getIdentity() {
@@ -43,4 +46,13 @@ public class AuthenticationResponse {
         return username;
     }
 
+    /**
+     * Returns the expiration of a given authentication in milliseconds.
+     *
+     * @return The expiration in milliseconds
+     */
+    public long getExpiration() {
+        return expiration;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java
index 5f4db40..54becb3 100644
--- a/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java
+++ b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java
@@ -37,13 +37,6 @@ public interface LoginIdentityProvider {
     AuthenticationResponse authenticate(LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException;
 
     /**
-     * Returns the expiration of a given authentication in milliseconds.
-     *
-     * @return The expiration in milliseconds
-     */
-    long getExpiration();
-
-    /**
      * Called immediately after instance creation for implementers to perform additional setup
      *
      * @param initializationContext in which to initialize

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
index 216e311..e8ed267 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
@@ -28,7 +28,7 @@ import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter;
 import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
 import org.apache.nifi.web.security.x509.X509AuthenticationFilter;
 import org.apache.nifi.web.security.x509.X509CertificateExtractor;
-import org.apache.nifi.web.security.x509.X509CertificateValidator;
+import org.apache.nifi.web.security.x509.X509IdentityProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -42,7 +42,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
 import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
 import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
-import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
 
 /**
  * NiFi Web Api Spring security
@@ -56,9 +55,8 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
     private UserService userService;
     private AuthenticationUserDetailsService userDetailsService;
     private JwtService jwtService;
-    private X509CertificateValidator certificateValidator;
     private X509CertificateExtractor certificateExtractor;
-    private X509PrincipalExtractor principalExtractor;
+    private X509IdentityProvider certificateIdentityProvider;
     private LoginIdentityProvider loginIdentityProvider;
 
     public NiFiWebApiSecurityConfiguration() {
@@ -113,7 +111,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
     }
 
     private NodeAuthorizedUserFilter buildNodeAuthorizedUserFilter() {
-        return new NodeAuthorizedUserFilter(properties);
+        final NodeAuthorizedUserFilter nodeFilter = new NodeAuthorizedUserFilter();
+        nodeFilter.setProperties(properties);
+        nodeFilter.setCertificateExtractor(certificateExtractor);
+        nodeFilter.setCertificateIdentityProvider(certificateIdentityProvider);
+        return nodeFilter;
     }
 
     private JwtAuthenticationFilter buildJwtFilter() throws Exception {
@@ -127,9 +129,8 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
     private X509AuthenticationFilter buildX509Filter() throws Exception {
         final X509AuthenticationFilter x509Filter = new X509AuthenticationFilter();
         x509Filter.setProperties(properties);
-        x509Filter.setPrincipalExtractor(principalExtractor);
         x509Filter.setCertificateExtractor(certificateExtractor);
-        x509Filter.setCertificateValidator(certificateValidator);
+        x509Filter.setCertificateIdentityProvider(certificateIdentityProvider);
         x509Filter.setAuthenticationManager(authenticationManager());
         return x509Filter;
     }
@@ -166,17 +167,13 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
     }
 
     @Autowired
-    public void setCertificateValidator(X509CertificateValidator certificateValidator) {
-        this.certificateValidator = certificateValidator;
-    }
-
-    @Autowired
     public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
         this.certificateExtractor = certificateExtractor;
     }
 
     @Autowired
-    public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
-        this.principalExtractor = principalExtractor;
+    public void setCertificateIdentityProvider(X509IdentityProvider certificateIdentityProvider) {
+        this.certificateIdentityProvider = certificateIdentityProvider;
     }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/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 9cb4141..67eb8b4 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
@@ -30,8 +30,6 @@ import com.wordnik.swagger.annotations.ApiParam;
 import com.wordnik.swagger.annotations.ApiResponse;
 import com.wordnik.swagger.annotations.ApiResponses;
 import java.net.URI;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.List;
@@ -41,6 +39,7 @@ import javax.ws.rs.FormParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.admin.service.AdministrationException;
 import org.apache.nifi.authentication.AuthenticationResponse;
 import org.apache.nifi.authentication.LoginCredentials;
@@ -48,8 +47,6 @@ import org.apache.nifi.authentication.LoginIdentityProvider;
 import org.apache.nifi.authentication.exception.IdentityAccessException;
 import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
 import org.apache.nifi.security.util.CertificateUtils;
-import org.apache.nifi.util.StringUtils;
-import static org.apache.nifi.web.api.ApplicationResource.CLIENT_ID;
 import org.apache.nifi.web.api.dto.AccessStatusDTO;
 import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
 import org.apache.nifi.web.api.dto.RevisionDTO;
@@ -62,15 +59,15 @@ import org.apache.nifi.web.security.jwt.JwtService;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
 import org.apache.nifi.web.security.x509.X509CertificateExtractor;
-import org.apache.nifi.web.security.x509.X509CertificateValidator;
+import org.apache.nifi.web.security.x509.X509IdentityProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.authentication.AccountStatusException;
-import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 import org.springframework.security.authentication.AuthenticationServiceException;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
 
 /**
  * RESTful endpoint for managing a cluster.
@@ -82,13 +79,15 @@ import org.springframework.security.web.authentication.preauth.x509.X509Principa
 )
 public class AccessResource extends ApplicationResource {
 
-    private NiFiProperties properties;
+    private static final Logger logger = LoggerFactory.getLogger(AccessResource.class);
 
-    private X509CertificateValidator certificateValidator;
-    private X509CertificateExtractor certificateExtractor;
-    private X509PrincipalExtractor principalExtractor;
+    private static final String AUTHORIZATION = "Authorization";
+
+    private NiFiProperties properties;
 
     private LoginIdentityProvider loginIdentityProvider;
+    private X509CertificateExtractor certificateExtractor;
+    private X509IdentityProvider certificateIdentityProvider;
     private JwtService jwtService;
 
     private AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService;
@@ -172,57 +171,64 @@ public class AccessResource extends ApplicationResource {
         final AccessStatusDTO accessStatus = new AccessStatusDTO();
 
         try {
-            // look for a certificate
-            final X509Certificate certificate = certificateExtractor.extractClientCertificate(httpServletRequest);
+            final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(httpServletRequest);
 
-            // if no certificate, just check the credentials
-            if (certificate == null) {
-                final String principal = jwtService.getAuthentication(httpServletRequest);
+            // if there is not certificate, consider a token
+            if (certificates == null) {
+                // look for an authorization token
+                final String authorization = httpServletRequest.getHeader(AUTHORIZATION);
 
-                // ensure we have something we can work with (certificate or crendentials)
-                if (principal == null) {
+                // if there is no authorization header, we don't know the user
+                if (authorization == null) {
                     accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
                     accessStatus.setMessage("No credentials supplied, unknown user.");
                 } else {
-                    // 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);
-
-                    // check authorization for this user
-                    checkAuthorization(chain);
-
-                    // no issues with authorization
-                    accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
-                    accessStatus.setStatus("Account is active and authorized");
+                    // TODO - use this token with the JWT service
+                    final String token = StringUtils.substringAfterLast(authorization, " ");
+
+                    // TODO - do not call this method of the jwt service
+                    final String principal = jwtService.getAuthentication(httpServletRequest);
+
+                    // TODO - catch jwt exception?
+                    // ensure we have something we can work with (certificate or crendentials)
+                    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));
+
+                        // without a certificate, this is not a proxied request
+                        final List<String> chain = Arrays.asList(principal);
+
+                        // check authorization for this user
+                        checkAuthorization(chain);
+
+                        // no issues with authorization
+                        accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
+                        accessStatus.setMessage("Account is active and authorized");
+                    }
                 }
             } else {
-                // we have a certificate so let's consider a proxy chain
-                final String principal = principalExtractor.extractPrincipal(certificate).toString();
-
-                try {
-                    // validate the certificate
-                    certificateValidator.validateClientCertificate(httpServletRequest, certificate);
-                } catch (CertificateExpiredException cee) {
-                    throw new IllegalArgumentException(String.format("Client certificate for (%s) is expired.", principal), cee);
-                } catch (CertificateNotYetValidException cnyve) {
-                    throw new IllegalArgumentException(String.format("Client certificate for (%s) is not yet valid.", principal), cnyve);
-                } catch (final Exception e) {
-                    throw new IllegalArgumentException(e.getMessage(), e);
-                }
+                final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates);
 
-                // set the user identity
-                accessStatus.setIdentity(principal);
-                accessStatus.setUsername(CertificateUtils.extractUsername(principal));
+                // 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(ProxiedEntitiesUtils.buildProxyChain(httpServletRequest, principal));
+                checkAuthorization(proxyChain);
+
+                // set the user identity
+                accessStatus.setIdentity(proxyChain.get(0));
+                accessStatus.setUsername(CertificateUtils.extractUsername(proxyChain.get(0)));
 
                 // no issues with authorization
                 accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
-                accessStatus.setStatus("Account is active and authorized");
+                accessStatus.setMessage("Account is active and authorized");
             }
         } catch (final UsernameNotFoundException unfe) {
             accessStatus.setStatus(AccessStatusDTO.Status.UNREGISTERED.name());
@@ -277,6 +283,7 @@ public class AccessResource extends ApplicationResource {
     @ApiResponses(
             value = {
                 @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
                 @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
             }
     )
@@ -297,58 +304,42 @@ public class AccessResource extends ApplicationResource {
 
         final LoginAuthenticationToken loginAuthenticationToken;
 
-        // if we don't have username/password, consider JWT or x509
-        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
-            // look for a certificate
-            final X509Certificate certificate = certificateExtractor.extractClientCertificate(httpServletRequest);
-
-            // if there is no certificate, look for an existing token
-            if (certificate == null) {
-                // if not configured for login, don't consider existing tokens
-                if (loginIdentityProvider == null) {
-                    throw new IllegalStateException("Login not supported.");
-                }
+        final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(httpServletRequest);
 
-                // look for the principal
-                final String principal = jwtService.getAuthentication(httpServletRequest);
-                if (principal == null) {
-                    throw new AuthenticationCredentialsNotFoundException("Unable to issue token as issue token as no credentials were found in the request.");
-                }
-
-                // create the authentication token
-                loginAuthenticationToken = new LoginAuthenticationToken(principal, loginIdentityProvider.getExpiration());
-            } else {
-                // extract the principal
-                final String principal = principalExtractor.extractPrincipal(certificate).toString();
-
-                try {
-                    certificateValidator.validateClientCertificate(httpServletRequest, certificate);
-                } catch (CertificateExpiredException cee) {
-                    throw new IllegalArgumentException(String.format("Client certificate for (%s) is expired.", principal), cee);
-                } catch (CertificateNotYetValidException cnyve) {
-                    throw new IllegalArgumentException(String.format("Client certificate for (%s) is not yet valid.", principal), cnyve);
-                } catch (final Exception e) {
-                    throw new IllegalArgumentException(e.getMessage(), e);
-                }
-
-                // authorize the proxy if necessary
-                authorizeProxyIfNecessary(ProxiedEntitiesUtils.buildProxyChain(httpServletRequest, principal));
-
-                // create the authentication token
-                loginAuthenticationToken = new LoginAuthenticationToken(principal, loginIdentityProvider.getExpiration());
+        // if there is not certificate, consider login credentials
+        if (certificates == null) {
+            // ensure we have login credentials
+            if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
+                throw new IllegalArgumentException("The username and password must be specified.");
             }
-        } else {
+
             try {
                 // attempt to authenticate
                 final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(new LoginCredentials(username, password));
 
                 // create the authentication token
-                loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getUsername(), loginIdentityProvider.getExpiration());
+                loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getUsername(), authenticationResponse.getExpiration());
             } catch (final InvalidLoginCredentialsException ilce) {
                 throw new IllegalArgumentException("The supplied username and password are not valid.", ilce);
             } catch (final IdentityAccessException iae) {
                 throw new AdministrationException(iae.getMessage(), iae);
             }
+        } else {
+            // consider a certificate
+            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.");
+            }
+
+            // authorize the proxy if necessary
+            authorizeProxyIfNecessary(proxyChain);
+
+            // create the authentication token
+            loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration());
         }
 
         // generate JWT for response
@@ -371,11 +362,14 @@ public class AccessResource extends ApplicationResource {
                 userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain));
             } catch (final UsernameNotFoundException unfe) {
                 // if a username not found exception was thrown, the proxies were authorized and now
-                // we can issue a new ID token to the end user
+                // we can issue a new token to the end user which they will use to identify themselves
+                // when they enter a new account request
+            } catch (final AuthenticationServiceException ase) {
+                // throw an administration exception which will return a 500
+                throw new AdministrationException(ase.getMessage(), ase);
             } catch (final Exception e) {
-                // any other issue we're going to treat as an authentication exception which will return 401
-                throw new AdministrationException(e.getMessage(), e) {
-                };
+                // any other issue we're going to treat as access denied exception which will return 403
+                throw new AccessDeniedException(e.getMessage(), e);
             }
         }
     }
@@ -393,16 +387,12 @@ public class AccessResource extends ApplicationResource {
         this.jwtService = jwtService;
     }
 
-    public void setCertificateValidator(X509CertificateValidator certificateValidator) {
-        this.certificateValidator = certificateValidator;
-    }
-
     public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
         this.certificateExtractor = certificateExtractor;
     }
 
-    public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
-        this.principalExtractor = principalExtractor;
+    public void setCertificateIdentityProvider(X509IdentityProvider certificateIdentityProvider) {
+        this.certificateIdentityProvider = certificateIdentityProvider;
     }
 
     public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.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/ApplicationResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
index 2e15d30..d0c36d4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
@@ -53,6 +53,8 @@ import org.apache.nifi.web.util.WebUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.nifi.user.NiFiUser;
+import org.apache.nifi.web.security.user.NiFiUserUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.core.Authentication;
@@ -363,9 +365,9 @@ public abstract class ApplicationResource {
         if (httpServletRequest.isSecure()) {
 
             // add the certificate DN to the proxy chain
-            final String xProxiedEntitiesChain = ProxiedEntitiesUtils.getXProxiedEntitiesChain(httpServletRequest);
-            if (StringUtils.isNotBlank(xProxiedEntitiesChain)) {
-                result.put(PROXIED_ENTITIES_CHAIN_HTTP_HEADER, xProxiedEntitiesChain);
+            final NiFiUser user = NiFiUserUtils.getNiFiUser();
+            if (user != null) {
+                result.put(PROXIED_ENTITIES_CHAIN_HTTP_HEADER, ProxiedEntitiesUtils.buildProxiedEntitiesChainString(user));
             }
 
             // add the user's authorities (if any) to the headers

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 0e1b20b..a02d1fd 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -823,7 +823,7 @@ public class ControllerFacade {
             final Map<String, String> attributes = event.getAttributes();
 
             // calculate the dn chain
-            final List<String> dnChain = ProxiedEntitiesUtils.getXProxiedEntitiesChain(user);
+            final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user);
 
             // ensure the users in this chain are allowed to download this content
             final DownloadAuthorization downloadAuthorization = userService.authorizeDownload(dnChain, attributes);

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/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 e992dc9..73929d8 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
@@ -243,9 +243,8 @@
     </bean>
     <bean id="accessResource" class="org.apache.nifi.web.api.AccessResource" scope="singleton">
         <property name="properties" ref="nifiProperties"/>
-        <property name="certificateValidator" ref="certificateValidator"/>
         <property name="certificateExtractor" ref="certificateExtractor"/>
-        <property name="principalExtractor" ref="principalExtractor"/>
+        <property name="certificateIdentityProvider" ref="certificateIdentityProvider"/>
         <property name="loginIdentityProvider" ref="loginIdentityProvider"/>
         <property name="jwtService" ref="jwtService"/>
         <property name="userDetailsService" ref="userDetailsService"/>

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
new file mode 100644
index 0000000..82fe73a
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.integration.accesscontrol;
+
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientResponse;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import javax.net.ssl.SSLContext;
+import org.apache.commons.io.FileUtils;
+import org.apache.nifi.integration.util.NiFiTestServer;
+import org.apache.nifi.integration.util.NiFiTestUser;
+import org.apache.nifi.integration.util.SourceTestProcessor;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.NarClassLoaders;
+import org.apache.nifi.security.util.SslContextFactory;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
+import org.apache.nifi.web.api.dto.AccessStatusDTO;
+import org.apache.nifi.web.api.dto.ProcessorDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
+import org.apache.nifi.web.api.entity.AccessStatusEntity;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.apache.nifi.web.util.WebUtils;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Access token endpoint test.
+ */
+public class AccessTokenEndpointTest {
+
+    private static final String CLIENT_ID = "token-endpoint-id";
+    private static final String CONTEXT_PATH = "/nifi-api";
+    private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-admin.xml";
+
+    private static NiFiTestServer SERVER;
+    private static NiFiTestUser TOKEN_USER;
+    private static String BASE_URL;
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        // configure the location of the nifi properties
+        File nifiPropertiesFile = new File("src/test/resources/access-control/nifi.properties");
+        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath());
+
+        // update the flow.xml property
+        NiFiProperties props = NiFiProperties.getInstance();
+        props.setProperty("nifi.flow.configuration.file", FLOW_XML_PATH);
+
+        // delete the database directory to avoid issues with re-registration in testRequestAccessUsingToken
+        FileUtils.deleteDirectory(props.getDatabaseRepositoryPath().toFile());
+
+        // load extensions
+        NarClassLoaders.load(props);
+        ExtensionManager.discoverExtensions();
+
+        // start the server
+        SERVER = new NiFiTestServer("src/main/webapp", CONTEXT_PATH);
+        SERVER.startServer();
+        SERVER.loadFlow();
+
+        // get the base url
+        BASE_URL = SERVER.getBaseUrl() + CONTEXT_PATH;
+
+        // create the user
+        final Client client = WebUtils.createClient(null, createTrustContext(props));
+        TOKEN_USER = new NiFiTestUser(client, null);
+    }
+
+    private static SSLContext createTrustContext(final NiFiProperties props) throws Exception {
+        return SslContextFactory.createTrustSslContext(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE),
+                props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray(),
+                props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE), "TLS");
+    }
+
+    // -----------
+    // LOGIN CONIG
+    // -----------
+    /**
+     * Test getting access configuration.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testGetAccessConfig() throws Exception {
+        String url = BASE_URL + "/access/config";
+
+        ClientResponse response = TOKEN_USER.testGet(url);
+
+        // ensure the request is successful
+        Assert.assertEquals(200, response.getStatus());
+
+        // extract the process group
+        AccessConfigurationEntity accessConfigEntity = response.getEntity(AccessConfigurationEntity.class);
+
+        // ensure there is content
+        Assert.assertNotNull(accessConfigEntity);
+
+        // extract the process group dto
+        AccessConfigurationDTO accessConfig = accessConfigEntity.getConfig();
+
+        // verify config
+        Assert.assertTrue(accessConfig.getSupportsLogin());
+        Assert.assertFalse(accessConfig.getSupportsAnonymous());
+    }
+
+    /**
+     * Obtains a token and creates a processor using it.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testCreateProcessorUsingToken() throws Exception {
+        String url = BASE_URL + "/access/token";
+
+        ClientResponse response = TOKEN_USER.testCreateToken(url, "user@nifi", "whateve");
+
+        // ensure the request is successful
+        Assert.assertEquals(201, response.getStatus());
+
+        // get the token
+        String token = response.getEntity(String.class);
+
+        // attempt to create a processor with it
+        createProcessor(token);
+    }
+
+    private ProcessorDTO createProcessor(final String token) throws Exception {
+        String url = BASE_URL + "/controller/process-groups/root/processors";
+
+        // authorization header
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", "Bearer " + token);
+
+        // create the processor
+        ProcessorDTO processor = new ProcessorDTO();
+        processor.setName("Copy");
+        processor.setType(SourceTestProcessor.class.getName());
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(CLIENT_ID);
+        revision.setVersion(NiFiTestUser.REVISION);
+
+        // create the entity body
+        ProcessorEntity entity = new ProcessorEntity();
+        entity.setRevision(revision);
+        entity.setProcessor(processor);
+
+        // perform the request
+        ClientResponse response = TOKEN_USER.testPostWithHeaders(url, entity, headers);
+
+        // ensure the request is successful
+        Assert.assertEquals(201, response.getStatus());
+
+        // get the entity body
+        entity = response.getEntity(ProcessorEntity.class);
+
+        // verify creation
+        processor = entity.getProcessor();
+        Assert.assertEquals("Copy", processor.getName());
+        Assert.assertEquals("org.apache.nifi.integration.util.SourceTestProcessor", processor.getType());
+
+        return processor;
+    }
+
+    /**
+     * Verifies the response when bad credentials are specified.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testInvalidCredentials() throws Exception {
+        String url = BASE_URL + "/access/token";
+
+        ClientResponse response = TOKEN_USER.testCreateToken(url, "user@nifi", "not a real password");
+
+        // ensure the request is successful
+        Assert.assertEquals(400, response.getStatus());
+    }
+
+    /**
+     * Verifies the response when the user is known.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testUnkownUser() throws Exception {
+        String url = BASE_URL + "/access/token";
+
+        ClientResponse response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password");
+
+        // ensure the request is successful
+        Assert.assertEquals(400, response.getStatus());
+    }
+
+    /**
+     * Request access using access token.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testRequestAccessUsingToken() throws Exception {
+        String accessStatusUrl = BASE_URL + "/access";
+        String accessTokenUrl = BASE_URL + "/access/token";
+        String registrationUrl = BASE_URL + "/controller/users";
+
+        ClientResponse response = TOKEN_USER.testGet(accessStatusUrl);
+
+        // ensure the request is successful
+        Assert.assertEquals(200, response.getStatus());
+
+        AccessStatusEntity accessStatusEntity = response.getEntity(AccessStatusEntity.class);
+        AccessStatusDTO accessStatus = accessStatusEntity.getAccessStatus();
+
+        // verify unknown
+        Assert.assertEquals("UNKNOWN", accessStatus.getStatus());
+
+        response = TOKEN_USER.testCreateToken(accessTokenUrl, "unregistered-user@nifi", "password");
+
+        // ensure the request is successful
+        Assert.assertEquals(201, response.getStatus());
+
+        // get the token
+        String token = response.getEntity(String.class);
+
+        // authorization header
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", "Bearer " + token);
+
+        // check the status with the token
+        response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
+
+        // ensure the request is successful
+        Assert.assertEquals(200, response.getStatus());
+
+        accessStatusEntity = response.getEntity(AccessStatusEntity.class);
+        accessStatus = accessStatusEntity.getAccessStatus();
+
+        // verify unregistered
+        Assert.assertEquals("UNREGISTERED", accessStatus.getStatus());
+
+        response = TOKEN_USER.testRegisterUser(registrationUrl, "Gimme access", headers);
+
+        // ensure the request is successful
+        Assert.assertEquals(201, response.getStatus());
+
+        // check the status with the token
+        response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
+
+        // ensure the request is successful
+        Assert.assertEquals(200, response.getStatus());
+
+        accessStatusEntity = response.getEntity(AccessStatusEntity.class);
+        accessStatus = accessStatusEntity.getAccessStatus();
+
+        // verify unregistered
+        Assert.assertEquals("NOT_ACTIVE", accessStatus.getStatus());
+    }
+
+    @AfterClass
+    public static void cleanup() throws Exception {
+        // shutdown the server
+        SERVER.shutdownServer();
+        SERVER = null;
+
+        // look for the flow.xml
+        File flow = new File(FLOW_XML_PATH);
+        if (flow.exists()) {
+            flow.delete();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java
index d51b7df..d29be92 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java
@@ -37,7 +37,7 @@ import org.apache.nifi.authorization.DownloadAuthorization;
  */
 public class NiFiTestAuthorizationProvider implements AuthorityProvider {
 
-    private Map<String, Set<Authority>> users;
+    private final Map<String, Set<Authority>> users;
 
     /**
      * Creates a new FileAuthorizationProvider.
@@ -48,6 +48,7 @@ public class NiFiTestAuthorizationProvider implements AuthorityProvider {
         users.put("CN=Lastname Firstname Middlename monitor, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_MONITOR));
         users.put("CN=Lastname Firstname Middlename dfm, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_DFM));
         users.put("CN=Lastname Firstname Middlename admin, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_ADMIN));
+        users.put("user@nifi", EnumSet.of(Authority.ROLE_DFM));
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..8ee51d9
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
@@ -0,0 +1,75 @@
+/*
+ * 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.integration.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.apache.nifi.authorization.exception.ProviderCreationException;
+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;
+
+/**
+ *
+ */
+public class NiFiTestLoginIdentityProvider implements LoginIdentityProvider {
+
+    private final Map<String, String> users;
+
+    /**
+     * Creates a new FileAuthorizationProvider.
+     */
+    public NiFiTestLoginIdentityProvider() {
+        users = new HashMap<>();
+        users.put("user@nifi", "whateve");
+        users.put("unregistered-user@nifi", "password");
+    }
+
+    private void checkUser(final String user, final String password) {
+        if (!users.containsKey(user)) {
+            throw new InvalidLoginCredentialsException("Unknown user");
+        }
+
+        if (!users.get(user).equals(password)) {
+            throw new InvalidLoginCredentialsException("Invalid password");
+        }
+    }
+
+    @Override
+    public AuthenticationResponse authenticate(LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException {
+        checkUser(credentials.getUsername(), credentials.getPassword());
+        return new AuthenticationResponse(credentials.getUsername(), credentials.getUsername(), TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS));
+    }
+
+    @Override
+    public void initialize(LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException {
+    }
+
+    @Override
+    public void onConfigured(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
+    }
+
+    @Override
+    public void preDestruction() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java
index 42b0aab..38c2d41 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java
@@ -78,8 +78,12 @@ public class NiFiTestServer {
     private void createSecureConnector() {
         org.eclipse.jetty.util.ssl.SslContextFactory contextFactory = new org.eclipse.jetty.util.ssl.SslContextFactory();
 
-        // need client auth
-        contextFactory.setNeedClientAuth(properties.getNeedClientAuth());
+        // require client auth when not supporting login or anonymous access
+        if (StringUtils.isBlank(properties.getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER)) && properties.getAnonymousAuthorities().isEmpty()) {
+            contextFactory.setNeedClientAuth(true);
+        } else {
+            contextFactory.setWantClientAuth(true);
+        }
 
         /* below code sets JSSE system properties when values are provided */
         // keystore properties
@@ -163,7 +167,6 @@ public class NiFiTestServer {
     }
 
     public Client getClient() {
-        // create the client
         return WebUtils.createClient(null, SslContextFactory.createSslContext(properties));
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
----------------------------------------------------------------------
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 c0e9246..621dc09 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
@@ -34,9 +34,27 @@ public class NiFiTestUser {
     private final Client client;
     private final String proxyDn;
 
-    public NiFiTestUser(Client client, String dn) {
+    public NiFiTestUser(Client client, String proxyDn) {
         this.client = client;
-        this.proxyDn = ProxiedEntitiesUtils.formatProxyDn(dn);
+        if (proxyDn != null) {
+            this.proxyDn = ProxiedEntitiesUtils.formatProxyDn(proxyDn);
+        } else {
+            this.proxyDn = null;
+        }
+    }
+
+    /**
+     * Conditionally adds the proxied entities chain.
+     *
+     * @param builder the resource builder
+     * @return the resource builder
+     */
+    private WebResource.Builder addProxiedEntities(final WebResource.Builder builder) {
+        if (proxyDn == null) {
+            return builder;
+        } else {
+            return builder.header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn);
+        }
     }
 
     /**
@@ -58,6 +76,18 @@ public class NiFiTestUser {
      * @return response
      */
     public ClientResponse testGet(String url, Map<String, String> queryParams) {
+        return testGetWithHeaders(url, queryParams, null);
+    }
+
+    /**
+     * Performs a GET using the specified url and query parameters.
+     *
+     * @param url url
+     * @param queryParams params
+     * @param headers http headers
+     * @return response
+     */
+    public ClientResponse testGetWithHeaders(String url, Map<String, String> queryParams, Map<String, String> headers) {
         // get the resource
         WebResource resource = client.resource(url);
 
@@ -68,8 +98,18 @@ public class NiFiTestUser {
             }
         }
 
+        // get the builder
+        WebResource.Builder builder = addProxiedEntities(resource.accept(MediaType.APPLICATION_JSON));
+
+        // append any headers
+        if (headers != null && !headers.isEmpty()) {
+            for (String key : headers.keySet()) {
+                builder = builder.header(key, headers.get(key));
+            }
+        }
+
         // perform the query
-        return resource.accept(MediaType.APPLICATION_JSON).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn).get(ClientResponse.class);
+        return builder.get(ClientResponse.class);
     }
 
     /**
@@ -92,14 +132,34 @@ public class NiFiTestUser {
      * @throws Exception ex
      */
     public ClientResponse testPost(String url, Object entity) throws Exception {
+        return testPostWithHeaders(url, entity, null);
+    }
+
+    /**
+     * Performs a POST using the specified url and entity body.
+     *
+     * @param url url
+     * @param entity entity
+     * @param headers http headers
+     * @return response
+     * @throws Exception ex
+     */
+    public ClientResponse testPostWithHeaders(String url, Object entity, Map<String, String> headers) throws Exception {
         // get the resource
-        WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn);
+        WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON));
 
         // include the request entity
         if (entity != null) {
             resourceBuilder = resourceBuilder.entity(entity);
         }
 
+        // append any headers
+        if (headers != null && !headers.isEmpty()) {
+            for (String key : headers.keySet()) {
+                resourceBuilder = resourceBuilder.header(key, headers.get(key));
+            }
+        }
+
         // perform the request
         return resourceBuilder.post(ClientResponse.class);
     }
@@ -109,18 +169,38 @@ public class NiFiTestUser {
      *
      * @param url url
      * @param entity entity
-     * @return repsonse
+     * @return response
      * @throws Exception ex
      */
     public ClientResponse testPostMultiPart(String url, Object entity) throws Exception {
+        return testPostMultiPartWithHeaders(url, entity, null);
+    }
+
+    /**
+     * Performs a POST using the specified url and entity body.
+     *
+     * @param url url
+     * @param entity entity
+     * @param headers http headers
+     * @return response
+     * @throws Exception ex
+     */
+    public ClientResponse testPostMultiPartWithHeaders(String url, Object entity, Map<String, String> headers) throws Exception {
         // get the resource
-        WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_XML).type(MediaType.MULTIPART_FORM_DATA).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn);
+        WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_XML).type(MediaType.MULTIPART_FORM_DATA));
 
         // include the request entity
         if (entity != null) {
             resourceBuilder = resourceBuilder.entity(entity);
         }
 
+        // append any headers
+        if (headers != null && !headers.isEmpty()) {
+            for (String key : headers.keySet()) {
+                resourceBuilder = resourceBuilder.header(key, headers.get(key));
+            }
+        }
+
         // perform the request
         return resourceBuilder.post(ClientResponse.class);
     }
@@ -134,6 +214,19 @@ public class NiFiTestUser {
      * @throws java.lang.Exception ex
      */
     public ClientResponse testPost(String url, Map<String, String> formData) throws Exception {
+        return testPostWithHeaders(url, formData, null);
+    }
+
+    /**
+     * Performs a POST using the specified url and form data.
+     *
+     * @param url url
+     * @param formData form data
+     * @param headers http headers
+     * @return response
+     * @throws java.lang.Exception ex
+     */
+    public ClientResponse testPostWithHeaders(String url, Map<String, String> formData, Map<String, String> headers) throws Exception {
         // convert the form data
         MultivaluedMapImpl entity = new MultivaluedMapImpl();
         for (String key : formData.keySet()) {
@@ -141,14 +234,20 @@ public class NiFiTestUser {
         }
 
         // get the resource
-        WebResource.Builder resourceBuilder
-                = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn);
+        WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED));
 
         // add the form data if necessary
         if (!entity.isEmpty()) {
             resourceBuilder = resourceBuilder.entity(entity);
         }
 
+        // append any headers
+        if (headers != null && !headers.isEmpty()) {
+            for (String key : headers.keySet()) {
+                resourceBuilder = resourceBuilder.header(key, headers.get(key));
+            }
+        }
+
         // perform the request
         return resourceBuilder.post(ClientResponse.class);
     }
@@ -162,14 +261,34 @@ public class NiFiTestUser {
      * @throws java.lang.Exception ex
      */
     public ClientResponse testPut(String url, Object entity) throws Exception {
+        return testPutWithHeaders(url, entity, null);
+    }
+
+    /**
+     * Performs a PUT using the specified url and entity body.
+     *
+     * @param url url
+     * @param entity entity
+     * @param headers http headers
+     * @return response
+     * @throws java.lang.Exception ex
+     */
+    public ClientResponse testPutWithHeaders(String url, Object entity, Map<String, String> headers) throws Exception {
         // get the resource
-        WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn);
+        WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON));
 
         // include the request entity
         if (entity != null) {
             resourceBuilder = resourceBuilder.entity(entity);
         }
 
+        // append any headers
+        if (headers != null && !headers.isEmpty()) {
+            for (String key : headers.keySet()) {
+                resourceBuilder = resourceBuilder.header(key, headers.get(key));
+            }
+        }
+
         // perform the request
         return resourceBuilder.put(ClientResponse.class);
     }
@@ -183,6 +302,19 @@ public class NiFiTestUser {
      * @throws java.lang.Exception ex
      */
     public ClientResponse testPut(String url, Map<String, String> formData) throws Exception {
+        return testPutWithHeaders(url, formData, null);
+    }
+
+    /**
+     * Performs a PUT using the specified url and form data.
+     *
+     * @param url url
+     * @param formData form data
+     * @param headers http headers
+     * @return response
+     * @throws java.lang.Exception ex
+     */
+    public ClientResponse testPutWithHeaders(String url, Map<String, String> formData, Map<String, String> headers) throws Exception {
         // convert the form data
         MultivaluedMapImpl entity = new MultivaluedMapImpl();
         for (String key : formData.keySet()) {
@@ -190,14 +322,20 @@ public class NiFiTestUser {
         }
 
         // get the resource
-        WebResource.Builder resourceBuilder
-                = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn);
+        WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED));
 
         // add the form data if necessary
         if (!entity.isEmpty()) {
             resourceBuilder = resourceBuilder.entity(entity);
         }
 
+        // append any headers
+        if (headers != null && !headers.isEmpty()) {
+            for (String key : headers.keySet()) {
+                resourceBuilder = resourceBuilder.header(key, headers.get(key));
+            }
+        }
+
         // perform the request
         return resourceBuilder.put(ClientResponse.class);
     }
@@ -210,24 +348,26 @@ public class NiFiTestUser {
      * @throws java.lang.Exception ex
      */
     public ClientResponse testDelete(String url) throws Exception {
-        return testDelete(url, (Object) null);
+        return testDelete(url, null);
     }
 
     /**
      * Performs a DELETE using the specified url and entity.
      *
      * @param url url
-     * @param entity entity
-     * @return repsonse
+     * @param headers http headers
+     * @return response
      * @throws java.lang.Exception ex
      */
-    public ClientResponse testDelete(String url, Object entity) throws Exception {
+    public ClientResponse testDeleteWithHeaders(String url, Map<String, String> headers) throws Exception {
         // get the resource
-        WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn);
+        WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON));
 
-        // append any query parameters
-        if (entity != null) {
-            resourceBuilder = resourceBuilder.entity(entity);
+        // append any headers
+        if (headers != null && !headers.isEmpty()) {
+            for (String key : headers.keySet()) {
+                resourceBuilder = resourceBuilder.header(key, headers.get(key));
+            }
         }
 
         // perform the query
@@ -254,7 +394,56 @@ public class NiFiTestUser {
         }
 
         // perform the request
-        return resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn).delete(ClientResponse.class);
+        return addProxiedEntities(resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED)).delete(ClientResponse.class);
     }
 
+    /**
+     * Attempts to create a token with the specified username and password.
+     *
+     * @param url the url
+     * @param username the username
+     * @param password the password
+     * @return response
+     * @throws Exception ex
+     */
+    public ClientResponse testCreateToken(String url, String username, String password) throws Exception {
+        // convert the form data
+        MultivaluedMapImpl entity = new MultivaluedMapImpl();
+        entity.add("username", username);
+        entity.add("password", password);
+
+        // get the resource
+        WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.TEXT_PLAIN).type(MediaType.APPLICATION_FORM_URLENCODED)).entity(entity);
+
+        // perform the request
+        return resourceBuilder.post(ClientResponse.class);
+    }
+
+    /**
+     * Attempts to create a token with the specified username and password.
+     *
+     * @param url the url
+     * @param justification justification
+     * @param headers http headers
+     * @return response
+     * @throws Exception ex
+     */
+    public ClientResponse testRegisterUser(String url, String justification, Map<String, String> headers) throws Exception {
+        // convert the form data
+        MultivaluedMapImpl entity = new MultivaluedMapImpl();
+        entity.add("justification", justification);
+
+        // get the resource
+        WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.TEXT_PLAIN).type(MediaType.APPLICATION_FORM_URLENCODED)).entity(entity);
+
+        // append any headers
+        if (headers != null && !headers.isEmpty()) {
+            for (String key : headers.keySet()) {
+                resourceBuilder = resourceBuilder.header(key, headers.get(key));
+            }
+        }
+
+        // perform the request
+        return resourceBuilder.post(ClientResponse.class);
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider
new file mode 100644
index 0000000..4b42e4f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider
@@ -0,0 +1,15 @@
+# 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.
+org.apache.nifi.integration.util.NiFiTestLoginIdentityProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/controller-services.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/controller-services.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/controller-services.xml
deleted file mode 100644
index f5bd96a..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/controller-services.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!--
-  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.
--->
-<services>
-
-</services>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml
new file mode 100644
index 0000000..04120c9
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  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.
+-->
+<!--
+    This file lists all authority providers to use when running securely.
+-->
+<loginIdentityProviders>
+    <provider>
+        <identifier>test-provider</identifier>
+        <class>org.apache.nifi.integration.util.NiFiTestLoginIdentityProvider</class>
+    </provider>
+</loginIdentityProviders>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties
index fc20d78..10db651 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties
@@ -14,16 +14,15 @@
 # limitations under the License.
 
 # Core Properties #
-nifi.version=nifi 0.2.1-SNAPSHOT 
+nifi.version=nifi version 
 nifi.flow.configuration.file=
 nifi.flow.configuration.archive.dir=target/archive
 nifi.flowcontroller.autoResumeState=true
 nifi.flowcontroller.graceful.shutdown.period=10 sec
 nifi.flowservice.writedelay.interval=2 sec
 
-nifi.reporting.task.configuration.file=target/test-classes/access-control/reporting-tasks.xml
-nifi.controller.service.configuration.file=target/test-classes/access-control/controller-services.xml
 nifi.authority.provider.configuration.file=target/test-classes/access-control/authority-providers.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
@@ -93,11 +92,11 @@ nifi.security.truststoreType=JKS
 nifi.security.truststorePasswd=localtest
 nifi.security.needClientAuth=true
 nifi.security.user.authority.provider=test-provider
-nifi.security.user.login.identity.provider=
+nifi.security.user.login.identity.provider=test-provider
 nifi.security.authorizedUsers.file=target/test-classes/access-control/users.xml
 nifi.security.user.credential.cache.duration=1 hr
 nifi.security.support.new.account.requests=
-nifi.security.default.user.roles=
+nifi.security.anonymous.authorities=
 
 # cluster common properties (cluster manager and nodes must have same values) #
 nifi.cluster.protocol.heartbeat.interval=5 sec

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/reporting-tasks.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/reporting-tasks.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/reporting-tasks.xml
deleted file mode 100644
index 251735e..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/reporting-tasks.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.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.
--->
-<tasks>
-</tasks>

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/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 e5e5c04..ec34ace 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
@@ -29,12 +29,14 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.user.NiFiUser;
 import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
 import org.apache.nifi.web.security.user.NiFiUserUtils;
 import org.slf4j.Logger;
 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;
@@ -85,8 +87,13 @@ public abstract class NiFiAuthenticationFilter implements Filter {
 
     private void authenticate(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
         try {
-            final Authentication authenticated = attemptAuthentication(request, response);
+            final NiFiAuthenticationRequestToken authenticated = attemptAuthentication(request, response);
             if (authenticated != null) {
+                // log the request attempt - response details will be logged later
+                logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)",
+                        ProxiedEntitiesUtils.formatProxyDn(StringUtils.join(authenticated.getChain(), "><")), request.getMethod(),
+                        request.getRequestURL().toString(), request.getRemoteAddr()));
+
                 final Authentication authorized = authenticationManager.authenticate(authenticated);
                 successfulAuthorization(request, response, authorized);
             }
@@ -97,7 +104,7 @@ public abstract class NiFiAuthenticationFilter implements Filter {
         }
     }
 
-    public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response);
+    public abstract NiFiAuthenticationRequestToken attemptAuthentication(HttpServletRequest request, HttpServletResponse response);
 
     protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
         if (logger.isDebugEnabled()) {
@@ -127,6 +134,9 @@ 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);
+            out.println(ae.getMessage());
         } else if (ae instanceof AccountStatusException) {
             response.setStatus(HttpServletResponse.SC_FORBIDDEN);
             out.println(ae.getMessage());

http://git-wip-us.apache.org/repos/asf/nifi/blob/a1962077/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
index 79e8eb2..eb0684b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
@@ -47,10 +47,12 @@ public class NiFiAuthenticationProvider implements AuthenticationProvider {
             final UserDetails userDetails = userDetailsService.loadUserDetails(request);
 
             // build an authentication for accesing nifi
-            return new NiFiAuthorizationToken(userDetails);
+            final NiFiAuthorizationToken result = new NiFiAuthorizationToken(userDetails);
+            result.setDetails(request.getDetails());
+            return result;
         } catch (final UsernameNotFoundException unfe) {
-            // if the result was an authenticated new account request and it could not be authorized because the user was not found,
-            // return the token so the new account could be created. this must go here to ensure that any proxies have been authorized
+            // if the authentication request is for a new account and it could not be authorized because the user was not found,
+            // return the token so the new account could be created. this must go here toe nsure that any proxies have been authorized
             if (isNewAccountAuthenticationToken(request)) {
                 return new NewAccountAuthenticationToken(((NewAccountAuthenticationRequestToken) authentication).getNewAccountRequest());
             } else {