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

nifi git commit: NIFI-655: - Starting to implement the JWT service. - Parsing JWT on client side in order to render who the user currently is when logged in.

Repository: nifi
Updated Branches:
  refs/heads/NIFI-655 93aa09dac -> b6d09b86b


NIFI-655:
- Starting to implement the JWT service.
- Parsing JWT on client side in order to render who the user currently is when logged in.

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

Branch: refs/heads/NIFI-655
Commit: b6d09b86b6b0b6873dda192d28e704653ac9522d
Parents: 93aa09d
Author: Matt Gilman <ma...@gmail.com>
Authored: Thu Nov 5 18:26:00 2015 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Thu Nov 5 18:26:00 2015 -0500

----------------------------------------------------------------------
 LICENSE                                         |  22 ++
 nifi-assembly/LICENSE                           |  22 ++
 .../nifi-web/nifi-web-security/pom.xml          |   5 +
 .../web/security/RegistrationStatusFilter.java  |   8 +-
 .../form/LoginAuthenticationFilter.java         |  26 +-
 .../nifi/web/security/jwt/JwtService.java       |  54 +++-
 .../token/LoginAuthenticationToken.java         |   8 +
 .../resources/nifi-web-security-context.xml     |   4 +-
 .../src/main/resources/META-INF/LICENSE         |  22 ++
 .../src/main/webapp/WEB-INF/pages/login.jsp     |   1 +
 .../WEB-INF/partials/canvas/canvas-header.jsp   |   4 +-
 .../partials/login/nifi-registration-form.jsp   |  12 +-
 .../nifi-web-ui/src/main/webapp/css/header.css  |  10 +
 .../nifi-web-ui/src/main/webapp/css/login.css   |  16 +-
 .../src/main/webapp/js/jquery/jquery.base64.js  | 123 +++++++++
 .../src/main/webapp/js/nf/canvas/nf-canvas.js   | 256 +++++++++----------
 .../src/main/webapp/js/nf/login/nf-login.js     | 132 +++++++---
 .../src/main/webapp/js/nf/nf-common.js          |  11 +-
 18 files changed, 522 insertions(+), 214 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index 59741e6..f4be753 100644
--- a/LICENSE
+++ b/LICENSE
@@ -374,6 +374,28 @@ For details see http://jqueryui.com
     OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
+This product bundles 'jquery.base64.js' which is available under an MIT style license.
+
+    Copyright (c) 2013 Yannick Albert (http://yckart.com/)
+
+    Permission is hereby granted, free of charge, to any person obtaining 
+    a copy of this software and associated documentation files (the "Software"), 
+    to deal in the Software without restriction, including without limitation 
+    the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 
+    sell copies of the Software, and to permit persons to whom the Software is 
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be 
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
+    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
+    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
 This product bundles 'SlickGrid v2.2' which is available under an MIT style license.
 
     Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-assembly/LICENSE
----------------------------------------------------------------------
diff --git a/nifi-assembly/LICENSE b/nifi-assembly/LICENSE
index 5abc79a..a7a73b8 100644
--- a/nifi-assembly/LICENSE
+++ b/nifi-assembly/LICENSE
@@ -374,6 +374,28 @@ For details see http://jqueryui.com
     OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
+This product bundles 'jquery.base64.js' which is available under an MIT style license.
+
+    Copyright (c) 2013 Yannick Albert (http://yckart.com/)
+
+    Permission is hereby granted, free of charge, to any person obtaining 
+    a copy of this software and associated documentation files (the "Software"), 
+    to deal in the Software without restriction, including without limitation 
+    the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 
+    sell copies of the Software, and to permit persons to whom the Software is 
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be 
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
+    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
+    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
 This product bundles 'SlickGrid v2.2' which is available under an MIT style license.
 
     Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
index c0db615..c954d0d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
@@ -78,6 +78,11 @@
             <artifactId>nifi-framework-core</artifactId>
         </dependency>
         <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.6.0</version>
+        </dependency>
+        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk16</artifactId>
         </dependency>

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/RegistrationStatusFilter.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/RegistrationStatusFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/RegistrationStatusFilter.java
index e914db5..606d2e3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/RegistrationStatusFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/RegistrationStatusFilter.java
@@ -98,7 +98,7 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi
             return new RegistrationStatusAuthenticationToken(tokenCredentials);
         } else {
             // we have a certificate so let's consider a proxy chain
-            final String principal = extractPrincipal(certificate);
+            final String principal = principalExtractor.extractPrincipal(certificate).toString();
 
             try {
                 // validate the certificate
@@ -144,12 +144,6 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi
         userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain));
     }
 
