From b6d09b86b6b0b6873dda192d28e704653ac9522d Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Thu, 5 Nov 2015 18:26:00 -0500 Subject: [PATCH] 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. --- LICENSE | 22 ++ nifi-assembly/LICENSE | 22 ++ .../nifi-web/nifi-web-security/pom.xml | 5 + .../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 +- .../src/main/webapp/css/header.css | 10 + .../nifi-web-ui/src/main/webapp/css/login.css | 16 +- .../main/webapp/js/jquery/jquery.base64.js | 123 ++++++++ .../src/main/webapp/js/nf/canvas/nf-canvas.js | 262 ++++++++---------- .../src/main/webapp/js/nf/login/nf-login.js | 132 ++++++--- .../src/main/webapp/js/nf/nf-common.js | 11 +- 18 files changed, 525 insertions(+), 217 deletions(-) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js diff --git a/LICENSE b/LICENSE index 59741e6035..f4be753c80 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 diff --git a/nifi-assembly/LICENSE b/nifi-assembly/LICENSE index 5abc79a51c..a7a73b8834 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 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 c0db61584b..c954d0d8b3 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 @@ -77,6 +77,11 @@ org.apache.nifi nifi-framework-core + + io.jsonwebtoken + jjwt + 0.6.0 + org.bouncycastle bcprov-jdk16 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 e914db5025..606d2e3081 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 { 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 388b81ecd8..fb45363b06 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 { 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 2012d6929c..8afa15ac83 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 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"); } + } 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 528b60be78..f908d792c1 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()); + } + } 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 45d3ba3be6..fa0b5b8dbe 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 @@ - + + + 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 40d0725e15..6769105002 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 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 925f93c075..e2b7b9b127 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 @@ + 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 81296d28f4..204b1b3703 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 @@