You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2017/11/07 18:50:19 UTC

[03/17] nifi-registry git commit: NIFIREG-33 Add LDAP and JWT auth support

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
new file mode 100644
index 0000000..e0eddba
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security;
+
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.web.security.authentication.NiFiAnonymousUserFilter;
+import org.apache.nifi.registry.web.security.authentication.jwt.JwtAuthenticationFilter;
+import org.apache.nifi.registry.web.security.authentication.jwt.JwtAuthenticationProvider;
+import org.apache.nifi.registry.web.security.authentication.x509.X509AuthenticationFilter;
+import org.apache.nifi.registry.web.security.authentication.x509.X509AuthenticationProvider;
+import org.apache.nifi.registry.web.security.authentication.x509.X509CertificateExtractor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
+
+/**
+ * NiFi Web Api Spring security
+ */
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter {
+    private static final Logger logger = LoggerFactory.getLogger(NiFiRegistrySecurityConfig.class);
+
+    @Autowired private NiFiRegistryProperties properties;
+
+    @Autowired X509CertificateExtractor certificateExtractor;
+    @Autowired X509PrincipalExtractor principalExtractor;
+    @Autowired private X509AuthenticationProvider x509AuthenticationProvider;
+    private X509AuthenticationFilter x509AuthenticationFilter;
+
+    @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider;
+    private JwtAuthenticationFilter jwtAuthenticationFilter;
+
+//    @Autowired private OtpAuthenticationProvider otpAuthenticationProvider;
+//    private OtpAuthenticationFilter otpAuthenticationFilter;
+
+    private NiFiAnonymousUserFilter anonymousAuthenticationFilter;
+
+    public NiFiRegistrySecurityConfig() {
+        super(true); // disable defaults
+    }
+
+    @Override
+    public void configure(WebSecurity webSecurity) throws Exception {
+        // ignore the access endpoints for obtaining the access config, access token
+        // granting, and access status for a given user (note: we are not ignoring the
+        // the /access/download-token endpoints)
+        webSecurity
+                .ignoring()
+                .antMatchers("/access", "/access/config", "/access/token");
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+                .rememberMe().disable()
+                .authorizeRequests()
+                    .anyRequest().fullyAuthenticated()
+                    .and()
+                .sessionManagement()
+                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+
+        // x509
+        http.addFilterBefore(x509AuthenticationFilter(), AnonymousAuthenticationFilter.class);
+
+        // jwt
+        http.addFilterBefore(jwtFilterBean(), AnonymousAuthenticationFilter.class);
+
+        // otp
+        // http.addFilterBefore(otpFilterBean(), AnonymousAuthenticationFilter.class);
+
+        // anonymous
+        http.anonymous().authenticationFilter(anonymousFilter());
+    }
+
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        // override xxxBean method so the authentication manager is available in app context (necessary for the method level security)
+        return super.authenticationManagerBean();
+    }
+
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+        auth
+                .authenticationProvider(x509AuthenticationProvider)
+                .authenticationProvider(jwtAuthenticationProvider);
+//                .authenticationProvider(otpAuthenticationProvider); // TODO OTP support
+    }
+
+    @Bean
+    public JwtAuthenticationFilter jwtFilterBean() throws Exception {
+        if (jwtAuthenticationFilter == null) {
+            jwtAuthenticationFilter = new JwtAuthenticationFilter();
+            jwtAuthenticationFilter.setProperties(properties);
+            jwtAuthenticationFilter.setAuthenticationManager(authenticationManager());
+        }
+        return jwtAuthenticationFilter;
+    }
+
+//    @Bean // TODO OtpAuthenticationFilter
+//    public OtpAuthenticationFilter otpFilterBean() throws Exception {
+//        if (otpAuthenticationFilter == null) {
+//            otpAuthenticationFilter = new OtpAuthenticationFilter();
+//            otpAuthenticationFilter.setProperties(properties);
+//            otpAuthenticationFilter.setAuthenticationManager(authenticationManager());
+//        }
+//        return otpAuthenticationFilter;
+//    }
+
+    @Bean
+    public X509AuthenticationFilter x509AuthenticationFilter() throws Exception {
+        if (x509AuthenticationFilter == null) {
+            x509AuthenticationFilter = new X509AuthenticationFilter();
+            x509AuthenticationFilter.setProperties(properties);
+            x509AuthenticationFilter.setCertificateExtractor(certificateExtractor);
+            x509AuthenticationFilter.setPrincipalExtractor(principalExtractor);
+            x509AuthenticationFilter.setAuthenticationManager(authenticationManager());
+        }
+        return x509AuthenticationFilter;
+    }
+
+    @Bean
+    public NiFiAnonymousUserFilter anonymousFilter() throws Exception {
+        if (anonymousAuthenticationFilter == null) {
+            anonymousAuthenticationFilter = new NiFiAnonymousUserFilter();
+        }
+        return anonymousAuthenticationFilter;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/ProxiedEntitiesUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/ProxiedEntitiesUtils.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/ProxiedEntitiesUtils.java
deleted file mode 100644
index 33015fc..0000000
--- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/ProxiedEntitiesUtils.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.web.security;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.registry.authorization.user.NiFiUser;
-import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- *
- */
-public class ProxiedEntitiesUtils {
-    private static final Logger logger = LoggerFactory.getLogger(ProxiedEntitiesUtils.class);
-
-    public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain";
-    public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted";
-    public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails";
-
-    private static final String GT = ">";
-    private static final String ESCAPED_GT = "\\\\>";
-    private static final String LT = "<";
-    private static final String ESCAPED_LT = "\\\\<";
-
-    private static final String ANONYMOUS_CHAIN = "<>";
-
-    /**
-     * Formats the specified DN to be set as a HTTP header using well known conventions.
-     *
-     * @param dn raw dn
-     * @return the dn formatted as an HTTP header
-     */
-    public static String formatProxyDn(String dn) {
-        return LT + sanitizeDn(dn) + GT;
-    }
-
-    /**
-     * If a user provides a DN with the sequence '><', they could escape the tokenization process and impersonate another user.
-     * <p>
-     * Example:
-     * <p>
-     * Provided DN: {@code jdoe><alopresto} -> {@code <jdoe><alopresto><proxy...>} would allow the user to impersonate jdoe
-     *
-     * @param rawDn the unsanitized DN
-     * @return the sanitized DN
-     */
-    private static String sanitizeDn(String rawDn) {
-        if (StringUtils.isEmpty(rawDn)) {
-            return rawDn;
-        } else {
-            String sanitizedDn = rawDn.replaceAll(GT, ESCAPED_GT).replaceAll(LT, ESCAPED_LT);
-            if (!sanitizedDn.equals(rawDn)) {
-                logger.warn("The provided DN [" + rawDn + "] contained dangerous characters that were escaped to [" + sanitizedDn + "]");
-            }
-            return sanitizedDn;
-        }
-    }
-
-    /**
-     * Reconstitutes the original DN from the sanitized version passed in the proxy chain.
-     * <p>
-     * Example:
-     * <p>
-     * {@code alopresto\>\<proxy1} -> {@code alopresto><proxy1}
-     *
-     * @param sanitizedDn the sanitized DN
-     * @return the original DN
-     */
-    private static String unsanitizeDn(String sanitizedDn) {
-        if (StringUtils.isEmpty(sanitizedDn)) {
-            return sanitizedDn;
-        } else {
-            String unsanitizedDn = sanitizedDn.replaceAll(ESCAPED_GT, GT).replaceAll(ESCAPED_LT, LT);
-            if (!unsanitizedDn.equals(sanitizedDn)) {
-                logger.warn("The provided DN [" + sanitizedDn + "] had been escaped, and was reconstituted to the dangerous DN [" + unsanitizedDn + "]");
-            }
-            return unsanitizedDn;
-        }
-    }
-
-    /**
-     * Tokenizes the specified proxy chain.
-     *
-     * @param rawProxyChain raw chain
-     * @return tokenized proxy chain
-     */
-    public static List<String> tokenizeProxiedEntitiesChain(String rawProxyChain) {
-        final List<String> proxyChain = new ArrayList<>();
-        if (!StringUtils.isEmpty(rawProxyChain)) {
-            // Split the String on the >< token
-            List<String> elements = Arrays.asList(StringUtils.splitByWholeSeparatorPreserveAllTokens(rawProxyChain, "><"));
-
-            // Unsanitize each DN and collect back
-            elements = elements.stream().map(ProxiedEntitiesUtils::unsanitizeDn).collect(Collectors.toList());
-
-            // Remove the leading < from the first element
-            elements.set(0, elements.get(0).replaceFirst(LT, ""));
-
-            // Remove the trailing > from the last element
-            int last = elements.size() - 1;
-            String lastElement = elements.get(last);
-            if (lastElement.endsWith(GT)) {
-                elements.set(last, lastElement.substring(0, lastElement.length() - 1));
-            }
-
-            proxyChain.addAll(elements);
-        }
-
-        return proxyChain;
-    }
-
-    /**
-     * Builds the proxy chain for the specified user.
-     *
-     * @param user The current user
-     * @return The proxy chain for that user in String form
-     */
-    public static String buildProxiedEntitiesChainString(final NiFiUser user) {
-        // calculate the dn chain
-        List<String> proxyChain = NiFiUserUtils.buildProxiedEntitiesChain(user);
-        if (proxyChain.isEmpty()) {
-            return ANONYMOUS_CHAIN;
-        }
-        proxyChain = proxyChain.stream().map(ProxiedEntitiesUtils::formatProxyDn).collect(Collectors.toList());
-        return StringUtils.join(proxyChain, "");
-    }
-
-    public static void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
-        if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
-            response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString());
-        }
-    }
-
-    public static void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
-        if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
-            response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/UntrustedProxyException.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/UntrustedProxyException.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/UntrustedProxyException.java
deleted file mode 100644
index fad8cc1..0000000
--- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/UntrustedProxyException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.web.security;
-
-import org.springframework.security.core.AuthenticationException;
-
-/**
- *
- */
-public class UntrustedProxyException extends AuthenticationException {
-
-    public UntrustedProxyException(String msg) {
-        super(msg);
-    }
-
-    public UntrustedProxyException(String msg, Throwable t) {
-        super(msg, t);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/LoginIdentityProviderFactory.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/LoginIdentityProviderFactory.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/LoginIdentityProviderFactory.java
new file mode 100644
index 0000000..434d881
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/LoginIdentityProviderFactory.java
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.authentication.LoginIdentityProvider;
+import org.apache.nifi.registry.security.authentication.LoginIdentityProviderConfigurationContext;
+import org.apache.nifi.registry.security.authentication.LoginIdentityProviderLookup;
+import org.apache.nifi.registry.security.authentication.annotation.LoginIdentityProviderContext;
+import org.apache.nifi.registry.extension.ExtensionManager;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.security.authentication.generated.IdentityProviders;
+import org.apache.nifi.registry.security.authentication.generated.Property;
+import org.apache.nifi.registry.security.authentication.generated.Provider;
+import org.apache.nifi.registry.security.util.XmlUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class LoginIdentityProviderFactory implements LoginIdentityProviderLookup, DisposableBean {
+
+    private static final Logger logger = LoggerFactory.getLogger(LoginIdentityProviderFactory.class);
+    private static final String LOGIN_IDENTITY_PROVIDERS_XSD = "/identity-providers.xsd";
+    private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.security.authentication.generated";
+    private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext();
+
+    private static JAXBContext initializeJaxbContext() {
+        try {
+            return JAXBContext.newInstance(JAXB_GENERATED_PATH, LoginIdentityProviderFactory.class.getClassLoader());
+        } catch (JAXBException e) {
+            throw new RuntimeException("Unable to create JAXBContext.");
+        }
+    }
+
+    private NiFiRegistryProperties properties;
+    private ExtensionManager extensionManager;
+    private LoginIdentityProvider loginIdentityProvider;
+    private final Map<String, LoginIdentityProvider> loginIdentityProviders = new HashMap<>();
+
+    @Autowired
+    public LoginIdentityProviderFactory(final NiFiRegistryProperties properties, final ExtensionManager extensionManager) {
+        this.properties = properties;
+        this.extensionManager = extensionManager;
+
+        if (this.properties == null) {
+            throw new IllegalStateException("NiFiRegistryProperties cannot be null");
+        }
+
+        if (this.extensionManager == null) {
+            throw new IllegalStateException("ExtensionManager cannot be null");
+        }
+    }
+
+    @Override
+    public LoginIdentityProvider getLoginIdentityProvider(String identifier) {
+        return loginIdentityProviders.get(identifier);
+    }
+
+    @Bean
+    public LoginIdentityProvider getLoginIdentityProvider() throws Exception {
+        if (loginIdentityProvider == null) {
+            // look up the login identity provider to use
+            final String loginIdentityProviderIdentifier = properties.getProperty(NiFiRegistryProperties.SECURITY_IDENTITY_PROVIDER);
+
+            // ensure the login identity provider class name was specified
+            if (StringUtils.isNotBlank(loginIdentityProviderIdentifier)) {
+                final IdentityProviders loginIdentityProviderConfiguration = loadLoginIdentityProvidersConfiguration();
+
+                // create each login identity provider
+                for (final Provider provider : loginIdentityProviderConfiguration.getProvider()) {
+                    loginIdentityProviders.put(provider.getIdentifier(), createLoginIdentityProvider(provider.getIdentifier(), provider.getClazz()));
+                }
+
+                // configure each login identity provider
+                for (final Provider provider : loginIdentityProviderConfiguration.getProvider()) {
+                    final LoginIdentityProvider instance = loginIdentityProviders.get(provider.getIdentifier());
+                    instance.onConfigured(loadLoginIdentityProviderConfiguration(provider));
+                }
+
+                // get the login identity provider instance
+                loginIdentityProvider = getLoginIdentityProvider(loginIdentityProviderIdentifier);
+
+                // ensure it was found
+                if (loginIdentityProvider == null) {
+                    throw new Exception(String.format("The specified login identity provider '%s' could not be found.", loginIdentityProviderIdentifier));
+                }
+            }
+        }
+
+        return loginIdentityProvider;
+    }
+
+    @Override
+    public void destroy() throws Exception {
+        if (loginIdentityProviders != null) {
+            loginIdentityProviders.entrySet().stream().forEach(e -> e.getValue().preDestruction());
+        }
+    }
+
+    private IdentityProviders loadLoginIdentityProvidersConfiguration() throws Exception {
+        final File loginIdentityProvidersConfigurationFile = properties.getIdentityProviderConfigurationFile();
+
+        // load the users from the specified file
+        if (loginIdentityProvidersConfigurationFile.exists()) {
+            try {
+                // find the schema
+                final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+                final Schema schema = schemaFactory.newSchema(IdentityProviders.class.getResource(LOGIN_IDENTITY_PROVIDERS_XSD));
+
+                // attempt to unmarshal
+                XMLStreamReader xsr = XmlUtils.createSafeReader(new StreamSource(loginIdentityProvidersConfigurationFile));
+                final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
+                unmarshaller.setSchema(schema);
+                final JAXBElement<IdentityProviders> element = unmarshaller.unmarshal(xsr, IdentityProviders.class);
+                return element.getValue();
+            } catch (SAXException | JAXBException e) {
+                throw new Exception("Unable to load the login identity provider configuration file at: " + loginIdentityProvidersConfigurationFile.getAbsolutePath());
+            }
+        } else {
+            throw new Exception("Unable to find the login identity provider configuration file at " + loginIdentityProvidersConfigurationFile.getAbsolutePath());
+        }
+    }
+
+    private LoginIdentityProvider createLoginIdentityProvider(final String identifier, final String loginIdentityProviderClassName) throws Exception {
+        final LoginIdentityProvider instance;
+
+        final ClassLoader classLoader = extensionManager.getExtensionClassLoader(loginIdentityProviderClassName);
+        if (classLoader == null) {
+            throw new IllegalStateException("Extension not found in any of the configured class loaders: " + loginIdentityProviderClassName);
+        }
+
+        // attempt to load the class
+        Class<?> rawLoginIdentityProviderClass = Class.forName(loginIdentityProviderClassName, true, classLoader);
+        Class<? extends LoginIdentityProvider> loginIdentityProviderClass = rawLoginIdentityProviderClass.asSubclass(LoginIdentityProvider.class);
+
+        // otherwise create a new instance
+        Constructor constructor = loginIdentityProviderClass.getConstructor();
+        instance = (LoginIdentityProvider) constructor.newInstance();
+
+        // method injection
+        performMethodInjection(instance, loginIdentityProviderClass);
+
+        // field injection
+        performFieldInjection(instance, loginIdentityProviderClass);
+
+        // call post construction lifecycle event
+        instance.initialize(new StandardLoginIdentityProviderInitializationContext(identifier, this));
+
+        return instance;
+    }
+
+    private LoginIdentityProviderConfigurationContext loadLoginIdentityProviderConfiguration(final Provider provider) {
+        final Map<String, String> providerProperties = new HashMap<>();
+
+        for (final Property property : provider.getProperty()) {
+            providerProperties.put(property.getName(), property.getValue());
+        }
+
+        return new StandardLoginIdentityProviderConfigurationContext(provider.getIdentifier(), providerProperties);
+    }
+
+    private void performMethodInjection(final LoginIdentityProvider instance, final Class loginIdentityProviderClass)
+            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+        for (final Method method : loginIdentityProviderClass.getMethods()) {
+            if (method.isAnnotationPresent(LoginIdentityProviderContext.class)) {
+                // make the method accessible
+                final boolean isAccessible = method.isAccessible();
+                method.setAccessible(true);
+
+                try {
+                    final Class<?>[] argumentTypes = method.getParameterTypes();
+
+                    // look for setters (single argument)
+                    if (argumentTypes.length == 1) {
+                        final Class<?> argumentType = argumentTypes[0];
+
+                        // look for well known types
+                        if (NiFiRegistryProperties.class.isAssignableFrom(argumentType)) {
+                            // nifi properties injection
+                            method.invoke(instance, properties);
+                        }
+                    }
+                } finally {
+                    method.setAccessible(isAccessible);
+                }
+            }
+        }
+
+        final Class parentClass = loginIdentityProviderClass.getSuperclass();
+        if (parentClass != null && LoginIdentityProvider.class.isAssignableFrom(parentClass)) {
+            performMethodInjection(instance, parentClass);
+        }
+    }
+
+    private void performFieldInjection(final LoginIdentityProvider instance, final Class loginIdentityProviderClass) throws IllegalArgumentException, IllegalAccessException {
+        for (final Field field : loginIdentityProviderClass.getDeclaredFields()) {
+            if (field.isAnnotationPresent(LoginIdentityProviderContext.class)) {
+                // make the method accessible
+                final boolean isAccessible = field.isAccessible();
+                field.setAccessible(true);
+
+                try {
+                    // get the type
+                    final Class<?> fieldType = field.getType();
+
+                    // only consider this field if it isn't set yet
+                    if (field.get(instance) == null) {
+                        // look for well known types
+                        if (NiFiRegistryProperties.class.isAssignableFrom(fieldType)) {
+                            // nifi properties injection
+                            field.set(instance, properties);
+                        }
+                    }
+
+                } finally {
+                    field.setAccessible(isAccessible);
+                }
+            }
+        }
+
+        final Class parentClass = loginIdentityProviderClass.getSuperclass();
+        if (parentClass != null && LoginIdentityProvider.class.isAssignableFrom(parentClass)) {
+            performFieldInjection(instance, parentClass);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAnonymousUserFilter.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAnonymousUserFilter.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAnonymousUserFilter.java
new file mode 100644
index 0000000..a27fd18
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAnonymousUserFilter.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails;
+import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
+import org.apache.nifi.registry.web.security.authentication.token.NiFiAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter {
+
+    private static final String ANONYMOUS_KEY = "anonymousNifiKey";
+
+    public NiFiAnonymousUserFilter() {
+        super(ANONYMOUS_KEY);
+    }
+
+    @Override
+    protected Authentication createAuthentication(HttpServletRequest request) {
+        return new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAuthenticationFilter.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAuthenticationFilter.java
new file mode 100644
index 0000000..24af504
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAuthenticationFilter.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.web.security.authentication.exception.InvalidAuthenticationException;
+import org.apache.nifi.registry.web.security.authentication.exception.UntrustedProxyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.GenericFilterBean;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ *
+ */
+public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
+
+    private static final Logger log = LoggerFactory.getLogger(NiFiAuthenticationFilter.class);
+
+    private AuthenticationManager authenticationManager;
+    private NiFiRegistryProperties properties;
+
+    @Override
+    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (log.isDebugEnabled()) {
+            log.debug("Checking secure context token: " + authentication);
+        }
+
+        if (requiresAuthentication((HttpServletRequest) request)) {
+            authenticate((HttpServletRequest) request, (HttpServletResponse) response, chain);
+        } else {
+            chain.doFilter(request, response);
+        }
+
+    }
+
+    private boolean requiresAuthentication(final HttpServletRequest request) {
+        return NiFiUserUtils.getNiFiUser() == null;
+    }
+
+    private void authenticate(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
+        String dnChain = null;
+        try {
+            final Authentication authenticationRequest = attemptAuthentication(request);
+            if (authenticationRequest != null) {
+                // log the request attempt - response details will be logged later
+                log.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", authenticationRequest.toString(), request.getMethod(),
+                        request.getRequestURL().toString(), request.getRemoteAddr()));
+
+                // attempt to authorize the user
+                final Authentication authenticated = authenticationManager.authenticate(authenticationRequest);
+                successfulAuthorization(request, response, authenticated);
+            }
+
+            // continue
+            chain.doFilter(request, response);
+        } catch (final AuthenticationException ae) {
+            // invalid authentication - always error out
+            unsuccessfulAuthorization(request, response, ae);
+        }
+    }
+
+    /**
+     * Attempt to extract an authentication attempt from the specified request.
+     *
+     * @param request The request
+     * @return The authentication attempt or null if none is found int he request
+     */
+    public abstract Authentication attemptAuthentication(HttpServletRequest request);
+
+    protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
+        log.info("Authentication success for " + authResult);
+
+        SecurityContextHolder.getContext().setAuthentication(authResult);
+        ProxiedEntitiesUtils.successfulAuthorization(request, response, authResult);
+    }
+
+    protected void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException {
+        // populate the response
+        ProxiedEntitiesUtils.unsuccessfulAuthorization(request, response, ae);
+
+        // set the response status
+        response.setContentType("text/plain");
+
+        // write the response message
+        PrintWriter out = response.getWriter();
+
+        // use the type of authentication exception to determine the response code
+        if (ae instanceof InvalidAuthenticationException) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            out.println(ae.getMessage());
+        } else if (ae instanceof UntrustedProxyException) {
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            out.println(ae.getMessage());
+        } else if (ae instanceof AuthenticationServiceException) {
+            log.error(String.format("Unable to authorize: %s", ae.getMessage()), ae);
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            out.println(String.format("Unable to authorize: %s", ae.getMessage()));
+        } else {
+            log.error(String.format("Unable to authorize: %s", ae.getMessage()), ae);
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            out.println("Access is denied.");
+        }
+
+        // log the failure
+        log.warn(String.format("Rejecting access to web api: %s", ae.getMessage()));
+
+        // optionally log the stack trace
+        if (log.isDebugEnabled()) {
+            log.debug(StringUtils.EMPTY, ae);
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
+        this.authenticationManager = authenticationManager;
+    }
+
+    public void setProperties(NiFiRegistryProperties properties) {
+        this.properties = properties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAuthenticationProvider.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAuthenticationProvider.java
new file mode 100644
index 0000000..5617e0e
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/NiFiAuthenticationProvider.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.ManagedAuthorizer;
+import org.apache.nifi.registry.security.authorization.UserAndGroups;
+import org.apache.nifi.registry.security.authorization.UserGroupProvider;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.properties.util.IdentityMapping;
+import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationProvider;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Base AuthenticationProvider that provides common functionality to mapping identities.
+ */
+public abstract class NiFiAuthenticationProvider implements AuthenticationProvider {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(NiFiAuthenticationProvider.class);
+
+    private NiFiRegistryProperties properties;
+    private Authorizer authorizer;
+    private List<IdentityMapping> mappings;
+
+    /**
+     * @param properties the NiFiProperties instance
+     */
+    public NiFiAuthenticationProvider(final NiFiRegistryProperties properties, final Authorizer authorizer) {
+        this.properties = properties;
+        this.mappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+        this.authorizer = authorizer;
+    }
+
+    public List<IdentityMapping> getMappings() {
+        return mappings;
+    }
+
+    protected String mapIdentity(final String identity) {
+        return IdentityMappingUtil.mapIdentity(identity, mappings);
+    }
+
+    protected Set<String> getUserGroups(final String identity) {
+        return getUserGroups(authorizer, identity);
+    }
+
+    protected static Set<String> getUserGroups(final Authorizer authorizer, final String userIdentity) {
+        if (authorizer instanceof ManagedAuthorizer) {
+            final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
+            final UserGroupProvider userGroupProvider = managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
+            final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(userIdentity);
+            final Set<Group> userGroups = userAndGroups.getGroups();
+
+            if (userGroups == null || userGroups.isEmpty()) {
+                return Collections.EMPTY_SET;
+            } else {
+                return userAndGroups.getGroups().stream().map(group -> group.getName()).collect(Collectors.toSet());
+            }
+        } else {
+            return null;
+        }
+    }
+}

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

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/ProxiedEntitiesUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/ProxiedEntitiesUtils.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/ProxiedEntitiesUtils.java
new file mode 100644
index 0000000..05687f8
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/ProxiedEntitiesUtils.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.authorization.user.NiFiUser;
+import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ */
+public class ProxiedEntitiesUtils {
+    private static final Logger logger = LoggerFactory.getLogger(ProxiedEntitiesUtils.class);
+
+    public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain";
+    public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted";
+    public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails";
+
+    private static final String GT = ">";
+    private static final String ESCAPED_GT = "\\\\>";
+    private static final String LT = "<";
+    private static final String ESCAPED_LT = "\\\\<";
+
+    private static final String ANONYMOUS_CHAIN = "<>";
+
+    /**
+     * Formats the specified DN to be set as a HTTP header using well known conventions.
+     *
+     * @param dn raw dn
+     * @return the dn formatted as an HTTP header
+     */
+    public static String formatProxyDn(String dn) {
+        return LT + sanitizeDn(dn) + GT;
+    }
+
+    /**
+     * If a user provides a DN with the sequence '><', they could escape the tokenization process and impersonate another user.
+     * <p>
+     * Example:
+     * <p>
+     * Provided DN: {@code jdoe><alopresto} -> {@code <jdoe><alopresto><proxy...>} would allow the user to impersonate jdoe
+     *
+     * @param rawDn the unsanitized DN
+     * @return the sanitized DN
+     */
+    private static String sanitizeDn(String rawDn) {
+        if (StringUtils.isEmpty(rawDn)) {
+            return rawDn;
+        } else {
+            String sanitizedDn = rawDn.replaceAll(GT, ESCAPED_GT).replaceAll(LT, ESCAPED_LT);
+            if (!sanitizedDn.equals(rawDn)) {
+                logger.warn("The provided DN [" + rawDn + "] contained dangerous characters that were escaped to [" + sanitizedDn + "]");
+            }
+            return sanitizedDn;
+        }
+    }
+
+    /**
+     * Reconstitutes the original DN from the sanitized version passed in the proxy chain.
+     * <p>
+     * Example:
+     * <p>
+     * {@code alopresto\>\<proxy1} -> {@code alopresto><proxy1}
+     *
+     * @param sanitizedDn the sanitized DN
+     * @return the original DN
+     */
+    private static String unsanitizeDn(String sanitizedDn) {
+        if (StringUtils.isEmpty(sanitizedDn)) {
+            return sanitizedDn;
+        } else {
+            String unsanitizedDn = sanitizedDn.replaceAll(ESCAPED_GT, GT).replaceAll(ESCAPED_LT, LT);
+            if (!unsanitizedDn.equals(sanitizedDn)) {
+                logger.warn("The provided DN [" + sanitizedDn + "] had been escaped, and was reconstituted to the dangerous DN [" + unsanitizedDn + "]");
+            }
+            return unsanitizedDn;
+        }
+    }
+
+    /**
+     * Tokenizes the specified proxy chain.
+     *
+     * @param rawProxyChain raw chain
+     * @return tokenized proxy chain
+     */
+    public static List<String> tokenizeProxiedEntitiesChain(String rawProxyChain) {
+        final List<String> proxyChain = new ArrayList<>();
+        if (!StringUtils.isEmpty(rawProxyChain)) {
+            // Split the String on the >< token
+            List<String> elements = Arrays.asList(StringUtils.splitByWholeSeparatorPreserveAllTokens(rawProxyChain, "><"));
+
+            // Unsanitize each DN and collect back
+            elements = elements.stream().map(ProxiedEntitiesUtils::unsanitizeDn).collect(Collectors.toList());
+
+            // Remove the leading < from the first element
+            elements.set(0, elements.get(0).replaceFirst(LT, ""));
+
+            // Remove the trailing > from the last element
+            int last = elements.size() - 1;
+            String lastElement = elements.get(last);
+            if (lastElement.endsWith(GT)) {
+                elements.set(last, lastElement.substring(0, lastElement.length() - 1));
+            }
+
+            proxyChain.addAll(elements);
+        }
+
+        return proxyChain;
+    }
+
+    /**
+     * Builds the proxy chain for the specified user.
+     *
+     * @param user The current user
+     * @return The proxy chain for that user in String form
+     */
+    public static String buildProxiedEntitiesChainString(final NiFiUser user) {
+        // calculate the dn chain
+        List<String> proxyChain = NiFiUserUtils.buildProxiedEntitiesChain(user);
+        if (proxyChain.isEmpty()) {
+            return ANONYMOUS_CHAIN;
+        }
+        proxyChain = proxyChain.stream().map(ProxiedEntitiesUtils::formatProxyDn).collect(Collectors.toList());
+        return StringUtils.join(proxyChain, "");
+    }
+
+    public static void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
+        if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
+            response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString());
+        }
+    }
+
+    public static void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
+        if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
+            response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/StandardLoginIdentityProviderConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/StandardLoginIdentityProviderConfigurationContext.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/StandardLoginIdentityProviderConfigurationContext.java
new file mode 100644
index 0000000..3a9cdd6
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/StandardLoginIdentityProviderConfigurationContext.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.nifi.registry.security.authentication.LoginIdentityProviderConfigurationContext;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ *
+ */
+public class StandardLoginIdentityProviderConfigurationContext implements LoginIdentityProviderConfigurationContext {
+
+    private final String identifier;
+    private final Map<String, String> properties;
+
+    public StandardLoginIdentityProviderConfigurationContext(String identifier, Map<String, String> properties) {
+        this.identifier = identifier;
+        this.properties = properties;
+    }
+
+    @Override
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+        return Collections.unmodifiableMap(properties);
+    }
+
+    @Override
+    public String getProperty(String property) {
+        return properties.get(property);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/StandardLoginIdentityProviderInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/StandardLoginIdentityProviderInitializationContext.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/StandardLoginIdentityProviderInitializationContext.java
new file mode 100644
index 0000000..e8fba2e
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/StandardLoginIdentityProviderInitializationContext.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.nifi.registry.security.authentication.LoginIdentityProviderInitializationContext;
+import org.apache.nifi.registry.security.authentication.LoginIdentityProviderLookup;
+
+/**
+ *
+ */
+public class StandardLoginIdentityProviderInitializationContext implements LoginIdentityProviderInitializationContext {
+
+    private final String identifier;
+    private final LoginIdentityProviderLookup lookup;
+
+    public StandardLoginIdentityProviderInitializationContext(String identifier, final LoginIdentityProviderLookup lookup) {
+        this.identifier = identifier;
+        this.lookup = lookup;
+    }
+
+    @Override
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public LoginIdentityProviderLookup getAuthorityProviderLookup() {
+        return lookup;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/InvalidAuthenticationException.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/InvalidAuthenticationException.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/InvalidAuthenticationException.java
new file mode 100644
index 0000000..016e9cb
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/InvalidAuthenticationException.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.exception;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * Thrown if the authentication of a given request is invalid. For instance,
+ * an expired certificate or token.
+ */
+public class InvalidAuthenticationException extends AuthenticationException {
+
+    public InvalidAuthenticationException(String msg) {
+        super(msg);
+    }
+
+    public InvalidAuthenticationException(String msg, Throwable t) {
+        super(msg, t);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java
new file mode 100644
index 0000000..6245ac2
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.exception;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ *
+ */
+public class UntrustedProxyException extends AuthenticationException {
+
+    public UntrustedProxyException(String msg) {
+        super(msg);
+    }
+
+    public UntrustedProxyException(String msg, Throwable t) {
+        super(msg, t);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtAuthenticationFilter.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..1e5c194
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtAuthenticationFilter.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.jwt;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.apache.nifi.registry.web.security.authentication.NiFiAuthenticationFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ */
+public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
+
+    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
+
+    public static final String AUTHORIZATION = "Authorization";
+    public static final String BEARER = "Bearer ";
+
+    @Override
+    public Authentication attemptAuthentication(final HttpServletRequest request) {
+        // only support jwt login when running securely
+        if (!request.isSecure()) {
+            return null;
+        }
+
+        // TODO: Refactor request header extraction logic to shared utility as it is duplicated in AccessResource
+
+        // get the principal out of the user token
+        final String authorization = request.getHeader(AUTHORIZATION);
+
+        // if there is no authorization header, we don't know the user
+        if (authorization == null || !StringUtils.startsWith(authorization, BEARER)) {
+            return null;
+        } else {
+            // Extract the Base64 encoded token from the Authorization header
+            final String token = StringUtils.substringAfterLast(authorization, " ");
+            return new JwtAuthenticationRequestToken(token, request.getRemoteAddr());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtAuthenticationProvider.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtAuthenticationProvider.java
new file mode 100644
index 0000000..2247923
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtAuthenticationProvider.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.jwt;
+
+import io.jsonwebtoken.JwtException;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.user.NiFiUser;
+import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails;
+import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.web.security.authentication.exception.InvalidAuthenticationException;
+import org.apache.nifi.registry.web.security.authentication.NiFiAuthenticationProvider;
+import org.apache.nifi.registry.web.security.authentication.token.NiFiAuthenticationToken;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ */
+@Component
+public class JwtAuthenticationProvider extends NiFiAuthenticationProvider {
+
+    private final JwtService jwtService;
+
+    @Autowired
+    public JwtAuthenticationProvider(JwtService jwtService, NiFiRegistryProperties nifiProperties, Authorizer authorizer) {
+        super(nifiProperties, authorizer);
+        this.jwtService = jwtService;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        final JwtAuthenticationRequestToken request = (JwtAuthenticationRequestToken) authentication;
+
+        try {
+            final String jwtPrincipal = jwtService.getAuthenticationFromToken(request.getToken());
+            final String mappedIdentity = mapIdentity(jwtPrincipal);
+            final NiFiUser user = new StandardNiFiUser.Builder()
+                    .identity(mappedIdentity)
+                    .groups(getUserGroups(mappedIdentity))
+                    .clientAddress(request.getClientAddress())
+                    .build();
+            return new NiFiAuthenticationToken(new NiFiUserDetails(user));
+        } catch (JwtException e) {
+            throw new InvalidAuthenticationException(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return JwtAuthenticationRequestToken.class.isAssignableFrom(authentication);
+    }
+}

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

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
new file mode 100644
index 0000000..49c17ea
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.jwt;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwsHeader;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.SigningKeyResolverAdapter;
+import io.jsonwebtoken.UnsupportedJwtException;
+import org.apache.commons.lang3.StringUtils;
+
+import org.apache.nifi.registry.exception.AdministrationException;
+import org.apache.nifi.registry.security.key.Key;
+import org.apache.nifi.registry.security.key.KeyService;
+import org.apache.nifi.registry.web.security.authentication.token.LoginAuthenticationToken;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
+
+@Service
+public class JwtService {
+
+    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtService.class);
+
+    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
+    private static final String KEY_ID_CLAIM = "kid";
+    private static final String USERNAME_CLAIM = "preferred_username";
+
+    private final KeyService keyService;
+
+    public JwtService(final KeyService keyService) {
+        this.keyService = keyService;
+    }
+
+    public String getAuthenticationFromToken(final String base64EncodedToken) throws JwtException {
+        // The library representations of the JWT should be kept internal to this service.
+        try {
+            final Jws<Claims> jws = parseTokenFromBase64EncodedString(base64EncodedToken);
+
+            if (jws == null) {
+                throw new JwtException("Unable to parse token");
+            }
+
+            // Additional validation that subject is present
+            if (StringUtils.isEmpty(jws.getBody().getSubject())) {
+                throw new JwtException("No subject available in token");
+            }
+
+            // TODO: Validate issuer against active registry?
+            if (StringUtils.isEmpty(jws.getBody().getIssuer())) {
+                throw new JwtException("No issuer available in token");
+            }
+            return jws.getBody().getSubject();
+        } catch (JwtException e) {
+            logger.debug("The Base64 encoded JWT: " + base64EncodedToken);
+            final String errorMessage = "There was an error validating the JWT";
+            logger.error(errorMessage, e);
+            throw e;
+        }
+    }
+
+    private Jws<Claims> parseTokenFromBase64EncodedString(final String base64EncodedToken) throws JwtException {
+        try {
+            return Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
+                @Override
+                public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
+                    final String identity = claims.getSubject();
+
+                    // Get the key based on the key id in the claims
+                    final String keyId = claims.get(KEY_ID_CLAIM, String.class);
+                    final Key key = keyService.getKey(keyId);
+
+                    // Ensure we were able to find a key that was previously issued by this key service for this user
+                    if (key == null || key.getKey() == null) {
+                        throw new UnsupportedJwtException("Unable to determine signing key for " + identity + " [kid: " + keyId + "]");
+                    }
+
+                    return key.getKey().getBytes(StandardCharsets.UTF_8);
+                }
+            }).parseClaimsJws(base64EncodedToken);
+        } catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException | AdministrationException e) {
+            // TODO: Exercise all exceptions to ensure none leak key material to logs
+            final String errorMessage = "Unable to validate the access token.";
+            throw new JwtException(errorMessage, e);
+        }
+    }
+
+    /**
+     * Generates a signed JWT token from the provided (Spring Security) login authentication token.
+     *
+     * @param authenticationToken an instance of the Spring Security token after login credentials have been verified against the respective information source
+     * @return a signed JWT containing the user identity and the identity provider, Base64-encoded
+     * @throws JwtException if there is a problem generating the signed token
+     */
+    public String generateSignedToken(final LoginAuthenticationToken authenticationToken) throws JwtException {
+        if (authenticationToken == null) {
+            throw new IllegalArgumentException("Cannot generate a JWT for a null authentication token");
+        }
+
+        // Set expiration from the token
+        final Calendar expiration = Calendar.getInstance();
+        expiration.setTimeInMillis(authenticationToken.getExpiration());
+
+        final Object principal = authenticationToken.getPrincipal();
+        if (principal == null || StringUtils.isEmpty(principal.toString())) {
+            final String errorMessage = "Cannot generate a JWT for a token with an empty identity issued by " + authenticationToken.getIssuer();
+            logger.error(errorMessage);
+            throw new JwtException(errorMessage);
+        }
+
+        // Create a JWT with the specified authentication
+        final String identity = principal.toString();
+        final String username = authenticationToken.getName();
+
+        try {
+            // Get/create the key for this user
+            final Key key = keyService.getOrCreateKey(identity);
+            final byte[] keyBytes = key.getKey().getBytes(StandardCharsets.UTF_8);
+
+            logger.trace("Generating JWT for " + authenticationToken);
+
+            // TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
+            // Build the token
+            return Jwts.builder().setSubject(identity)
+                    .setIssuer(authenticationToken.getIssuer())
+                    .setAudience(authenticationToken.getIssuer())
+                    .claim(USERNAME_CLAIM, username)
+                    .claim(KEY_ID_CLAIM, key.getId())
+                    .setExpiration(expiration.getTime())
+                    .setIssuedAt(Calendar.getInstance().getTime())
+                    .signWith(SIGNATURE_ALGORITHM, keyBytes).compact();
+        } catch (NullPointerException | AdministrationException e) {
+            final String errorMessage = "Could not retrieve the signing key for JWT for " + identity;
+            logger.error(errorMessage, e);
+            throw new JwtException(errorMessage, e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/90f36dd2/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/LoginAuthenticationToken.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/LoginAuthenticationToken.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/LoginAuthenticationToken.java
new file mode 100644
index 0000000..08f0637
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/LoginAuthenticationToken.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.token;
+
+import org.apache.nifi.registry.security.util.CertificateUtils;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+/**
+ * This is an Authentication Token for logging in. Once a user is authenticated, they can be issued an ID token.
+ */
+public class LoginAuthenticationToken extends AbstractAuthenticationToken {
+
+    private final String identity;
+    private final String username;
+    private final long expiration;
+    private final String issuer;
+
+    /**
+     * Creates a representation of the authentication token for a user.
+     *
+     * @param identity   The unique identifier for this user
+     * @param expiration The relative time to expiration in milliseconds
+     * @param issuer     The IdentityProvider implementation that generated this token
+     */
+    public LoginAuthenticationToken(final String identity, final long expiration, final String issuer) {
+        this(identity, null, expiration, issuer);
+    }
+
+    /**
+     * Creates a representation of the authentication token for a user.
+     *
+     * @param identity   The unique identifier for this user (cannot be null or empty)
+     * @param username   The preferred username for this user
+     * @param expiration The relative time to expiration in milliseconds
+     * @param issuer     The IdentityProvider implementation that generated this token
+     */
+    public LoginAuthenticationToken(final String identity, final String username, final long expiration, final String issuer) {
+        super(null);
+        setAuthenticated(true);
+        this.identity = identity;
+        this.username = username;
+        this.issuer = issuer;
+        Calendar now = Calendar.getInstance();
+        this.expiration = now.getTimeInMillis() + expiration;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return null;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return identity;
+    }
+
+    /**
+     * Returns the expiration instant in milliseconds. This value is an absolute point in time (i.e. Nov
+     * 16, 2015 11:30:00.000 GMT), not a relative time (i.e. 60 minutes). It is calculated by adding the
+     * relative expiration from the constructor to the timestamp at object creation.
+     *
+     * @return the expiration in millis
+     */
+    public long getExpiration() {
+        return expiration;
+    }
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    @Override
+    public String getName() {
+        if (username == null) {
+            // if the username is a DN this will extract the username or CN... if not will return what was passed
+            return CertificateUtils.extractUsername(identity);
+        } else {
+            return username;
+        }
+    }
+
+    @Override
+    public String toString() {
+        Calendar expirationTime = Calendar.getInstance();
+        expirationTime.setTimeInMillis(getExpiration());
+        long remainingTime = expirationTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis();
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS");
+        dateFormat.setTimeZone(expirationTime.getTimeZone());
+        String expirationTimeString = dateFormat.format(expirationTime.getTime());
+
+        return new StringBuilder("LoginAuthenticationToken for ")
+                .append(getName())
+                .append(" issued by ")
+                .append(getIssuer())
+                .append(" expiring at ")
+                .append(expirationTimeString)
+                .append(" [")
+                .append(getExpiration())
+                .append(" ms, ")
+                .append(remainingTime)
+                .append(" ms remaining]")
+                .toString();
+    }
+
+}