-    private String extractPrincipal(final X509Certificate certificate) {
-        // extract the principal
-        final Object certificatePrincipal = principalExtractor.extractPrincipal(certificate);
-        return ProxiedEntitiesUtils.formatProxyDn(certificatePrincipal.toString());
-    }
-
     @Override
     protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authentication)
             throws IOException, ServletException {

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.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/form/LoginAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java
index 388b81e..fb45363 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java
@@ -44,7 +44,6 @@ 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.AbstractAuthenticationProcessingFilter;
-import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
 import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
 
 /**
@@ -88,16 +87,16 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
             // if there is no certificate, look for an existing token
             if (certificate == null) {
                 final String principal = jwtService.getAuthentication(request);
-                
+
                 if (principal == null) {
                     throw new AuthenticationCredentialsNotFoundException("Unable to issue token as issue token as no credentials were found in the request.");
                 }
-                
+
                 final LoginCredentials tokenCredentials = new LoginCredentials(principal, null);
                 return new LoginAuthenticationToken(tokenCredentials);
             } else {
                 // extract the principal
-                final String principal = extractPrincipal(certificate);
+                final String principal = principalExtractor.extractPrincipal(certificate).toString();
 
                 try {
                     certificateValidator.validateClientCertificate(request, certificate);
@@ -151,16 +150,14 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
             } 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
+            } catch (final Exception e) {
+                // any other issue we're going to treat as an authentication exception which will return 401
+                throw new AuthenticationException(e.getMessage(), e) {
+                };
             }
         }
     }
 
-    private String extractPrincipal(final X509Certificate certificate) {
-        // extract the principal
-        final Object certificatePrincipal = principalExtractor.extractPrincipal(certificate);
-        return ProxiedEntitiesUtils.formatProxyDn(certificatePrincipal.toString());
-    }
-
     private LoginCredentials getLoginCredentials(HttpServletRequest request) {
         final String username = request.getParameter("username");
         final String password = request.getParameter("password");
@@ -178,20 +175,15 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
 
         // generate JWT for response
         jwtService.addToken(response, authentication);
-
-        // mark as successful
-        response.setStatus(HttpServletResponse.SC_CREATED);
-        response.setContentType("text/plain");
-        response.setContentLength(0);
     }
 
     @Override
     protected void unsuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException failed) throws IOException, ServletException {
         response.setContentType("text/plain");
-        
+
         final PrintWriter out = response.getWriter();
         out.println(failed.getMessage());
-        
+
         if (failed instanceof BadCredentialsException || failed instanceof AuthenticationCredentialsNotFoundException) {
             response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
         } else {

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
index 2012d69..8afa15a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
@@ -16,9 +16,22 @@
  */
 package org.apache.nifi.web.security.jwt;
 
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.UnsupportedJwtException;
+import io.jsonwebtoken.impl.TextCodec;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Calendar;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.util.NiFiProperties;
 import org.springframework.security.core.Authentication;
 
 /**
@@ -28,6 +41,16 @@ public class JwtService {
 
     private final static String AUTHORIZATION = "Authorization";
 
+    private final String key;
+    private final Integer expires;
+
+    public JwtService(final NiFiProperties properties) {
+        // TODO - load key (and algo/provider?) and expiration from properties
+
+        key = TextCodec.BASE64.encode("nififtw!");
+        expires = 1;
+    }
+
     /**
      * Gets the Authentication by extracting a JWT token from the specified request.
      *
@@ -35,12 +58,16 @@ public class JwtService {
      * @return The user identifier from the token
      */
     public String getAuthentication(final HttpServletRequest request) {
-        // TODO : actually extract/verify token
-
         // extract/verify token from incoming request
         final String authorization = request.getHeader(AUTHORIZATION);
-        final String username = StringUtils.substringAfterLast(authorization, " ");
-        return username;
+        final String token = StringUtils.substringAfterLast(authorization, " ");
+
+        try {
+            final Jws<Claims> jwt = Jwts.parser().setSigningKey(key).parseClaimsJws(token);
+            return jwt.getBody().getSubject();
+        } catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException e) {
+            return null;
+        }
     }
 
     /**
@@ -48,14 +75,25 @@ public class JwtService {
      *
      * @param response The response to add the token to
      * @param authentication The authentication to generate a token for
+     * @throws java.io.IOException if an io exception occurs
      */
-    public void addToken(final HttpServletResponse response, final Authentication authentication) {
-        // TODO : actually create real token... in header or response body?
+    public void addToken(final HttpServletResponse response, final Authentication authentication) throws IOException {
+        // set expiration to one day from now
+        final Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.DATE, expires);
 
         // create a token the specified authentication
-        String token = authentication.getName();
+        final String identity = authentication.getPrincipal().toString();
+        final String username = authentication.getName();
+        final String token = Jwts.builder().setSubject(identity).claim("preferred_username", username).setExpiration(calendar.getTime()).signWith(SignatureAlgorithm.HS512, key).compact();
 
         // add the token as a response header
-        response.setHeader(AUTHORIZATION, "Bearer " + token);
+        final PrintWriter out = response.getWriter();
+        out.print(token);
+
+        // mark the response as successful
+        response.setStatus(HttpServletResponse.SC_CREATED);
+        response.setContentType("text/plain");
     }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.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/token/LoginAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
index 528b60b..f908d79 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
@@ -17,6 +17,7 @@
 package org.apache.nifi.web.security.token;
 
 import org.apache.nifi.authentication.LoginCredentials;
+import org.apache.nifi.security.util.CertificateUtils;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 
 /**
@@ -45,4 +46,11 @@ public class LoginAuthenticationToken extends AbstractAuthenticationToken {
     public Object getPrincipal() {
         return credentials.getUsername();
     }
+
+    @Override
+    public String getName() {
+        // if the username is a DN this will extract the username or CN... if not will return what was passed
+        return CertificateUtils.extractUsername(credentials.getUsername());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
index 45d3ba3..fa0b5b8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
@@ -40,7 +40,9 @@
     </bean>
     
     <!-- jwt service -->
-    <bean id="jwtService" class="org.apache.nifi.web.security.jwt.JwtService"></bean>
+    <bean id="jwtService" class="org.apache.nifi.web.security.jwt.JwtService">
+        <constructor-arg ref="nifiProperties"/>
+    </bean>
     
     <!-- login identity provider -->
     <bean id="loginIdentityProvider" class="org.apache.nifi.web.security.spring.LoginIdentityProviderFactoryBean">

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE
index 40d0725..6769105 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE
@@ -351,6 +351,28 @@ For details see http://jqueryui.com
     OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
+This product bundles 'jquery.base64.js' which is available under an MIT style license.
+
+    Copyright (c) 2013 Yannick Albert (http://yckart.com/)
+
+    Permission is hereby granted, free of charge, to any person obtaining 
+    a copy of this software and associated documentation files (the "Software"), 
+    to deal in the Software without restriction, including without limitation 
+    the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 
+    sell copies of the Software, and to permit persons to whom the Software is 
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be 
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
+    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
+    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
 This product bundles 'SlickGrid v2.2' which is available under an MIT style license.
 
     Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp
index 925f93c..e2b7b9b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp
@@ -27,6 +27,7 @@
         <link rel="stylesheet" href="js/jquery/qtip2/jquery.qtip.min.css?" type="text/css" />
         <link rel="stylesheet" href="js/jquery/ui-smoothness/jquery-ui-1.10.4.min.css" type="text/css" />
         <script type="text/javascript" src="js/jquery/jquery-2.1.1.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.base64.js"></script>
         <script type="text/javascript" src="js/jquery/jquery.count.js"></script>
         <script type="text/javascript" src="js/jquery/jquery.center.js"></script>
         <script type="text/javascript" src="js/jquery/modal/jquery.modal.js?${project.version}"></script>

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
index 81296d2..204b1b3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
@@ -45,7 +45,9 @@
     <div id="header-links-container">
         <ul>
             <li id="current-user-container">
-                <span id="current-user"></span>
+                <div id="anonymous-user-alert"></div>
+                <div id="current-user"></div>
+                <div class="clear"></div>
             </li>
             <li id="login-link-container">
                 <span id="login-link" class="link">login</span>

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp
index afa7687..59191ce 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp
@@ -16,7 +16,17 @@
 --%>
 <%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
 <div id="nifi-registration-container" class="hidden">
-    <div id="nifi-registration-title" class="login-title">Submit Justification</div>
+    <div id="nifi-registration-title" class="login-title nifi-submit-justification">Submit Justification</div>
+    <div id="nifi-user-submit-justification-container" class="nifi-submit-justification">
+        <div class="setting">
+            <div class="setting-name">User</div>
+            <div class="setting-field">
+                <div id="nifi-user-submit-justification"></div>
+                <span id="nifi-user-submit-justification-logout" class="link hidden">logout</span>
+                <div class="clear"></div>
+            </div>
+        </div>
+    </div>
     <div class="setting">
         <div class="setting-name">Justification</div>
         <div class="setting-field">

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css
index 3f0b299..8f2450c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css
@@ -506,7 +506,17 @@ div.search-glass-pane {
 
 /* styles for the status link */
 
+#anonymous-user-alert {
+    float: left;
+    margin-top: -2px;
+    margin-right: 6px;
+    width: 18px;
+    height: 16px;
+    background-image: url(../images/iconAlert.png);
+}
+
 #current-user {
+    float: left;
     margin-right: 8px;
     font-weight: bold;
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css
index 2fbe107..38ce410 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css
@@ -73,10 +73,24 @@ body.login-body input, body.login-body textarea {
 */
 
 #nifi-registration-container {
-    margin-top: 10px;
     width: 412px;
 }
 
+#nifi-user-submit-justification-container {
+    margin-bottom: 10px;
+}
+
+#nifi-user-submit-justification {
+    float: left;
+    font-weight: bold;
+}
+
+#nifi-user-submit-justification-logout {
+    margin-left: 10px;
+    float: left;
+    text-decoration: underline;
+}
+
 #nifi-registration-justification {
     height: 200px;
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js
new file mode 100644
index 0000000..32ceab0
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js
@@ -0,0 +1,123 @@
+/*!
+ * jquery.base64.js 0.0.3 - https://github.com/yckart/jquery.base64.js
+ * Makes Base64 en & -decoding simpler as it is.
+ *
+ * Based upon: https://gist.github.com/Yaffle/1284012
+ *
+ * Copyright (c) 2012 Yannick Albert (http://yckart.com)
+ * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php).
+ * 2013/02/10
+ **/
+;(function($) {
+
+    var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
+        a256 = '',
+        r64 = [256],
+        r256 = [256],
+        i = 0;
+
+    var UTF8 = {
+
+        /**
+         * Encode multi-byte Unicode string into utf-8 multiple single-byte characters
+         * (BMP / basic multilingual plane only)
+         *
+         * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
+         *
+         * @param {String} strUni Unicode string to be encoded as UTF-8
+         * @returns {String} encoded string
+         */
+        encode: function(strUni) {
+            // use regular expressions & String.replace callback function for better efficiency
+            // than procedural approaches
+            var strUtf = strUni.replace(/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
+            function(c) {
+                var cc = c.charCodeAt(0);
+                return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f);
+            })
+            .replace(/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
+            function(c) {
+                var cc = c.charCodeAt(0);
+                return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3F, 0x80 | cc & 0x3f);
+            });
+            return strUtf;
+        },
+
+        /**
+         * Decode utf-8 encoded string back into multi-byte Unicode characters
+         *
+         * @param {String} strUtf UTF-8 string to be decoded back to Unicode
+         * @returns {String} decoded string
+         */
+        decode: function(strUtf) {
+            // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
+            var strUni = strUtf.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars
+            function(c) { // (note parentheses for precence)
+                var cc = ((c.charCodeAt(0) & 0x0f) << 12) | ((c.charCodeAt(1) & 0x3f) << 6) | (c.charCodeAt(2) & 0x3f);
+                return String.fromCharCode(cc);
+            })
+            .replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars
+            function(c) { // (note parentheses for precence)
+                var cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f;
+                return String.fromCharCode(cc);
+            });
+            return strUni;
+        }
+    };
+
+    while(i < 256) {
+        var c = String.fromCharCode(i);
+        a256 += c;
+        r256[i] = i;
+        r64[i] = b64.indexOf(c);
+        ++i;
+    }
+
+    function code(s, discard, alpha, beta, w1, w2) {
+        s = String(s);
+        var buffer = 0,
+            i = 0,
+            length = s.length,
+            result = '',
+            bitsInBuffer = 0;
+
+        while(i < length) {
+            var c = s.charCodeAt(i);
+            c = c < 256 ? alpha[c] : -1;
+
+            buffer = (buffer << w1) + c;
+            bitsInBuffer += w1;
+
+            while(bitsInBuffer >= w2) {
+                bitsInBuffer -= w2;
+                var tmp = buffer >> bitsInBuffer;
+                result += beta.charAt(tmp);
+                buffer ^= tmp << bitsInBuffer;
+            }
+            ++i;
+        }
+        if(!discard && bitsInBuffer > 0) result += beta.charAt(buffer << (w2 - bitsInBuffer));
+        return result;
+    }
+
+    var Plugin = $.base64 = function(dir, input, encode) {
+            return input ? Plugin[dir](input, encode) : dir ? null : this;
+        };
+
+    Plugin.btoa = Plugin.encode = function(plain, utf8encode) {
+        plain = Plugin.raw === false || Plugin.utf8encode || utf8encode ? UTF8.encode(plain) : plain;
+        plain = code(plain, false, r256, b64, 8, 6);
+        return plain + '===='.slice((plain.length % 4) || 4);
+    };
+
+    Plugin.atob = Plugin.decode = function(coded, utf8decode) {
+        coded = coded.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+        coded = String(coded).split('=');
+        var i = coded.length;
+        do {--i;
+            coded[i] = code(coded[i], true, r64, a256, 6, 8);
+        } while (i > 0);
+        coded = coded.join('');
+        return Plugin.raw === false || Plugin.utf8decode || utf8decode ? UTF8.decode(coded) : coded;
+    };
+}(jQuery));

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index 46578ab..c316ef2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -64,7 +64,6 @@ nf.Canvas = (function () {
             bulletinBoard: '../nifi-api/controller/bulletin-board',
             banners: '../nifi-api/controller/banners',
             controller: '../nifi-api/controller',
-            token: '../nifi-api/token',
             controllerConfig: '../nifi-api/controller/config',
             loginConfig: '../nifi-api/controller/login/config',
             cluster: '../nifi-api/cluster',
@@ -191,7 +190,7 @@ nf.Canvas = (function () {
                     if (!refreshContainer.is(':visible')) {
                         $('#stats-last-refreshed').addClass('alert');
                         var refreshMessage = "This flow has been modified by '" + revision.lastModifier + "'. Please refresh.";
-                        
+
                         // update the tooltip
                         var refreshRequiredIcon = $('#refresh-required-icon');
                         if (refreshRequiredIcon.data('qtip')) {
@@ -201,10 +200,10 @@ nf.Canvas = (function () {
                                 content: refreshMessage
                             }, nf.CanvasUtils.config.systemTooltipConfig));
                         }
-                    
+
                         refreshContainer.show();
                     }
-                    
+
                     // insert the refresh needed text in the settings - if necessary
                     if (!settingsRefreshIcon.is(':visible')) {
                         $('#settings-last-refreshed').addClass('alert');
@@ -336,7 +335,7 @@ nf.Canvas = (function () {
                     'offset': '100%',
                     'stop-color': '#ffffff'
                 });
-                
+
         // define the gradient for the expiration icon
         var expirationBackground = defs.append('linearGradient')
                 .attr({
@@ -346,7 +345,7 @@ nf.Canvas = (function () {
                     'x2': '0%',
                     'y2': '100%'
                 });
-                
+
         expirationBackground.append('stop')
                 .attr({
                     'offset': '0%',
@@ -400,105 +399,105 @@ nf.Canvas = (function () {
                 // prevent further propagation (to parents and others handlers 
                 // on the same element to prevent zoom behavior)
                 d3.event.stopImmediatePropagation();
-                
+
                 // prevents the browser from changing to a text selection cursor
                 d3.event.preventDefault();
             }
         })
-        .on('mousemove.selection', function () {
-            // update selection box if shift is held down
-            if (d3.event.shiftKey) {
-                // get the selection box
-                var selectionBox = d3.select('rect.selection');
-                if (!selectionBox.empty()) {
-                    // get the original position
-                    var originalPosition = selectionBox.datum();
-                    var position = d3.mouse(canvas.node());
-                    
-                    var d = {};
-                    if (originalPosition[0] < position[0]) {
-                        d.x = originalPosition[0];
-                        d.width = position[0] - originalPosition[0];
-                    } else {
-                        d.x = position[0];
-                        d.width = originalPosition[0] - position[0];
-                    }
-
-                    if (originalPosition[1] < position[1]) {
-                        d.y = originalPosition[1];
-                        d.height = position[1] - originalPosition[1];
-                    } else {
-                        d.y = position[1];
-                        d.height = originalPosition[1] - position[1];
-                    }
+                .on('mousemove.selection', function () {
+                    // update selection box if shift is held down
+                    if (d3.event.shiftKey) {
+                        // get the selection box
+                        var selectionBox = d3.select('rect.selection');
+                        if (!selectionBox.empty()) {
+                            // get the original position
+                            var originalPosition = selectionBox.datum();
+                            var position = d3.mouse(canvas.node());
+
+                            var d = {};
+                            if (originalPosition[0] < position[0]) {
+                                d.x = originalPosition[0];
+                                d.width = position[0] - originalPosition[0];
+                            } else {
+                                d.x = position[0];
+                                d.width = originalPosition[0] - position[0];
+                            }
 
-                    // update the selection box
-                    selectionBox.attr(d);
-                    
-                    // prevent further propagation (to parents)
-                    d3.event.stopPropagation();
-                }
-            }
-        })
-        .on('mouseup.selection', function () {
-            // ensure this originated from clicking the canvas, not a component.
-            // when clicking on a component, the event propagation is stopped so
-            // it never reaches the canvas. we cannot do this however on up events
-            // since the drag events break down
-            if (canvasClicked === false) {
-                return;
-            }
+                            if (originalPosition[1] < position[1]) {
+                                d.y = originalPosition[1];
+                                d.height = position[1] - originalPosition[1];
+                            } else {
+                                d.y = position[1];
+                                d.height = originalPosition[1] - position[1];
+                            }
 
-            // reset the canvas click flag
-            canvasClicked = false;
-
-            // get the selection box 
-            var selectionBox = d3.select('rect.selection');
-            if (!selectionBox.empty()) {
-                var selectionBoundingBox = {
-                    x: parseInt(selectionBox.attr('x'), 10),
-                    y: parseInt(selectionBox.attr('y'), 10),
-                    width: parseInt(selectionBox.attr('width'), 10),
-                    height: parseInt(selectionBox.attr('height'), 10)
-                };
+                            // update the selection box
+                            selectionBox.attr(d);
 
-                // see if a component should be selected or not
-                d3.selectAll('g.component').classed('selected', function (d) {
-                    // consider it selected if its already selected or enclosed in the bounding box
-                    return d3.select(this).classed('selected') ||
-                            d.component.position.x >= selectionBoundingBox.x && (d.component.position.x + d.dimensions.width) <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
-                            d.component.position.y >= selectionBoundingBox.y && (d.component.position.y + d.dimensions.height) <= (selectionBoundingBox.y + selectionBoundingBox.height);
-                });
+                            // prevent further propagation (to parents)
+                            d3.event.stopPropagation();
+                        }
+                    }
+                })
+                .on('mouseup.selection', function () {
+                    // ensure this originated from clicking the canvas, not a component.
+                    // when clicking on a component, the event propagation is stopped so
+                    // it never reaches the canvas. we cannot do this however on up events
+                    // since the drag events break down
+                    if (canvasClicked === false) {
+                        return;
+                    }
 
-                // see if a connection should be selected or not
-                d3.selectAll('g.connection').classed('selected', function (d) {
-                    // consider all points
-                    var points = [d.start].concat(d.bends, [d.end]);
+                    // reset the canvas click flag
+                    canvasClicked = false;
 
-                    // determine the bounding box
-                    var x = d3.extent(points, function (pt) {
-                        return pt.x;
-                    });
-                    var y = d3.extent(points, function (pt) {
-                        return pt.y;
-                    });
+                    // get the selection box 
+                    var selectionBox = d3.select('rect.selection');
+                    if (!selectionBox.empty()) {
+                        var selectionBoundingBox = {
+                            x: parseInt(selectionBox.attr('x'), 10),
+                            y: parseInt(selectionBox.attr('y'), 10),
+                            width: parseInt(selectionBox.attr('width'), 10),
+                            height: parseInt(selectionBox.attr('height'), 10)
+                        };
 
-                    // consider it selected if its already selected or enclosed in the bounding box
-                    return d3.select(this).classed('selected') ||
-                            x[0] >= selectionBoundingBox.x && x[1] <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
-                            y[0] >= selectionBoundingBox.y && y[1] <= (selectionBoundingBox.y + selectionBoundingBox.height);
-                });
+                        // see if a component should be selected or not
+                        d3.selectAll('g.component').classed('selected', function (d) {
+                            // consider it selected if its already selected or enclosed in the bounding box
+                            return d3.select(this).classed('selected') ||
+                                    d.component.position.x >= selectionBoundingBox.x && (d.component.position.x + d.dimensions.width) <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
+                                    d.component.position.y >= selectionBoundingBox.y && (d.component.position.y + d.dimensions.height) <= (selectionBoundingBox.y + selectionBoundingBox.height);
+                        });
+
+                        // see if a connection should be selected or not
+                        d3.selectAll('g.connection').classed('selected', function (d) {
+                            // consider all points
+                            var points = [d.start].concat(d.bends, [d.end]);
+
+                            // determine the bounding box
+                            var x = d3.extent(points, function (pt) {
+                                return pt.x;
+                            });
+                            var y = d3.extent(points, function (pt) {
+                                return pt.y;
+                            });
 
-                // remove the selection box
-                selectionBox.remove();
-            } else if (panning === false) {
-                // deselect as necessary if we are not panning
-                nf.CanvasUtils.getSelection().classed('selected', false);
-            }
+                            // consider it selected if its already selected or enclosed in the bounding box
+                            return d3.select(this).classed('selected') ||
+                                    x[0] >= selectionBoundingBox.x && x[1] <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
+                                    y[0] >= selectionBoundingBox.y && y[1] <= (selectionBoundingBox.y + selectionBoundingBox.height);
+                        });
+
+                        // remove the selection box
+                        selectionBox.remove();
+                    } else if (panning === false) {
+                        // deselect as necessary if we are not panning
+                        nf.CanvasUtils.getSelection().classed('selected', false);
+                    }
 
-            // update the toolbar
-            nf.CanvasToolbar.refresh();
-        });
+                    // update the toolbar
+                    nf.CanvasToolbar.refresh();
+                });
 
         // define a function for update the graph dimensions
         var updateGraphSize = function () {
@@ -513,7 +512,7 @@ nf.Canvas = (function () {
             var top = parseInt(canvasContainer.css('top'), 10);
             var windowHeight = $(window).height();
             var canvasHeight = (windowHeight - (bottom + top));
-            
+
             // canvas/svg
             canvasContainer.css({
                 'height': canvasHeight + 'px',
@@ -539,7 +538,7 @@ nf.Canvas = (function () {
             }
         }).on('keydown', function (evt) {
             var isCtrl = evt.ctrlKey || evt.metaKey;
-            
+
             // consider escape, before checking dialogs
             if (!isCtrl && evt.keyCode === 27) {
                 // esc
@@ -555,7 +554,7 @@ nf.Canvas = (function () {
                 // first consider read only property detail dialog
                 if ($('div.property-detail').is(':visible')) {
                     nf.Common.removeAllPropertyDetailDialogs();
-                    
+
                     // prevent further bubbling as we're already handled it
                     evt.stopPropagation();
                     evt.preventDefault();
@@ -573,7 +572,7 @@ nf.Canvas = (function () {
                                 var dialogMax = null;
 
                                 // identify the top most cancellable
-                                $.each(cancellables, function(_, cancellable) {
+                                $.each(cancellables, function (_, cancellable) {
                                     var dialog = $(cancellable);
                                     var zIndex = dialog.css('zIndex');
 
@@ -618,10 +617,10 @@ nf.Canvas = (function () {
                         }
                     }
                 }
-                
+
                 return;
             }
-            
+
             // if a dialog is open, disable canvas shortcuts
             if ($('.dialog').is(':visible')) {
                 return;
@@ -836,7 +835,7 @@ nf.Canvas = (function () {
                         bulletinIcon.show();
                     }
                 }
-                
+
                 // update controller service and reporting task bulletins
                 nf.Settings.setBulletins(controllerStatus.controllerServiceBulletins, controllerStatus.reportingTaskBulletins);
 
@@ -937,22 +936,18 @@ nf.Canvas = (function () {
     };
 
     return {
-        
         ANONYMOUS_USER_TEXT: 'Anonymous user',
         CANVAS_OFFSET: 0,
-        
         /**
          * Determines if the current broswer supports SVG.
          */
         SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect,
-        
         /**
          * Hides the splash that is displayed while the application is loading.
          */
         hideSplash: function () {
             $('#splash').fadeOut();
         },
-        
         /**
          * Stop polling for revision.
          */
@@ -960,7 +955,6 @@ nf.Canvas = (function () {
             // set polling flag
             revisionPolling = false;
         },
-        
         /**
          * Remove the status poller.
          */
@@ -968,7 +962,6 @@ nf.Canvas = (function () {
             // set polling flag
             statusPolling = false;
         },
-        
         /**
          * Reloads the flow from the server based on the currently specified group id.
          * To load another group, update nf.Canvas.setGroupId and call nf.Canvas.reload.
@@ -977,7 +970,7 @@ nf.Canvas = (function () {
             return $.Deferred(function (deferred) {
                 // hide the context menu
                 nf.ContextMenu.hide();
-                
+
                 // get the process group to refresh everything
                 var processGroupXhr = reloadProcessGroup(nf.Canvas.getGroupId());
                 var statusXhr = reloadFlowStatus();
@@ -1011,7 +1004,6 @@ nf.Canvas = (function () {
                 });
             }).promise();
         },
-        
         /**
          * Reloads the status.
          */
@@ -1025,7 +1017,6 @@ nf.Canvas = (function () {
                 });
             }).promise();
         },
-        
         /**
          * Initialize NiFi.
          */
@@ -1036,16 +1027,16 @@ nf.Canvas = (function () {
                 url: config.urls.identity,
                 dataType: 'json'
             });
-            
+
             // get the current user's authorities
             var authoritiesXhr = $.ajax({
                 type: 'GET',
                 url: config.urls.authorities,
                 dataType: 'json'
             });
-            
+
             // load the identity and authorities for the current user
-            var userXhr = $.Deferred(function(deferred) {
+            var userXhr = $.Deferred(function (deferred) {
                 $.when(authoritiesXhr, identityXhr).done(function (authoritiesResult, identityResult) {
                     var authoritiesResponse = authoritiesResult[0];
                     var identityResponse = identityResult[0];
@@ -1057,6 +1048,7 @@ nf.Canvas = (function () {
 
                     // if the user is logged, we want to determine if they were logged in using a certificate
                     if (identityResponse.identity !== 'anonymous') {
+                        // rendner the users name
                         $('#current-user').text(identityResponse.identity).show();
 
                         // render the logout button if there is a token locally
@@ -1064,6 +1056,16 @@ nf.Canvas = (function () {
                             $('#logout-link-container').show();
                         }
                     } else {
+                        // alert user's of anonymous access
+                        $('#anonymous-user-alert').show().qtip($.extend({}, nf.Common.config.tooltipConfig, {
+                            content: 'You are accessing with limited authority. Log in or request an account to access with additional authority granted to you by an administrator.',
+                            position: {
+                                my: 'top right',
+                                at: 'bottom left'
+                            }
+                        }));
+
+                        // render the anonymous user text
                         $('#current-user').text(nf.Canvas.ANONYMOUS_USER_TEXT).show();
                     }
                     deferred.resolve();
@@ -1076,7 +1078,7 @@ nf.Canvas = (function () {
                     }
                 });
             }).promise();
-            
+
             userXhr.done(function () {
                 // get the controller config to register the status poller
                 var configXhr = $.ajax({
@@ -1109,7 +1111,7 @@ nf.Canvas = (function () {
                         }
                     });
                 }).promise();
-                
+
                 // ensure the config requests are loaded
                 $.when(configXhr, loginXhr, userXhr).done(function (configResult, loginResult) {
                     var configResponse = configResult[0];
@@ -1193,7 +1195,6 @@ nf.Canvas = (function () {
                 }).fail(nf.Common.handleAjaxError);
             }).fail(nf.Common.handleAjaxError);
         },
-        
         /**
          * Defines the gradient colors used to render processors.
          * 
@@ -1202,7 +1203,6 @@ nf.Canvas = (function () {
         defineProcessorColors: function (colors) {
             setColors(colors, 'processor');
         },
-        
         /**
          * Defines the gradient colors used to render label.
          * 
@@ -1211,7 +1211,6 @@ nf.Canvas = (function () {
         defineLabelColors: function (colors) {
             setColors(colors, 'label');
         },
-        
         /**
          * Return whether this instance of NiFi is clustered.
          * 
@@ -1220,14 +1219,12 @@ nf.Canvas = (function () {
         isClustered: function () {
             return clustered === true;
         },
-        
         /**
          * Returns whether site to site communications is secure.
          */
         isSecureSiteToSite: function () {
             return secureSiteToSite;
         },
-        
         /**
          * Set the group id.
          * 
@@ -1236,14 +1233,12 @@ nf.Canvas = (function () {
         setGroupId: function (gi) {
             groupId = gi;
         },
-        
         /**
          * Get the group id.
          */
         getGroupId: function () {
             return groupId;
         },
-        
         /**
          * Set the group name.
          * 
@@ -1252,14 +1247,12 @@ nf.Canvas = (function () {
         setGroupName: function (gn) {
             groupName = gn;
         },
-        
         /**
          * Get the group name.
          */
         getGroupName: function () {
             return groupName;
         },
-        
         /**
          * Set the parent group id.
          * 
@@ -1268,16 +1261,14 @@ nf.Canvas = (function () {
         setParentGroupId: function (pgi) {
             parentGroupId = pgi;
         },
-        
         /**
          * Get the parent group id.
          */
         getParentGroupId: function () {
             return parentGroupId;
         },
-        
         View: (function () {
-            
+
             /**
              * Updates component visibility based on their proximity to the screen's viewport.
              */
@@ -1344,8 +1335,8 @@ nf.Canvas = (function () {
                             .classed('entering', function () {
                                 return visible && !wasVisible;
                             }).classed('leaving', function () {
-                                return !visible && wasVisible;
-                            });
+                        return !visible && wasVisible;
+                    });
                 };
 
                 // get the all components
@@ -1432,7 +1423,6 @@ nf.Canvas = (function () {
                     // add the behavior to the canvas and disable dbl click zoom
                     svg.call(behavior).on('dblclick.zoom', null);
                 },
-                
                 /**
                  * Whether or not a component should be rendered based solely on the current scale.
                  * 
@@ -1441,7 +1431,6 @@ nf.Canvas = (function () {
                 shouldRenderPerScale: function () {
                     return nf.Canvas.View.scale() >= MIN_SCALE_TO_RENDER;
                 },
-                
                 /**
                  * Updates component visibility based on the current translation/scale.
                  */
@@ -1449,7 +1438,6 @@ nf.Canvas = (function () {
                     updateComponentVisibility();
                     nf.Graph.pan();
                 },
-                
                 /**
                  * Sets/gets the current translation.
                  * 
@@ -1462,7 +1450,6 @@ nf.Canvas = (function () {
                         behavior.translate(translate);
                     }
                 },
-                
                 /**
                  * Sets/gets the current scale.
                  * 
@@ -1475,7 +1462,6 @@ nf.Canvas = (function () {
                         behavior.scale(scale);
                     }
                 },
-                
                 /**
                  * Zooms in a single zoom increment.
                  */
@@ -1500,7 +1486,6 @@ nf.Canvas = (function () {
                         height: 1
                     });
                 },
-                
                 /**
                  * Zooms out a single zoom increment.
                  */
@@ -1525,7 +1510,6 @@ nf.Canvas = (function () {
                         height: 1
                     });
                 },
-                
                 /**
                  * Zooms to fit the entire graph on the canvas.
                  */
@@ -1572,7 +1556,6 @@ nf.Canvas = (function () {
                         height: canvasHeight / newScale
                     });
                 },
-                
                 /**
                  * Zooms to the actual size (1 to 1).
                  */
@@ -1621,7 +1604,6 @@ nf.Canvas = (function () {
                     // center as appropriate
                     nf.CanvasUtils.centerBoundingBox(box);
                 },
-                
                 /**
                  * Refreshes the view based on the configured translation and scale.
                  * 

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
index 4f4cb1d..6152867 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
@@ -75,7 +75,7 @@ nf.Login = (function () {
     var showUserRegistration = function () {
         showNiFiRegistration();
 
-        $('#nifi-registration-title').hide();
+        $('div.nifi-submit-justification').hide();
         $('#user-registration-container').show();
         $('#login-submission-button').text('Create');
     };
@@ -109,41 +109,15 @@ nf.Login = (function () {
                 'username': $('#username').val(),
                 'password': $('#password').val()
             }
-        }).done(function (response, status, xhr) {
-            var authorization = xhr.getResponseHeader('Authorization');
-            var badToken = false;
-
-            // ensure there was a token in the response
-            if (authorization) {
-                var tokens = authorization.split(/ /);
-
-                // ensure the token is the appropriate length
-                if (tokens.length === 2) {
-                    // store the jwt and reload the page
-                    nf.Storage.setItem('jwt', tokens[1]);
+        }).done(function (jwt) {
+            // store the jwt and reload the page
+            nf.Storage.setItem('jwt', jwt);
 
-                    // reload as appropriate
-                    if (top !== window) {
-                        parent.window.location = '/nifi';
-                    } else {
-                        window.location = '/nifi';
-                    }
-                    return;
-                } else {
-                    badToken = true;
-                }
+            // reload as appropriate
+            if (top !== window) {
+                parent.window.location = '/nifi';
             } else {
-                badToken = true;
-            }
-
-            if (badToken === true) {
-                $('#login-message-title').text('An unexpected error has occurred');
-                $('#login-message').text('The user token could not be parsed.');
-
-                // update visibility
-                $('#login-container').hide();
-                $('#login-submission-container').hide();
-                $('#login-message-container').show();
+                window.location = '/nifi';
             }
         }).fail(function (xhr, status, error) {
             if (xhr.status === 400) {
@@ -164,13 +138,25 @@ nf.Login = (function () {
     };
 
     var createUserAccount = function () {
+        var password = $('#registration-password').val();
+        var passwordConfirmation = $('#registration-password-confirmation').val();
+
+        // ensure the password matches
+        if (password !== passwordConfirmation) {
+            nf.Dialog.showOkDialog({
+                dialogContent: 'The specified passwords do not match.',
+                overlayBackground: false
+            });
+            return;
+        }
+
         // attempt to create the user account registration
         $.ajax({
             type: 'POST',
             url: config.urls.registration,
             data: {
                 'username': $('#registration-username').val(),
-                'password': $('#registration-password').val(),
+                'password': password,
                 'justification': $('#nifi-registration-justification').val()
             }
         }).done(function (response, status, xhr) {
@@ -220,6 +206,33 @@ nf.Login = (function () {
         });
     };
 
+    /**
+     * Extracts the subject from the specified jwt. If the jwt is not as expected
+     * an empty string is returned.
+     * 
+     * @param {string} jwt
+     * @returns {string}
+     */
+    var getJwtSubject = function (jwt) {
+        if (nf.Common.isDefinedAndNotNull(jwt)) {
+            var segments = jwt.split(/\./);
+            if (segments.length !== 3) {
+                return '';
+            }
+
+            var rawPayload = $.base64.atob(segments[1]);
+            var payload = JSON.parse(rawPayload);
+
+            if (nf.Common.isDefinedAndNotNull(payload['preferred_username'])) {
+                return payload['preferred_username'];
+            } else {
+                '';
+            }
+        }
+
+        return '';
+    };
+
     return {
         /**
          * Initializes the login page.
@@ -231,6 +244,16 @@ nf.Login = (function () {
             var needsLogin = false;
             var needsNiFiRegistration = false;
 
+            var logout = function () {
+                nf.Storage.removeItem('jwt');
+            };
+            
+            // handle logout
+            $('#nifi-user-submit-justification-logout').on('click', function () {
+                logout();
+                window.location = '/nifi/login';
+            });
+
             var token = $.ajax({
                 type: 'GET',
                 url: config.urls.token
@@ -250,7 +273,8 @@ nf.Login = (function () {
                         isAnonymous = true;
 
                         // request a token without including credentials, if successful then the user is using a certificate
-                        token.done(function () {
+                        token.done(function (jwt) {
+
                             // the user is using a certificate/token, see if their account is active/pending/revoked/etc
                             $.ajax({
                                 type: 'GET',
@@ -263,6 +287,16 @@ nf.Login = (function () {
                                 $('#login-message').text('Your account is active and you are already logged in.');
                             }).fail(function (xhr, status, error) {
                                 if (xhr.status === 401) {
+                                    var user = getJwtSubject(jwt);
+                                    
+                                    // show the user
+                                    $('#nifi-user-submit-justification').text(user);
+
+                                    // render the logout button if there is a token locally
+                                    if (nf.Storage.getItem('jwt') !== null) {
+                                        $('#nifi-user-submit-justification-logout').show();
+                                    }
+
                                     // anonymous user and 401 means they need nifi registration
                                     needsNiFiRegistration = true;
                                 } else {
@@ -279,7 +313,12 @@ nf.Login = (function () {
                             }).always(function () {
                                 deferred.resolve();
                             });
-                        }).fail(function () {
+                        }).fail(function (tokenXhr) {
+                            if (tokenXhr.status === 400) {
+                                // no credentials supplied so 400 must be due to an invalid/expired token
+                                logout();
+                            }
+
                             // no token granted, user has no certificate and needs to login with their credentials
                             needsLogin = true;
                             deferred.resolve();
@@ -296,10 +335,25 @@ nf.Login = (function () {
                     // unable to get identity (and no anonymous user) see if we can offer login
                     if (xhr.status === 401) {
                         // attempt to get a token for the current user without passing login credentials
-                        token.done(function () {
+                        token.done(function (jwt) {
+                            var user = getJwtSubject(jwt);
+
+                            // show the user
+                            $('#nifi-user-submit-justification').text(user);
+
+                            // render the logout button if there is a token locally
+                            if (nf.Storage.getItem('jwt') !== null) {
+                                $('#nifi-user-submit-justification-logout').show();
+                            }
+
                             // 401 from identity request and 200 from token means they have a certificate/token but have not yet requested an account 
                             needsNiFiRegistration = true;
-                        }).fail(function () {
+                        }).fail(function (tokenXhr) {
+                            if (tokenXhr.status === 400) {
+                                // no credentials supplied so 400 must be due to an invalid/expired token
+                                logout();
+                            }
+
                             // no token granted, user needs to login with their credentials
                             needsLogin = true;
                         }).always(function () {

http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index c798a61..8c023e7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -206,7 +206,14 @@ nf.Common = {
         
         // if an error occurs while the splash screen is visible close the canvas show the error message
         if ($('#splash').is(':visible')) {
-            $('#message-title').text('An unexpected error has occurred');
+            if (xhr.status === 401) {
+                $('#message-title').text('Unauthorized');
+            } else if (xhr.status === 403) {
+                $('#message-title').text('Access Denied');
+            } else {
+                $('#message-title').text('An unexpected error has occurred');
+            }
+            
             if ($.trim(xhr.responseText) === '') {
                 $('#message-content').text('Please check the logs.');
             } else {
@@ -249,7 +256,7 @@ nf.Common = {
                     $('#message-content').text(xhr.responseText);
                 }
             } else if (xhr.status === 403) {
-                $('#message-title').text('Forbidden');
+                $('#message-title').text('Access Denied');
                 if ($.trim(xhr.responseText) === '') {
                     $('#message-content').text('Unable to authorize you to use this NiFi.');
                 } else {