You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@nifi.apache.org by GitBox <gi...@apache.org> on 2021/07/29 19:44:47 UTC

[GitHub] [nifi] exceptionfactory opened a new pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

exceptionfactory opened a new pull request #5262:
URL: https://github.com/apache/nifi/pull/5262


   #### Description of PR
   
   NIFI-8766 Implements asymmetric-key processing for internal JSON Web Token signing and verification. The changes replace the current approach based on per-user symmetric-key signing with a key rotation strategy using RSA Key Pairs. The implementation eliminates persistent storage of private key information and introduces support for JSON Web Token revocation.
   
   The implementation leverages the latest version of Spring Security OAuth2 and Nimbus JWT for authentication and validation. Updates include a new section in the Administration Guide describing JSON Web Token processing as well as the new configuration property for controlling the key rotation period.
   
   In order to streamline the review of the contribution we ask you
   to ensure the following steps have been taken:
   
   ### For all changes:
   - [X] Is there a JIRA ticket associated with this PR? Is it referenced 
        in the commit message?
   
   - [X] Does your PR title start with **NIFI-XXXX** where XXXX is the JIRA number you are trying to resolve? Pay particular attention to the hyphen "-" character.
   
   - [X] Has your PR been rebased against the latest commit within the target branch (typically `main`)?
   
   - [X] Is your initial contribution a single, squashed commit? _Additional commits in response to PR reviewer feedback should be made on this branch and pushed to allow change tracking. Do not `squash` or use `--force` when pushing to allow for clean monitoring of changes._
   
   ### For code changes:
   - [X] Have you ensured that the full suite of tests is executed via `mvn -Pcontrib-check clean install` at the root `nifi` folder?
   - [X] Have you written or updated unit tests to verify your changes?
   - [X] Have you verified that the full build is successful on JDK 8?
   - [ ] Have you verified that the full build is successful on JDK 11?
   - [X] If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under [ASF 2.0](http://www.apache.org/legal/resolved.html#category-a)? 
   - [ ] If applicable, have you updated the `LICENSE` file, including the main `LICENSE` file under `nifi-assembly`?
   - [X] If applicable, have you updated the `NOTICE` file, including the main `NOTICE` file found under `nifi-assembly`?
   - [ ] If adding new Properties, have you added `.displayName` in addition to .name (programmatic access) for each of the new properties?
   
   ### For documentation related changes:
   - [X] Have you ensured that format looks appropriate for the output in which it is rendered?
   
   ### Note:
   Please ensure that once the PR is submitted, you check GitHub Actions CI for build issues and submit an update to your PR as soon as possible.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] kevdoran commented on a change in pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
kevdoran commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r691268425



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       This does not change existing functionality, so it is not a blocker to the PR, but just for my own understanding as I have not seen how NiFi does this - in NiFi, is the jwt token value also used as the CSRF prevention token value?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] kevdoran commented on a change in pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
kevdoran commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r691268425



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       Just for my own understanding - is the jwt token value also used as the CSRF prevention token value?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r691271795



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       That's correct. Changes in NIFI-7870 introduced sending the JWT in an HTTP session cookie, but retained sending the JWT in the Authorization header. This helps mitigate potential CSRF issues since the browser automatically sends Cookies, but does not send the Authorization header unless the NiFi UI includes it as part of the request.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#issuecomment-901241731


   Thanks for the review and approval @kevdoran!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] thenatog commented on pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
thenatog commented on pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#issuecomment-902030784


   This PR looks good to me - tested logging in and using the flow, advanced UI and downloading flow file content in a secure cluster. As far as I can tell this PR hasn't introduced secure signing key sharing across nodes yet so I had to log in with username/pass to each node, but it sounds like that should be relatively easy to add in the future. I've asked exceptionfactory a few questions offline and sounds like important security considerations are covered.
   
   +1, great work.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680405345



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/converter/StandardJwtAuthenticationConverter.java
##########
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.converter;
+
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
+import org.apache.nifi.authorization.util.UserGroupUtil;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Standard Converter from JSON Web Token to NiFi Authentication Token
+ */
+public class StandardJwtAuthenticationConverter implements Converter<Jwt, NiFiAuthenticationToken> {
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final List<IdentityMapping> identityMappings;
+
+    public StandardJwtAuthenticationConverter(final Authorizer authorizer, final IdpUserGroupService idpUserGroupService, final NiFiProperties properties) {
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.identityMappings = IdentityMappingUtil.getIdentityMappings(properties);
+    }
+
+    /**
+     * Convert JSON Web Token to NiFi Authentication Token
+     *
+     * @param jwt JSON Web Token
+     * @return NiFi Authentication Token
+     */
+    @Override
+    public NiFiAuthenticationToken convert(final Jwt jwt) {
+        final NiFiUser user = getUser(jwt);
+        return new NiFiAuthenticationToken(new NiFiUserDetails(user));
+    }
+
+    private NiFiUser getUser(final Jwt jwt) {
+        final String identity = IdentityMappingUtil.mapIdentity(jwt.getSubject(), identityMappings);
+
+        return new StandardNiFiUser.Builder()
+                .identity(identity)
+                .groups(UserGroupUtil.getUserGroups(authorizer, identity))
+                .identityProviderGroups(getIdentityProviderGroups(identity))
+                .build();
+    }
+
+    private Set<String> getIdentityProviderGroups(final String identity) {
+        return idpUserGroupService.getUserGroups(identity).stream()
+                .map(userGroup -> userGroup.getGroupName())

Review comment:
       > Thank you for this contribution, @exceptionfactory! This will be a great security upgrade for NiFi's JWT support. Overall I found the code clearer than the past implementation.
   > 
   > So far I have only tested this with `SingleUserLoginIdentityProvider`, and I noticed that the framework doesn't appear to be handling authentication expiration correctly. I allowed 8 hours for the `SingleUserLoginIdentityProvider` expiration and then revisited NiFi in my browser. This caused the following exception:
   > 
   > ```
   > DEBUG [NiFi Web Server-363] o.a.n.w.s.j.k.StandardVerificationKeySelector Key Identifier [e1917151-33c3-4c08-9789-06c69845c719] Verification Keys Found [0]
   > ERROR [NiFi Web Server-363] o.a.nifi.web.api.config.ThrowableMapper An unexpected error has occurred: org.springframework.security.oauth2.server.resource.InvalidBearerTokenException: An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found. Returning Internal Server Error response.
   > org.springframework.security.oauth2.server.resource.InvalidBearerTokenException: An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
   > 	at org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider.getJwt(JwtAuthenticationProvider.java:101)
   > 	at org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider.authenticate(JwtAuthenticationProvider.java:88)
   > 	at org.apache.nifi.web.api.AccessResource.getAccessStatus(AccessResource.java:261)
   > ```
   > 
   > Clearing my `__Host-Authorization-Bearer` cookie for the site resolved the issue, but clearly this is not what the user should have to do every time their authentication period expires.
   
   Thanks for describing the details. The exception is correct since the verification key for the original JWT expired, but the behavior should be improved. In this case, returning a better response that removes the cookie seems like the best approach.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680405495



##########
File path: nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
##########
@@ -170,6 +171,7 @@
     public static final String SECURITY_GROUP_MAPPING_PATTERN_PREFIX = "nifi.security.group.mapping.pattern.";
     public static final String SECURITY_GROUP_MAPPING_VALUE_PREFIX = "nifi.security.group.mapping.value.";
     public static final String SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX = "nifi.security.group.mapping.transform.";
+    public static final String SECURITY_USER_JWS_KEY_ROTATION_PERIOD = "nifi.security.user.jws.key.rotation.period";

Review comment:
       That's correct, so overriding the default value would require adding the property line to `nifi.properties`. However, adding an explicit default value to the configuration is helpful, so I will go ahead and include it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680404488



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
##########
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.configuration;
+
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import com.nimbusds.jwt.proc.JWTProcessor;
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardJwtAuthenticationConverter;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.jws.StandardJwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.command.KeyExpirationCommand;
+import org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand;
+import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
+import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
+import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationValidator;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.command.RevocationExpirationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * JSON Web Token Configuration for Authentication Security
+ */
+@Configuration
+public class JwtAuthenticationSecurityConfiguration {
+    private static final Set<String> REQUIRED_CLAIMS = new HashSet<>(Arrays.asList("sub", "iss", "aud", "nbf", "iat", "exp", "jti"));
+
+    private final NiFiProperties niFiProperties;
+
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final StateManagerProvider stateManagerProvider;
+
+    @Autowired
+    public JwtAuthenticationSecurityConfiguration(
+            final NiFiProperties niFiProperties,
+            final Authorizer authorizer,
+            final IdpUserGroupService idpUserGroupService,
+            final StateManagerProvider stateManagerProvider
+    ) {
+        this.niFiProperties = niFiProperties;
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.stateManagerProvider = stateManagerProvider;
+    }
+
+    @Bean
+    public JwtAuthenticationProvider jwtAuthenticationProvider() {
+        final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtDecoder());
+        jwtAuthenticationProvider.setJwtAuthenticationConverter(jwtAuthenticationConverter());
+        return jwtAuthenticationProvider;
+    }
+
+    @Bean
+    public JwtDecoder jwtDecoder() {
+        final NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor());
+        final OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
+                JwtValidators.createDefault(),
+                jwtRevocationValidator()
+        );
+        jwtDecoder.setJwtValidator(jwtValidator);
+        return jwtDecoder;
+    }
+
+    @Bean
+    public OAuth2TokenValidator<Jwt> jwtRevocationValidator() {
+        return new JwtRevocationValidator(jwtRevocationService());
+    }
+
+    @Bean
+    public JwtRevocationService jwtRevocationService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardJwtRevocationService.class.getName());
+        return new StandardJwtRevocationService(stateManager);
+    }
+
+    @Bean
+    public JwtLogoutListener jwtLogoutListener() {
+        return new StandardJwtLogoutListener(jwtDecoder(), jwtRevocationService());
+    }
+
+    @Bean
+    public JWTProcessor<SecurityContext> jwtProcessor() {
+        final DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
+        jwtProcessor.setJWSKeySelector(jwsKeySelector());
+        jwtProcessor.setJWTClaimsSetVerifier(claimsSetVerifier());
+        return jwtProcessor;
+    }
+
+    @Bean
+    public JWSKeySelector<SecurityContext> jwsKeySelector() {
+        return new StandardJWSKeySelector<>(verificationKeySelector());
+    }
+
+    @Bean
+    public JWTClaimsSetVerifier<SecurityContext> claimsSetVerifier() {
+        return new DefaultJWTClaimsVerifier<>(null, REQUIRED_CLAIMS);
+    }
+
+    @Bean
+    public StandardJwtAuthenticationConverter jwtAuthenticationConverter() {
+        return new StandardJwtAuthenticationConverter(authorizer, idpUserGroupService, niFiProperties);
+    }
+
+    @Bean
+    public BearerTokenProvider bearerTokenProvider() {
+        return new StandardBearerTokenProvider(jwsSignerProvider());
+    }
+
+    @Bean
+    public StandardJwsSignerProvider jwsSignerProvider() {
+        return new StandardJwsSignerProvider(verificationKeySelector());
+    }
+
+    @Bean
+    public StandardVerificationKeySelector verificationKeySelector() {
+        return new StandardVerificationKeySelector(verificationKeyService(), niFiProperties.getSecurityUserJwsKeyRotationPeriod());
+    }
+
+    @Bean
+    public VerificationKeyService verificationKeyService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardVerificationKeyService.class.getName());
+        return new StandardVerificationKeyService(stateManager);
+    }
+
+    @Bean
+    public KeyGenerationCommand keyGenerationCommand() {
+        final KeyGenerationCommand command = new KeyGenerationCommand(jwsSignerProvider(), verificationKeySelector());
+        commandScheduler().scheduleAtFixedRate(command, niFiProperties.getSecurityUserJwsKeyRotationPeriod());
+        return command;
+    }
+
+    @Bean
+    public KeyExpirationCommand keyExpirationCommand() {
+        final KeyExpirationCommand command = new KeyExpirationCommand(verificationKeyService());
+        commandScheduler().scheduleAtFixedRate(command, niFiProperties.getSecurityUserJwsKeyRotationPeriod());
+        return command;
+    }
+
+    @Bean
+    public RevocationExpirationCommand revocationExpirationCommand() {
+        final RevocationExpirationCommand command = new RevocationExpirationCommand(jwtRevocationService());
+        commandScheduler().scheduleAtFixedRate(command, niFiProperties.getSecurityUserJwsKeyRotationPeriod());

Review comment:
       The Revocation Expiration Command cleans up the cache of revoked tokens to avoid keeping unnecessary references. The revoked tokens include the JSON Web Token identifier and associated expiration, so the storage used is minimal, and keeping references around for a longer than the expiration should not be a problem.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] gresockj commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
gresockj commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680402358



##########
File path: nifi-docs/src/main/asciidoc/administration-guide.adoc
##########
@@ -489,6 +489,28 @@ To enable authentication via Apache Knox the following properties must be config
 this listing. The audience that is populated in the token can be configured in Knox.
 |==================================================================================================================================================
 
+[[json_web_token]]
+=== JSON Web Tokens
+
+NiFi uses JSON Web Tokens to provide authenticated access after the initial login process. Generated JSON Web Tokens include the authenticated user identity
+as well as the issuer and expiration from the configured Login Identity Provider.
+
+NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the `RS512` algorithm for JSON Web Signatures. The system stores RSA
+Public Keys using the configured local State Provider and retains the RSA Private Key in memory. This approach supports signature verification
+for the expiration configured in the Login Identity Provider without persisting the private key.
+
+JSON Web Token support includes revocation on logout using JSON Web Token Identifiers. The system denies access for expired tokens based on the
+Login Identity Provider configuration, but revocation invalidates the token prior to expiration. The system stores revoked identifiers using the

Review comment:
       I see your point -- I had read "based on the Login Identity Provider configuration" as only referring to the explicit configuration values, but it makes sense that this would also cover the default settings, so no change should be needed.  A separate PR to update the Single User documentation sounds fine.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r691271795



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       That's correct. Changes in NIFI-7870 introduced sending the JWT in an HTTP session cookie, but retained sending the JWT in the Authorization header. This helps mitigate potential CSRF issues since the browser automatically sends Cookies, but does not send the Authorization header unless the NiFi UI includes it as part of the request.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] thenatog closed pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
thenatog closed pull request #5262:
URL: https://github.com/apache/nifi/pull/5262


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] greyp9 commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
greyp9 commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r682048618



##########
File path: nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
##########
@@ -354,6 +356,7 @@
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_TRUSTSTORE_STRATEGY = "JDK";
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_CONNECT_TIMEOUT = "30 secs";
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_READ_TIMEOUT = "30 secs";
+    private static final String DEFAULT_SECURITY_USER_JWS_KEY_ROTATION_PERIOD = "PT1H";

Review comment:
       This format is preferable.  Just suggesting this conversion for all properties in the context of a potential 2.0 release.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680402335



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
##########
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.configuration;
+
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import com.nimbusds.jwt.proc.JWTProcessor;
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardJwtAuthenticationConverter;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.jws.StandardJwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.command.KeyExpirationCommand;
+import org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand;
+import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
+import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
+import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationValidator;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.command.RevocationExpirationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * JSON Web Token Configuration for Authentication Security
+ */
+@Configuration
+public class JwtAuthenticationSecurityConfiguration {
+    private static final Set<String> REQUIRED_CLAIMS = new HashSet<>(Arrays.asList("sub", "iss", "aud", "nbf", "iat", "exp", "jti"));
+
+    private final NiFiProperties niFiProperties;
+
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final StateManagerProvider stateManagerProvider;
+
+    @Autowired
+    public JwtAuthenticationSecurityConfiguration(
+            final NiFiProperties niFiProperties,
+            final Authorizer authorizer,
+            final IdpUserGroupService idpUserGroupService,
+            final StateManagerProvider stateManagerProvider
+    ) {
+        this.niFiProperties = niFiProperties;
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.stateManagerProvider = stateManagerProvider;
+    }
+
+    @Bean
+    public JwtAuthenticationProvider jwtAuthenticationProvider() {
+        final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtDecoder());
+        jwtAuthenticationProvider.setJwtAuthenticationConverter(jwtAuthenticationConverter());
+        return jwtAuthenticationProvider;
+    }
+
+    @Bean
+    public JwtDecoder jwtDecoder() {
+        final NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor());
+        final OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
+                JwtValidators.createDefault(),
+                jwtRevocationValidator()
+        );
+        jwtDecoder.setJwtValidator(jwtValidator);
+        return jwtDecoder;
+    }
+
+    @Bean
+    public OAuth2TokenValidator<Jwt> jwtRevocationValidator() {
+        return new JwtRevocationValidator(jwtRevocationService());
+    }
+
+    @Bean
+    public JwtRevocationService jwtRevocationService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardJwtRevocationService.class.getName());
+        return new StandardJwtRevocationService(stateManager);
+    }
+
+    @Bean
+    public JwtLogoutListener jwtLogoutListener() {
+        return new StandardJwtLogoutListener(jwtDecoder(), jwtRevocationService());
+    }
+
+    @Bean
+    public JWTProcessor<SecurityContext> jwtProcessor() {
+        final DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
+        jwtProcessor.setJWSKeySelector(jwsKeySelector());
+        jwtProcessor.setJWTClaimsSetVerifier(claimsSetVerifier());
+        return jwtProcessor;
+    }
+
+    @Bean
+    public JWSKeySelector<SecurityContext> jwsKeySelector() {
+        return new StandardJWSKeySelector<>(verificationKeySelector());
+    }
+
+    @Bean
+    public JWTClaimsSetVerifier<SecurityContext> claimsSetVerifier() {
+        return new DefaultJWTClaimsVerifier<>(null, REQUIRED_CLAIMS);
+    }
+
+    @Bean
+    public StandardJwtAuthenticationConverter jwtAuthenticationConverter() {
+        return new StandardJwtAuthenticationConverter(authorizer, idpUserGroupService, niFiProperties);
+    }
+
+    @Bean
+    public BearerTokenProvider bearerTokenProvider() {
+        return new StandardBearerTokenProvider(jwsSignerProvider());
+    }
+
+    @Bean
+    public StandardJwsSignerProvider jwsSignerProvider() {
+        return new StandardJwsSignerProvider(verificationKeySelector());
+    }
+
+    @Bean
+    public StandardVerificationKeySelector verificationKeySelector() {
+        return new StandardVerificationKeySelector(verificationKeyService(), niFiProperties.getSecurityUserJwsKeyRotationPeriod());
+    }
+
+    @Bean
+    public VerificationKeyService verificationKeyService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardVerificationKeyService.class.getName());
+        return new StandardVerificationKeyService(stateManager);
+    }
+
+    @Bean
+    public KeyGenerationCommand keyGenerationCommand() {
+        final KeyGenerationCommand command = new KeyGenerationCommand(jwsSignerProvider(), verificationKeySelector());
+        commandScheduler().scheduleAtFixedRate(command, niFiProperties.getSecurityUserJwsKeyRotationPeriod());

Review comment:
       The `Duration.parse()` method should have thrown a more detailed message for an invalid duration, and that seems to be the best approach as opposed to implementing additional validation. Unfortunately the stack traces for configuration errors related to Spring Framework beans have multiple levels, but that impacts a number of properties.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] greyp9 commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
greyp9 commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r683866639



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/revocation/JwtRevocationService.java
##########
@@ -14,36 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.admin.service;
+package org.apache.nifi.web.security.jwt.revocation;
 
-import org.apache.nifi.key.Key;
+import java.time.Instant;
 
 /**
- * Manages NiFi user keys.
+ * JSON Web Token Revocation Service
  */
-public interface KeyService {
-
+public interface JwtRevocationService {
     /**
-     * Gets a key for the specified user identity. Returns null if the user has not had a key issued
-     *
-     * @param id The key id
-     * @return The key or null
+     * Delete Expired Revocations
      */
-    Key getKey(int id);
+    void deleteExpired();
 
     /**
-     * Gets a key for the specified user identity. If a key does not exist, one will be created.
+     * Is JSON Web Token Identifier Revoked
      *
-     * @param identity The user identity
-     * @return The key
-     * @throws AdministrationException if it failed to get/create the key
+     * @param id JSON Web Token Identifier
+     * @return Revoked Status
      */
-    Key getOrCreateKey(String identity);
+    boolean isRevoked(String id);
 
     /**
-     * Deletes keys for the specified identity.
+     * Set Revoked Status using JSON Web Token Identifier
      *
-     * @param keyId The user's key ID
+     * @param id JSON Web Token Identifier
+     * @param expiration Expiration of Revocation Status

Review comment:
       Got it; thanks.  So maybe the javadoc comment could reflect that somehow?  Suggestion:
   
   > retention period for revocation status record 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r683926529



##########
File path: nifi-docs/src/main/asciidoc/administration-guide.adoc
##########
@@ -489,6 +489,28 @@ To enable authentication via Apache Knox the following properties must be config
 this listing. The audience that is populated in the token can be configured in Knox.
 |==================================================================================================================================================
 
+[[json_web_token]]
+=== JSON Web Tokens
+
+NiFi uses JSON Web Tokens to provide authenticated access after the initial login process. Generated JSON Web Tokens include the authenticated user identity
+as well as the issuer and expiration from the configured Login Identity Provider.
+
+NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the `RS512` algorithm for JSON Web Signatures. The system stores RSA

Review comment:
       Thanks for the follow up and background reference, that is helpful to know.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680401530



##########
File path: nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
##########
@@ -170,6 +171,7 @@
     public static final String SECURITY_GROUP_MAPPING_PATTERN_PREFIX = "nifi.security.group.mapping.pattern.";
     public static final String SECURITY_GROUP_MAPPING_VALUE_PREFIX = "nifi.security.group.mapping.value.";
     public static final String SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX = "nifi.security.group.mapping.transform.";
+    public static final String SECURITY_USER_JWS_KEY_ROTATION_PERIOD = "nifi.security.user.jws.key.rotation.period";

Review comment:
       There may be some value in leaving it as an internal configuration property, but given that the default distribution includes declared property values, adding this with the default value makes sense.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] greyp9 commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
greyp9 commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r683865470



##########
File path: nifi-docs/src/main/asciidoc/administration-guide.adoc
##########
@@ -489,6 +489,28 @@ To enable authentication via Apache Knox the following properties must be config
 this listing. The audience that is populated in the token can be configured in Knox.
 |==================================================================================================================================================
 
+[[json_web_token]]
+=== JSON Web Tokens
+
+NiFi uses JSON Web Tokens to provide authenticated access after the initial login process. Generated JSON Web Tokens include the authenticated user identity
+as well as the issuer and expiration from the configured Login Identity Provider.
+
+NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the `RS512` algorithm for JSON Web Signatures. The system stores RSA

Review comment:
       I see the recent industry movement to 3072-bit minimum RSA key lengths.  4096 is probably a good choice to future proof this implementation.  I have concerns about performance of key generation at startup on lower-end devices, but this is probably not a valid concern for NiFi.
   
   https://knowledge.digicert.com/alerts/code-signing-new-minimum-rsa-keysize.html




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] thenatog commented on pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
thenatog commented on pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#issuecomment-902051547


   Will merge.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] greyp9 commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
greyp9 commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680237283



##########
File path: nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
##########
@@ -354,6 +356,7 @@
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_TRUSTSTORE_STRATEGY = "JDK";
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_CONNECT_TIMEOUT = "30 secs";
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_READ_TIMEOUT = "30 secs";
+    private static final String DEFAULT_SECURITY_USER_JWS_KEY_ROTATION_PERIOD = "PT1H";

Review comment:
       Maybe usage of ISO 8601 intervals could be part of the NiFi 2.0 date/time effort? 

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/StandardVerificationKeySelector.java
##########
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key;
+
+import org.apache.nifi.web.security.jwt.jws.SigningKeyListener;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Key;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Standard Verification Key Selector implements listener interfaces for updating Key status
+ */
+public class StandardVerificationKeySelector implements SigningKeyListener, VerificationKeyListener, VerificationKeySelector {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardVerificationKeySelector.class);
+
+    private final VerificationKeyService verificationKeyService;
+
+    private final Duration keyRotationPeriod;
+
+    public StandardVerificationKeySelector(final VerificationKeyService verificationKeyService, final Duration keyRotationPeriod) {
+        this.verificationKeyService = Objects.requireNonNull(verificationKeyService, "Verification Key Service required");
+        this.keyRotationPeriod = Objects.requireNonNull(keyRotationPeriod, "Key Rotation Period required");
+    }
+
+    /**
+     * On Verification Key Generated persist encoded Key with expiration
+     *
+     * @param keyIdentifier Key Identifier
+     * @param key Key
+     */
+    @Override
+    public void onVerificationKeyGenerated(final String keyIdentifier, final Key key) {
+        final Instant expiration = Instant.now().plus(keyRotationPeriod);
+        verificationKeyService.save(keyIdentifier, key, expiration);
+        LOGGER.debug("Verification Key Saved [{}] Expiration [{}]", keyIdentifier, expiration);
+    }
+
+    /**
+     * Get Verification Keys
+     *
+     * @param keyIdentifier Key Identifier
+     * @return List of Keys
+     */
+    @Override
+    public List<? extends Key> getVerificationKeys(final String keyIdentifier) {
+        final Optional<Key> key = verificationKeyService.findById(keyIdentifier);
+        final List<? extends Key> keys = key.map(Collections::singletonList).orElse(Collections.emptyList());
+        LOGGER.debug("Key Identifier [{}] Verification Keys Found [{}]", keyIdentifier, keys.size());
+        return keys;
+    }
+
+    /**
+     * On Signing Key Used set new expiration

Review comment:
       The intent is to keep the signing key alive as long as it is being used?

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/revocation/JwtRevocationService.java
##########
@@ -14,36 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.admin.service;
+package org.apache.nifi.web.security.jwt.revocation;
 
-import org.apache.nifi.key.Key;
+import java.time.Instant;
 
 /**
- * Manages NiFi user keys.
+ * JSON Web Token Revocation Service
  */
-public interface KeyService {
-
+public interface JwtRevocationService {
     /**
-     * Gets a key for the specified user identity. Returns null if the user has not had a key issued
-     *
-     * @param id The key id
-     * @return The key or null
+     * Delete Expired Revocations
      */
-    Key getKey(int id);
+    void deleteExpired();
 
     /**
-     * Gets a key for the specified user identity. If a key does not exist, one will be created.
+     * Is JSON Web Token Identifier Revoked
      *
-     * @param identity The user identity
-     * @return The key
-     * @throws AdministrationException if it failed to get/create the key
+     * @param id JSON Web Token Identifier
+     * @return Revoked Status
      */
-    Key getOrCreateKey(String identity);
+    boolean isRevoked(String id);
 
     /**
-     * Deletes keys for the specified identity.
+     * Set Revoked Status using JSON Web Token Identifier
      *
-     * @param keyId The user's key ID
+     * @param id JSON Web Token Identifier
+     * @param expiration Expiration of Revocation Status

Review comment:
       What is the purpose of the `expiration` parameter?  Are there cases where we would want a revoked JWT to become un-revoked?

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java
##########
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.provider;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jwt.JWTClaimsSet;
+import org.apache.nifi.web.security.jwt.jws.JwsSignerContainer;
+import org.apache.nifi.web.security.jwt.jws.JwsSignerProvider;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Standard Bearer Token Provider supports returning serialized and signed JSON Web Tokens
+ */
+public class StandardBearerTokenProvider implements BearerTokenProvider {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardBearerTokenProvider.class);
+
+    private static final String URL_ENCODED_CHARACTER_SET = StandardCharsets.UTF_8.name();
+
+    private final JwsSignerProvider jwsSignerProvider;
+
+    public StandardBearerTokenProvider(final JwsSignerProvider jwsSignerProvider) {
+        this.jwsSignerProvider = jwsSignerProvider;
+    }
+
+    /**
+     * Get Signed JSON Web Token using Login Authentication Token
+     *
+     * @param loginAuthenticationToken Login Authentication Token
+     * @return Serialized Signed JSON Web Token
+     */
+    @Override
+    public String getBearerToken(final LoginAuthenticationToken loginAuthenticationToken) {
+        Objects.requireNonNull(loginAuthenticationToken, "LoginAuthenticationToken required");

Review comment:
       Is the incoming authenticationToken pre-validated?

##########
File path: nifi-docs/src/main/asciidoc/administration-guide.adoc
##########
@@ -489,6 +489,28 @@ To enable authentication via Apache Knox the following properties must be config
 this listing. The audience that is populated in the token can be configured in Knox.
 |==================================================================================================================================================
 
+[[json_web_token]]
+=== JSON Web Tokens
+
+NiFi uses JSON Web Tokens to provide authenticated access after the initial login process. Generated JSON Web Tokens include the authenticated user identity
+as well as the issuer and expiration from the configured Login Identity Provider.
+
+NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the `RS512` algorithm for JSON Web Signatures. The system stores RSA

Review comment:
       Since this feature will involve the generation of a key pair on every startup, we should consider the tradeoff between security and performance.  The generation of a 4096 bit key pair can be expensive in a resource constrained environment.
   
   A smaller value might be a better all-purpose default.  Alternately, allowing configuration of this value might be useful.
   

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/resolver/StandardBearerTokenResolver.java
##########
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.resolver;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.web.security.http.SecurityCookieName;
+import org.apache.nifi.web.security.http.SecurityHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
+import org.springframework.web.util.WebUtils;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Bearer Token Resolver prefers the HTTP Authorization Header and then evaluates the Authorization Cookie when found

Review comment:
       > Bearer Token Resolver prefers the HTTP Authorization Header; when not found, the Authorization Cookie is evaluated...
   
   trying to convey that the cookie is ignored if the HTTP header is found




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r681270987



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/resolver/StandardBearerTokenResolver.java
##########
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.resolver;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.web.security.http.SecurityCookieName;
+import org.apache.nifi.web.security.http.SecurityHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
+import org.springframework.web.util.WebUtils;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Bearer Token Resolver prefers the HTTP Authorization Header and then evaluates the Authorization Cookie when found

Review comment:
       Thanks for the suggestion, about the following?
   
   > Bearer Token Resolver prefers the HTTP Authorization Header and then fall backs to the Authorization-Bearer cookie




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] gresockj commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
gresockj commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680403315



##########
File path: nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
##########
@@ -170,6 +171,7 @@
     public static final String SECURITY_GROUP_MAPPING_PATTERN_PREFIX = "nifi.security.group.mapping.pattern.";
     public static final String SECURITY_GROUP_MAPPING_VALUE_PREFIX = "nifi.security.group.mapping.value.";
     public static final String SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX = "nifi.security.group.mapping.transform.";
+    public static final String SECURITY_USER_JWS_KEY_ROTATION_PERIOD = "nifi.security.user.jws.key.rotation.period";

Review comment:
       Just to make sure I follow, are you suggesting leaving this property at its default in the framework so it's not exposed to the user?  That seems like a valid option, I just want to understand the intent.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r681267476



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java
##########
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.provider;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jwt.JWTClaimsSet;
+import org.apache.nifi.web.security.jwt.jws.JwsSignerContainer;
+import org.apache.nifi.web.security.jwt.jws.JwsSignerProvider;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Standard Bearer Token Provider supports returning serialized and signed JSON Web Tokens
+ */
+public class StandardBearerTokenProvider implements BearerTokenProvider {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardBearerTokenProvider.class);
+
+    private static final String URL_ENCODED_CHARACTER_SET = StandardCharsets.UTF_8.name();
+
+    private final JwsSignerProvider jwsSignerProvider;
+
+    public StandardBearerTokenProvider(final JwsSignerProvider jwsSignerProvider) {
+        this.jwsSignerProvider = jwsSignerProvider;
+    }
+
+    /**
+     * Get Signed JSON Web Token using Login Authentication Token
+     *
+     * @param loginAuthenticationToken Login Authentication Token
+     * @return Serialized Signed JSON Web Token
+     */
+    @Override
+    public String getBearerToken(final LoginAuthenticationToken loginAuthenticationToken) {
+        Objects.requireNonNull(loginAuthenticationToken, "LoginAuthenticationToken required");

Review comment:
       That is correct, calling classes authenticate provided credentials and return a `LoginAuthenticationToken` on success.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] gresockj commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
gresockj commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680498235



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/converter/StandardJwtAuthenticationConverter.java
##########
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.converter;
+
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
+import org.apache.nifi.authorization.util.UserGroupUtil;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Standard Converter from JSON Web Token to NiFi Authentication Token
+ */
+public class StandardJwtAuthenticationConverter implements Converter<Jwt, NiFiAuthenticationToken> {
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final List<IdentityMapping> identityMappings;
+
+    public StandardJwtAuthenticationConverter(final Authorizer authorizer, final IdpUserGroupService idpUserGroupService, final NiFiProperties properties) {
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.identityMappings = IdentityMappingUtil.getIdentityMappings(properties);
+    }
+
+    /**
+     * Convert JSON Web Token to NiFi Authentication Token
+     *
+     * @param jwt JSON Web Token
+     * @return NiFi Authentication Token
+     */
+    @Override
+    public NiFiAuthenticationToken convert(final Jwt jwt) {
+        final NiFiUser user = getUser(jwt);
+        return new NiFiAuthenticationToken(new NiFiUserDetails(user));
+    }
+
+    private NiFiUser getUser(final Jwt jwt) {
+        final String identity = IdentityMappingUtil.mapIdentity(jwt.getSubject(), identityMappings);
+
+        return new StandardNiFiUser.Builder()
+                .identity(identity)
+                .groups(UserGroupUtil.getUserGroups(authorizer, identity))
+                .identityProviderGroups(getIdentityProviderGroups(identity))
+                .build();
+    }
+
+    private Set<String> getIdentityProviderGroups(final String identity) {
+        return idpUserGroupService.getUserGroups(identity).stream()
+                .map(userGroup -> userGroup.getGroupName())

Review comment:
       Thanks for the update -- this now works as described.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680402551



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
##########
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.configuration;
+
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import com.nimbusds.jwt.proc.JWTProcessor;
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardJwtAuthenticationConverter;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.jws.StandardJwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.command.KeyExpirationCommand;
+import org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand;
+import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
+import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
+import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationValidator;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.command.RevocationExpirationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * JSON Web Token Configuration for Authentication Security
+ */
+@Configuration
+public class JwtAuthenticationSecurityConfiguration {
+    private static final Set<String> REQUIRED_CLAIMS = new HashSet<>(Arrays.asList("sub", "iss", "aud", "nbf", "iat", "exp", "jti"));
+
+    private final NiFiProperties niFiProperties;
+
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final StateManagerProvider stateManagerProvider;
+
+    @Autowired
+    public JwtAuthenticationSecurityConfiguration(
+            final NiFiProperties niFiProperties,
+            final Authorizer authorizer,
+            final IdpUserGroupService idpUserGroupService,
+            final StateManagerProvider stateManagerProvider
+    ) {
+        this.niFiProperties = niFiProperties;
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.stateManagerProvider = stateManagerProvider;
+    }
+
+    @Bean
+    public JwtAuthenticationProvider jwtAuthenticationProvider() {
+        final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtDecoder());
+        jwtAuthenticationProvider.setJwtAuthenticationConverter(jwtAuthenticationConverter());
+        return jwtAuthenticationProvider;
+    }
+
+    @Bean
+    public JwtDecoder jwtDecoder() {
+        final NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor());
+        final OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
+                JwtValidators.createDefault(),
+                jwtRevocationValidator()
+        );
+        jwtDecoder.setJwtValidator(jwtValidator);
+        return jwtDecoder;
+    }
+
+    @Bean
+    public OAuth2TokenValidator<Jwt> jwtRevocationValidator() {
+        return new JwtRevocationValidator(jwtRevocationService());
+    }
+
+    @Bean
+    public JwtRevocationService jwtRevocationService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardJwtRevocationService.class.getName());
+        return new StandardJwtRevocationService(stateManager);
+    }
+
+    @Bean
+    public JwtLogoutListener jwtLogoutListener() {
+        return new StandardJwtLogoutListener(jwtDecoder(), jwtRevocationService());
+    }
+
+    @Bean
+    public JWTProcessor<SecurityContext> jwtProcessor() {
+        final DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
+        jwtProcessor.setJWSKeySelector(jwsKeySelector());
+        jwtProcessor.setJWTClaimsSetVerifier(claimsSetVerifier());
+        return jwtProcessor;
+    }
+
+    @Bean
+    public JWSKeySelector<SecurityContext> jwsKeySelector() {
+        return new StandardJWSKeySelector<>(verificationKeySelector());
+    }
+
+    @Bean
+    public JWTClaimsSetVerifier<SecurityContext> claimsSetVerifier() {
+        return new DefaultJWTClaimsVerifier<>(null, REQUIRED_CLAIMS);
+    }
+
+    @Bean
+    public StandardJwtAuthenticationConverter jwtAuthenticationConverter() {
+        return new StandardJwtAuthenticationConverter(authorizer, idpUserGroupService, niFiProperties);
+    }
+
+    @Bean
+    public BearerTokenProvider bearerTokenProvider() {
+        return new StandardBearerTokenProvider(jwsSignerProvider());
+    }
+
+    @Bean
+    public StandardJwsSignerProvider jwsSignerProvider() {
+        return new StandardJwsSignerProvider(verificationKeySelector());
+    }
+
+    @Bean
+    public StandardVerificationKeySelector verificationKeySelector() {
+        return new StandardVerificationKeySelector(verificationKeyService(), niFiProperties.getSecurityUserJwsKeyRotationPeriod());

Review comment:
       It should be an inexpensive call to parse the property, but given the potential for syntax issues, setting an instance variable in the configuration constructor should help.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680401912



##########
File path: nifi-docs/src/main/asciidoc/administration-guide.adoc
##########
@@ -489,6 +489,28 @@ To enable authentication via Apache Knox the following properties must be config
 this listing. The audience that is populated in the token can be configured in Knox.
 |==================================================================================================================================================
 
+[[json_web_token]]
+=== JSON Web Tokens
+
+NiFi uses JSON Web Tokens to provide authenticated access after the initial login process. Generated JSON Web Tokens include the authenticated user identity
+as well as the issuer and expiration from the configured Login Identity Provider.
+
+NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the `RS512` algorithm for JSON Web Signatures. The system stores RSA
+Public Keys using the configured local State Provider and retains the RSA Private Key in memory. This approach supports signature verification
+for the expiration configured in the Login Identity Provider without persisting the private key.
+
+JSON Web Token support includes revocation on logout using JSON Web Token Identifiers. The system denies access for expired tokens based on the
+Login Identity Provider configuration, but revocation invalidates the token prior to expiration. The system stores revoked identifiers using the

Review comment:
       Thanks for pointing out the detail of the `SingleUserLoginIdentityProvider`. The documentation is generic to cover both explicit configuration values or internal default settings, which are specific to the provider implementation. In light that, do you think the current word is sufficient? Perhaps a separate PR to update the Single User authentication section would be helpful.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r681266804



##########
File path: nifi-docs/src/main/asciidoc/administration-guide.adoc
##########
@@ -489,6 +489,28 @@ To enable authentication via Apache Knox the following properties must be config
 this listing. The audience that is populated in the token can be configured in Knox.
 |==================================================================================================================================================
 
+[[json_web_token]]
+=== JSON Web Tokens
+
+NiFi uses JSON Web Tokens to provide authenticated access after the initial login process. Generated JSON Web Tokens include the authenticated user identity
+as well as the issuer and expiration from the configured Login Identity Provider.
+
+NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the `RS512` algorithm for JSON Web Signatures. The system stores RSA

Review comment:
       The current implementation uses a hard-coded setting for the key pair size to avoid potential misconfiguration, however, it may be more than necessary in some instances. Given the other resource constraints on a running system, this does not seem to be too expensive for most deployments.  An alternative could be to make this configurable, but there is a tradeoff between having too many settings and sensible defaults. What do you think?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] gresockj commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
gresockj commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680166368



##########
File path: nifi-docs/src/main/asciidoc/administration-guide.adoc
##########
@@ -489,6 +489,28 @@ To enable authentication via Apache Knox the following properties must be config
 this listing. The audience that is populated in the token can be configured in Knox.
 |==================================================================================================================================================
 
+[[json_web_token]]
+=== JSON Web Tokens
+
+NiFi uses JSON Web Tokens to provide authenticated access after the initial login process. Generated JSON Web Tokens include the authenticated user identity
+as well as the issuer and expiration from the configured Login Identity Provider.
+
+NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the `RS512` algorithm for JSON Web Signatures. The system stores RSA
+Public Keys using the configured local State Provider and retains the RSA Private Key in memory. This approach supports signature verification
+for the expiration configured in the Login Identity Provider without persisting the private key.
+
+JSON Web Token support includes revocation on logout using JSON Web Token Identifiers. The system denies access for expired tokens based on the
+Login Identity Provider configuration, but revocation invalidates the token prior to expiration. The system stores revoked identifiers using the

Review comment:
       I noticed that the SingleUserLoginIdentityProvider is hard-coded to 8 hours rather than being configured in login-identity-provider.xml as this implies.  I think that's appropriate for this provider, but you may want to clarify here.  It may also be useful to add the 8-hour expiration time in the `login-identity-providers.xml` comments.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] gresockj commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
gresockj commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680166368



##########
File path: nifi-docs/src/main/asciidoc/administration-guide.adoc
##########
@@ -489,6 +489,28 @@ To enable authentication via Apache Knox the following properties must be config
 this listing. The audience that is populated in the token can be configured in Knox.
 |==================================================================================================================================================
 
+[[json_web_token]]
+=== JSON Web Tokens
+
+NiFi uses JSON Web Tokens to provide authenticated access after the initial login process. Generated JSON Web Tokens include the authenticated user identity
+as well as the issuer and expiration from the configured Login Identity Provider.
+
+NiFi uses generated RSA Key Pairs with a key size of 4096 bits to support the `RS512` algorithm for JSON Web Signatures. The system stores RSA
+Public Keys using the configured local State Provider and retains the RSA Private Key in memory. This approach supports signature verification
+for the expiration configured in the Login Identity Provider without persisting the private key.
+
+JSON Web Token support includes revocation on logout using JSON Web Token Identifiers. The system denies access for expired tokens based on the
+Login Identity Provider configuration, but revocation invalidates the token prior to expiration. The system stores revoked identifiers using the

Review comment:
       I noticed that the SingleUserLoginIdentityProvider is hard-coded to 8 hours rather than being configured in login-identity-provider.xml as this implies.  I think that's appropriate for this provider, but you may want to clarify here.

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/converter/StandardJwtAuthenticationConverter.java
##########
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.converter;
+
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
+import org.apache.nifi.authorization.util.UserGroupUtil;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Standard Converter from JSON Web Token to NiFi Authentication Token
+ */
+public class StandardJwtAuthenticationConverter implements Converter<Jwt, NiFiAuthenticationToken> {
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final List<IdentityMapping> identityMappings;
+
+    public StandardJwtAuthenticationConverter(final Authorizer authorizer, final IdpUserGroupService idpUserGroupService, final NiFiProperties properties) {
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.identityMappings = IdentityMappingUtil.getIdentityMappings(properties);
+    }
+
+    /**
+     * Convert JSON Web Token to NiFi Authentication Token
+     *
+     * @param jwt JSON Web Token
+     * @return NiFi Authentication Token
+     */
+    @Override
+    public NiFiAuthenticationToken convert(final Jwt jwt) {
+        final NiFiUser user = getUser(jwt);
+        return new NiFiAuthenticationToken(new NiFiUserDetails(user));
+    }
+
+    private NiFiUser getUser(final Jwt jwt) {
+        final String identity = IdentityMappingUtil.mapIdentity(jwt.getSubject(), identityMappings);
+
+        return new StandardNiFiUser.Builder()
+                .identity(identity)
+                .groups(UserGroupUtil.getUserGroups(authorizer, identity))
+                .identityProviderGroups(getIdentityProviderGroups(identity))
+                .build();
+    }
+
+    private Set<String> getIdentityProviderGroups(final String identity) {
+        return idpUserGroupService.getUserGroups(identity).stream()
+                .map(userGroup -> userGroup.getGroupName())

Review comment:
       Minor suggestion:
   ```suggestion
                   .map(IdpUserGroup::getGroupName)
   ```

##########
File path: nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
##########
@@ -170,6 +171,7 @@
     public static final String SECURITY_GROUP_MAPPING_PATTERN_PREFIX = "nifi.security.group.mapping.pattern.";
     public static final String SECURITY_GROUP_MAPPING_VALUE_PREFIX = "nifi.security.group.mapping.value.";
     public static final String SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX = "nifi.security.group.mapping.transform.";
+    public static final String SECURITY_USER_JWS_KEY_ROTATION_PERIOD = "nifi.security.user.jws.key.rotation.period";

Review comment:
       This should also be configured in `nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties` and `nifi-assembly/pom.xml`

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/service/StandardVerificationKeyService.java
##########
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Standard Verification Key Service implemented using State Manager
+ */
+public class StandardVerificationKeyService implements VerificationKeyService {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardVerificationKeyService.class);
+
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());
+
+    private static final Scope SCOPE = Scope.LOCAL;
+
+    private final StateManager stateManager;
+
+    public StandardVerificationKeyService(final StateManager stateManager) {
+        this.stateManager = stateManager;
+    }
+
+    /**
+     * Find Key using specified Key Identifier
+     *
+     * @param id Key Identifier
+     * @return Optional Key
+     */
+    @Override
+    public Optional<Key> findById(final String id) {
+        final Optional<String> serializedKey = findSerializedKey(id);
+        return serializedKey.map(this::getVerificationKey).map(this::getKey);
+    }
+
+    /**
+     * Delete Expired Verification Keys is synchronized to avoid losing updates from other methods
+     */
+    @Override
+    public synchronized void deleteExpired() {
+        final Map<String, String> state = getStateMap().toMap();
+
+        final Instant now = Instant.now();
+        final Map<String, String> updatedState = state
+                .values()
+                .stream()
+                .map(this::getVerificationKey)
+                .filter(verificationKey -> verificationKey.getExpiration().isAfter(now))
+                .collect(Collectors.toMap(VerificationKey::getId, this::serializeVerificationKey));
+
+        if (updatedState.equals(state)) {
+            LOGGER.debug("Expired Verification Keys not found");
+        } else {
+            try {
+                stateManager.setState(updatedState, SCOPE);
+            } catch (final IOException e) {
+                throw new UncheckedIOException("Delete Expired Verification Keys Failed", e);
+            }
+            LOGGER.debug("Delete Expired Verification Keys: Before [{}] After [{}]", state.size(), updatedState.size());
+        }
+    }
+
+    /**
+     * Save Verification Key
+     *
+     * @param id Key Identifier
+     * @param key Key
+     * @param expiration Expiration
+     */
+    @Override
+    public void save(final String id, final Key key, final Instant expiration) {
+        final VerificationKey verificationKey = new VerificationKey();
+        verificationKey.setId(id);
+        verificationKey.setEncoded(key.getEncoded());
+        verificationKey.setAlgorithm(key.getAlgorithm());
+        verificationKey.setExpiration(expiration);
+        setVerificationKey(verificationKey);
+    }
+
+    /**
+     * Set Expiration of Verification Key when found
+     *
+     * @param id Key Identifier
+     * @param expiration Expiration
+     */
+    @Override
+    public void setExpiration(final String id, final Instant expiration) {
+        final Optional<String> serializedKey = findSerializedKey(id);
+        if (serializedKey.isPresent()) {
+            final VerificationKey verificationKey = getVerificationKey(serializedKey.get());
+            verificationKey.setExpiration(expiration);
+            setVerificationKey(verificationKey);
+        }
+    }
+
+    /**
+     * Set Verification Key is synchronized to avoid competing updates to the State Map
+     *
+     * @param verificationKey Verification Key to be stored
+     */
+    private synchronized void setVerificationKey(final VerificationKey verificationKey) {
+        try {
+            final String serialized = serializeVerificationKey(verificationKey);
+            final Map<String, String> state = new HashMap<>(getStateMap().toMap());
+            state.put(verificationKey.getId(), serialized);
+            stateManager.setState(state, SCOPE);
+        } catch (final IOException e) {
+            throw new UncheckedIOException("Set Verification Key State Failed", e);
+        }
+        LOGGER.debug("Stored Verification Key [{}] Expiration [{}]", verificationKey.getId(), verificationKey.getExpiration());
+    }
+
+    private Optional<String> findSerializedKey(final String id) {
+        final StateMap stateMap = getStateMap();
+        final String serializedKey = stateMap.get(id);
+        return serializedKey == null ? Optional.empty() : Optional.of(serializedKey);

Review comment:
       Could this be:
   ```suggestion
           return Optional.ofNullable(stateMap.get(id));
   ```

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/jws/JwsSignerProvider.java
##########
@@ -14,31 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.admin.service.action;
+package org.apache.nifi.web.security.jwt.jws;
 
-import org.apache.nifi.admin.dao.DAOFactory;
-import org.apache.nifi.admin.dao.DataAccessException;
-import org.apache.nifi.admin.dao.KeyDAO;
+import java.time.Instant;
 
 /**
- *
+ * JSON Web Signature Signer Provider supports accessing signer and identifier properties
  */
-public class DeleteKeyAction implements AdministrationAction<Integer> {

Review comment:
       A bit perplexing which classes GitHub sometimes thinks are the same, no? 🙂 

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/VerificationKeySelector.java
##########
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key;
+
+import java.security.Key;
+import java.util.List;
+
+/**
+ * Verification Key Selector returns a List of java.security.Key objects for signature verification
+ */
+public interface VerificationKeySelector {
+    /**
+     * Get Verification Keys for Key Identifier
+     *
+     * @param keyIdentifier Key Identifier
+     * @return List of Keys

Review comment:
       I liked the description from from `StandardJWSKeySelector`:
   ```suggestion
        * @return List of found java.security.Key objects
   ```

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
##########
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.configuration;
+
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import com.nimbusds.jwt.proc.JWTProcessor;
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardJwtAuthenticationConverter;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.jws.StandardJwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.command.KeyExpirationCommand;
+import org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand;
+import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
+import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
+import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationValidator;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.command.RevocationExpirationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * JSON Web Token Configuration for Authentication Security
+ */
+@Configuration
+public class JwtAuthenticationSecurityConfiguration {
+    private static final Set<String> REQUIRED_CLAIMS = new HashSet<>(Arrays.asList("sub", "iss", "aud", "nbf", "iat", "exp", "jti"));
+
+    private final NiFiProperties niFiProperties;
+
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final StateManagerProvider stateManagerProvider;
+
+    @Autowired
+    public JwtAuthenticationSecurityConfiguration(
+            final NiFiProperties niFiProperties,
+            final Authorizer authorizer,
+            final IdpUserGroupService idpUserGroupService,
+            final StateManagerProvider stateManagerProvider
+    ) {
+        this.niFiProperties = niFiProperties;
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.stateManagerProvider = stateManagerProvider;
+    }
+
+    @Bean
+    public JwtAuthenticationProvider jwtAuthenticationProvider() {
+        final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtDecoder());
+        jwtAuthenticationProvider.setJwtAuthenticationConverter(jwtAuthenticationConverter());
+        return jwtAuthenticationProvider;
+    }
+
+    @Bean
+    public JwtDecoder jwtDecoder() {
+        final NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor());
+        final OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
+                JwtValidators.createDefault(),
+                jwtRevocationValidator()
+        );
+        jwtDecoder.setJwtValidator(jwtValidator);
+        return jwtDecoder;
+    }
+
+    @Bean
+    public OAuth2TokenValidator<Jwt> jwtRevocationValidator() {
+        return new JwtRevocationValidator(jwtRevocationService());
+    }
+
+    @Bean
+    public JwtRevocationService jwtRevocationService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardJwtRevocationService.class.getName());
+        return new StandardJwtRevocationService(stateManager);
+    }
+
+    @Bean
+    public JwtLogoutListener jwtLogoutListener() {
+        return new StandardJwtLogoutListener(jwtDecoder(), jwtRevocationService());
+    }
+
+    @Bean
+    public JWTProcessor<SecurityContext> jwtProcessor() {
+        final DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
+        jwtProcessor.setJWSKeySelector(jwsKeySelector());
+        jwtProcessor.setJWTClaimsSetVerifier(claimsSetVerifier());
+        return jwtProcessor;
+    }
+
+    @Bean
+    public JWSKeySelector<SecurityContext> jwsKeySelector() {
+        return new StandardJWSKeySelector<>(verificationKeySelector());
+    }
+
+    @Bean
+    public JWTClaimsSetVerifier<SecurityContext> claimsSetVerifier() {
+        return new DefaultJWTClaimsVerifier<>(null, REQUIRED_CLAIMS);
+    }
+
+    @Bean
+    public StandardJwtAuthenticationConverter jwtAuthenticationConverter() {
+        return new StandardJwtAuthenticationConverter(authorizer, idpUserGroupService, niFiProperties);
+    }
+
+    @Bean
+    public BearerTokenProvider bearerTokenProvider() {
+        return new StandardBearerTokenProvider(jwsSignerProvider());
+    }
+
+    @Bean
+    public StandardJwsSignerProvider jwsSignerProvider() {
+        return new StandardJwsSignerProvider(verificationKeySelector());
+    }
+
+    @Bean
+    public StandardVerificationKeySelector verificationKeySelector() {
+        return new StandardVerificationKeySelector(verificationKeyService(), niFiProperties.getSecurityUserJwsKeyRotationPeriod());

Review comment:
       Do you think it would be appropriate to store the value of `niFiProperties.getSecurityUserJwsKeyRotationPeriod()` in an instance variable so it can be reused among these methods?

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
##########
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.configuration;
+
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import com.nimbusds.jwt.proc.JWTProcessor;
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardJwtAuthenticationConverter;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.jws.StandardJwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.command.KeyExpirationCommand;
+import org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand;
+import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
+import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
+import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationValidator;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.command.RevocationExpirationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * JSON Web Token Configuration for Authentication Security
+ */
+@Configuration
+public class JwtAuthenticationSecurityConfiguration {
+    private static final Set<String> REQUIRED_CLAIMS = new HashSet<>(Arrays.asList("sub", "iss", "aud", "nbf", "iat", "exp", "jti"));
+
+    private final NiFiProperties niFiProperties;
+
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final StateManagerProvider stateManagerProvider;
+
+    @Autowired
+    public JwtAuthenticationSecurityConfiguration(
+            final NiFiProperties niFiProperties,
+            final Authorizer authorizer,
+            final IdpUserGroupService idpUserGroupService,
+            final StateManagerProvider stateManagerProvider
+    ) {
+        this.niFiProperties = niFiProperties;
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.stateManagerProvider = stateManagerProvider;
+    }
+
+    @Bean
+    public JwtAuthenticationProvider jwtAuthenticationProvider() {
+        final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtDecoder());
+        jwtAuthenticationProvider.setJwtAuthenticationConverter(jwtAuthenticationConverter());
+        return jwtAuthenticationProvider;
+    }
+
+    @Bean
+    public JwtDecoder jwtDecoder() {
+        final NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor());
+        final OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
+                JwtValidators.createDefault(),
+                jwtRevocationValidator()
+        );
+        jwtDecoder.setJwtValidator(jwtValidator);
+        return jwtDecoder;
+    }
+
+    @Bean
+    public OAuth2TokenValidator<Jwt> jwtRevocationValidator() {
+        return new JwtRevocationValidator(jwtRevocationService());
+    }
+
+    @Bean
+    public JwtRevocationService jwtRevocationService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardJwtRevocationService.class.getName());
+        return new StandardJwtRevocationService(stateManager);
+    }
+
+    @Bean
+    public JwtLogoutListener jwtLogoutListener() {
+        return new StandardJwtLogoutListener(jwtDecoder(), jwtRevocationService());
+    }
+
+    @Bean
+    public JWTProcessor<SecurityContext> jwtProcessor() {
+        final DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
+        jwtProcessor.setJWSKeySelector(jwsKeySelector());
+        jwtProcessor.setJWTClaimsSetVerifier(claimsSetVerifier());
+        return jwtProcessor;
+    }
+
+    @Bean
+    public JWSKeySelector<SecurityContext> jwsKeySelector() {
+        return new StandardJWSKeySelector<>(verificationKeySelector());
+    }
+
+    @Bean
+    public JWTClaimsSetVerifier<SecurityContext> claimsSetVerifier() {
+        return new DefaultJWTClaimsVerifier<>(null, REQUIRED_CLAIMS);
+    }
+
+    @Bean
+    public StandardJwtAuthenticationConverter jwtAuthenticationConverter() {
+        return new StandardJwtAuthenticationConverter(authorizer, idpUserGroupService, niFiProperties);
+    }
+
+    @Bean
+    public BearerTokenProvider bearerTokenProvider() {
+        return new StandardBearerTokenProvider(jwsSignerProvider());
+    }
+
+    @Bean
+    public StandardJwsSignerProvider jwsSignerProvider() {
+        return new StandardJwsSignerProvider(verificationKeySelector());
+    }
+
+    @Bean
+    public StandardVerificationKeySelector verificationKeySelector() {
+        return new StandardVerificationKeySelector(verificationKeyService(), niFiProperties.getSecurityUserJwsKeyRotationPeriod());
+    }
+
+    @Bean
+    public VerificationKeyService verificationKeyService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardVerificationKeyService.class.getName());
+        return new StandardVerificationKeyService(stateManager);
+    }
+
+    @Bean
+    public KeyGenerationCommand keyGenerationCommand() {
+        final KeyGenerationCommand command = new KeyGenerationCommand(jwsSignerProvider(), verificationKeySelector());
+        commandScheduler().scheduleAtFixedRate(command, niFiProperties.getSecurityUserJwsKeyRotationPeriod());

Review comment:
       I noticed that if you specify an invalid key rotation period like `PT0M`, the resulting error message could be more friendly:
   ```
   Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand]: Factory method 'keyGenerationCommand' threw exception; nested exception is java.lang.IllegalArgumentException
   	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
   	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
   	... 53 common frames omitted
   Caused by: java.lang.IllegalArgumentException: null
   	at java.util.concurrent.ScheduledThreadPoolExecutor.scheduleAtFixedRate(ScheduledThreadPoolExecutor.java:565)
   	at org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler.scheduleAtFixedRate(ThreadPoolTaskScheduler.java:407)
   ```
   Perhaps `NiFiProperties#getSecurityUserJwsKeyRotationPeriod` could validate the value and include the property name in the error message if it's invalid?

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/service/StandardVerificationKeyService.java
##########
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Standard Verification Key Service implemented using State Manager
+ */
+public class StandardVerificationKeyService implements VerificationKeyService {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardVerificationKeyService.class);
+
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());
+
+    private static final Scope SCOPE = Scope.LOCAL;
+
+    private final StateManager stateManager;
+
+    public StandardVerificationKeyService(final StateManager stateManager) {
+        this.stateManager = stateManager;
+    }
+
+    /**
+     * Find Key using specified Key Identifier
+     *
+     * @param id Key Identifier
+     * @return Optional Key
+     */
+    @Override
+    public Optional<Key> findById(final String id) {
+        final Optional<String> serializedKey = findSerializedKey(id);
+        return serializedKey.map(this::getVerificationKey).map(this::getKey);
+    }
+
+    /**
+     * Delete Expired Verification Keys is synchronized to avoid losing updates from other methods
+     */
+    @Override
+    public synchronized void deleteExpired() {
+        final Map<String, String> state = getStateMap().toMap();
+
+        final Instant now = Instant.now();
+        final Map<String, String> updatedState = state
+                .values()
+                .stream()
+                .map(this::getVerificationKey)
+                .filter(verificationKey -> verificationKey.getExpiration().isAfter(now))
+                .collect(Collectors.toMap(VerificationKey::getId, this::serializeVerificationKey));
+
+        if (updatedState.equals(state)) {
+            LOGGER.debug("Expired Verification Keys not found");
+        } else {
+            try {
+                stateManager.setState(updatedState, SCOPE);
+            } catch (final IOException e) {
+                throw new UncheckedIOException("Delete Expired Verification Keys Failed", e);
+            }
+            LOGGER.debug("Delete Expired Verification Keys: Before [{}] After [{}]", state.size(), updatedState.size());

Review comment:
       This was slightly cryptic to me when I saw it in the logs.  What do you think about:
   ```suggestion
               LOGGER.debug("Delete Expired Verification Keys: Before [{}] keys, After [{}] keys", state.size(), updatedState.size());
   ```

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/jws/JwsSignerProvider.java
##########
@@ -14,31 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.admin.service.action;
+package org.apache.nifi.web.security.jwt.jws;
 
-import org.apache.nifi.admin.dao.DAOFactory;
-import org.apache.nifi.admin.dao.DataAccessException;
-import org.apache.nifi.admin.dao.KeyDAO;
+import java.time.Instant;
 
 /**
- *
+ * JSON Web Signature Signer Provider supports accessing signer and identifier properties
  */
-public class DeleteKeyAction implements AdministrationAction<Integer> {
-
-    private final Integer keyId;
-
+public interface JwsSignerProvider {
     /**
-     * Creates a new transactions for deleting keys for a specified user based on their keyId.
+     * Get JSON Web Signature Signer Container
      *
-     * @param keyId user identity
+     * @param expiration JSON Web Token Expiration

Review comment:
       It's not clear from the method name or description here that the expiration argument is intended to set the expiration.  Perhaps:
   ```suggestion
        * @param expiration JSON Web Token Expiration to set
   ```

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/revocation/StandardJwtRevocationService.java
##########
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.revocation;
+
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Standard JSON Web Token Revocation Service using State Manager
+ */
+public class StandardJwtRevocationService implements JwtRevocationService {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardJwtRevocationService.class);
+
+    private static final Scope SCOPE = Scope.LOCAL;
+
+    private final StateManager stateManager;
+
+    public StandardJwtRevocationService(final StateManager stateManager) {
+        this.stateManager = stateManager;
+    }
+
+    /**
+     * Delete Expired Revocations is synchronized is avoid losing updates from setRevoked()
+     */
+    @Override
+    public synchronized void deleteExpired() {
+        final Map<String, String> state = getStateMap().toMap();
+
+        final Instant now = Instant.now();
+        final Map<String, String> updatedState = state
+                .entrySet()
+                .stream()
+                .filter(entry -> Instant.parse(entry.getValue()).isAfter(now))
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        if (updatedState.equals(state)) {
+            LOGGER.debug("Expired Revocations not found");
+        } else {
+            try {
+                stateManager.setState(updatedState, SCOPE);
+            } catch (final IOException e) {
+                throw new UncheckedIOException("Delete Expired Revocations Failed", e);
+            }
+            LOGGER.debug("Delete Expired Revocations: Before [{}] After [{}]", state.size(), updatedState.size());

Review comment:
       I'm wondering if this and the "Delete Expired Verification Keys: Before..." message should be a higher log level than most of the others in this PR.  I could see a user wanting to see when revocations and verification keys were actually deleted, but not wanting to see every time a verification key was found or an expired revocation was not found.  What do you think?

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
##########
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.configuration;
+
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import com.nimbusds.jwt.proc.JWTProcessor;
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardJwtAuthenticationConverter;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.jws.StandardJwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.command.KeyExpirationCommand;
+import org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand;
+import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
+import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
+import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationValidator;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.command.RevocationExpirationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * JSON Web Token Configuration for Authentication Security
+ */
+@Configuration
+public class JwtAuthenticationSecurityConfiguration {
+    private static final Set<String> REQUIRED_CLAIMS = new HashSet<>(Arrays.asList("sub", "iss", "aud", "nbf", "iat", "exp", "jti"));
+
+    private final NiFiProperties niFiProperties;
+
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final StateManagerProvider stateManagerProvider;
+
+    @Autowired
+    public JwtAuthenticationSecurityConfiguration(
+            final NiFiProperties niFiProperties,
+            final Authorizer authorizer,
+            final IdpUserGroupService idpUserGroupService,
+            final StateManagerProvider stateManagerProvider
+    ) {
+        this.niFiProperties = niFiProperties;
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.stateManagerProvider = stateManagerProvider;
+    }
+
+    @Bean
+    public JwtAuthenticationProvider jwtAuthenticationProvider() {
+        final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtDecoder());
+        jwtAuthenticationProvider.setJwtAuthenticationConverter(jwtAuthenticationConverter());
+        return jwtAuthenticationProvider;
+    }
+
+    @Bean
+    public JwtDecoder jwtDecoder() {
+        final NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor());
+        final OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
+                JwtValidators.createDefault(),
+                jwtRevocationValidator()
+        );
+        jwtDecoder.setJwtValidator(jwtValidator);
+        return jwtDecoder;
+    }
+
+    @Bean
+    public OAuth2TokenValidator<Jwt> jwtRevocationValidator() {
+        return new JwtRevocationValidator(jwtRevocationService());
+    }
+
+    @Bean
+    public JwtRevocationService jwtRevocationService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardJwtRevocationService.class.getName());
+        return new StandardJwtRevocationService(stateManager);
+    }
+
+    @Bean
+    public JwtLogoutListener jwtLogoutListener() {
+        return new StandardJwtLogoutListener(jwtDecoder(), jwtRevocationService());
+    }
+
+    @Bean
+    public JWTProcessor<SecurityContext> jwtProcessor() {
+        final DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
+        jwtProcessor.setJWSKeySelector(jwsKeySelector());
+        jwtProcessor.setJWTClaimsSetVerifier(claimsSetVerifier());
+        return jwtProcessor;
+    }
+
+    @Bean
+    public JWSKeySelector<SecurityContext> jwsKeySelector() {
+        return new StandardJWSKeySelector<>(verificationKeySelector());
+    }
+
+    @Bean
+    public JWTClaimsSetVerifier<SecurityContext> claimsSetVerifier() {
+        return new DefaultJWTClaimsVerifier<>(null, REQUIRED_CLAIMS);
+    }
+
+    @Bean
+    public StandardJwtAuthenticationConverter jwtAuthenticationConverter() {
+        return new StandardJwtAuthenticationConverter(authorizer, idpUserGroupService, niFiProperties);
+    }
+
+    @Bean
+    public BearerTokenProvider bearerTokenProvider() {
+        return new StandardBearerTokenProvider(jwsSignerProvider());
+    }
+
+    @Bean
+    public StandardJwsSignerProvider jwsSignerProvider() {
+        return new StandardJwsSignerProvider(verificationKeySelector());
+    }
+
+    @Bean
+    public StandardVerificationKeySelector verificationKeySelector() {
+        return new StandardVerificationKeySelector(verificationKeyService(), niFiProperties.getSecurityUserJwsKeyRotationPeriod());
+    }
+
+    @Bean
+    public VerificationKeyService verificationKeyService() {
+        final StateManager stateManager = stateManagerProvider.getStateManager(StandardVerificationKeyService.class.getName());
+        return new StandardVerificationKeyService(stateManager);
+    }
+
+    @Bean
+    public KeyGenerationCommand keyGenerationCommand() {
+        final KeyGenerationCommand command = new KeyGenerationCommand(jwsSignerProvider(), verificationKeySelector());
+        commandScheduler().scheduleAtFixedRate(command, niFiProperties.getSecurityUserJwsKeyRotationPeriod());
+        return command;
+    }
+
+    @Bean
+    public KeyExpirationCommand keyExpirationCommand() {
+        final KeyExpirationCommand command = new KeyExpirationCommand(verificationKeyService());
+        commandScheduler().scheduleAtFixedRate(command, niFiProperties.getSecurityUserJwsKeyRotationPeriod());
+        return command;
+    }
+
+    @Bean
+    public RevocationExpirationCommand revocationExpirationCommand() {
+        final RevocationExpirationCommand command = new RevocationExpirationCommand(jwtRevocationService());
+        commandScheduler().scheduleAtFixedRate(command, niFiProperties.getSecurityUserJwsKeyRotationPeriod());

Review comment:
       In the unlikely scenario that the user configures the key rotation period to be longer than the login identity provider's authentication expiration, this wouldn't expire the revocation in time, right?  Perhaps that could be considered a user error, though.

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
##########
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.configuration;
+
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import com.nimbusds.jwt.proc.JWTProcessor;
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardJwtAuthenticationConverter;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.jws.StandardJwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.command.KeyExpirationCommand;
+import org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand;
+import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
+import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
+import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationValidator;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.command.RevocationExpirationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * JSON Web Token Configuration for Authentication Security
+ */
+@Configuration
+public class JwtAuthenticationSecurityConfiguration {
+    private static final Set<String> REQUIRED_CLAIMS = new HashSet<>(Arrays.asList("sub", "iss", "aud", "nbf", "iat", "exp", "jti"));

Review comment:
       Although developers familiar with JWT will likely know what these abbreviations are, what do you think about adding some private constants for others?  Or at least a comment including their meanings.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680402860



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/jws/JwsSignerProvider.java
##########
@@ -14,31 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.admin.service.action;
+package org.apache.nifi.web.security.jwt.jws;
 
-import org.apache.nifi.admin.dao.DAOFactory;
-import org.apache.nifi.admin.dao.DataAccessException;
-import org.apache.nifi.admin.dao.KeyDAO;
+import java.time.Instant;
 
 /**
- *
+ * JSON Web Signature Signer Provider supports accessing signer and identifier properties
  */
-public class DeleteKeyAction implements AdministrationAction<Integer> {

Review comment:
       Yes, it is confusing, apparently it is related to an internal Git configuration for the project.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680404259



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/revocation/StandardJwtRevocationService.java
##########
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.revocation;
+
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Standard JSON Web Token Revocation Service using State Manager
+ */
+public class StandardJwtRevocationService implements JwtRevocationService {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardJwtRevocationService.class);
+
+    private static final Scope SCOPE = Scope.LOCAL;
+
+    private final StateManager stateManager;
+
+    public StandardJwtRevocationService(final StateManager stateManager) {
+        this.stateManager = stateManager;
+    }
+
+    /**
+     * Delete Expired Revocations is synchronized is avoid losing updates from setRevoked()
+     */
+    @Override
+    public synchronized void deleteExpired() {
+        final Map<String, String> state = getStateMap().toMap();
+
+        final Instant now = Instant.now();
+        final Map<String, String> updatedState = state
+                .entrySet()
+                .stream()
+                .filter(entry -> Instant.parse(entry.getValue()).isAfter(now))
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        if (updatedState.equals(state)) {
+            LOGGER.debug("Expired Revocations not found");
+        } else {
+            try {
+                stateManager.setState(updatedState, SCOPE);
+            } catch (final IOException e) {
+                throw new UncheckedIOException("Delete Expired Revocations Failed", e);
+            }
+            LOGGER.debug("Delete Expired Revocations: Before [{}] After [{}]", state.size(), updatedState.size());

Review comment:
       That's a good question, but it seems better as a debug message than an information message. Key generation and expiration should be transparent to the user under normal circumstances, so leaving as a debug message seems sufficient as an option for troubleshooting.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r681270987



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/resolver/StandardBearerTokenResolver.java
##########
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.resolver;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.web.security.http.SecurityCookieName;
+import org.apache.nifi.web.security.http.SecurityHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
+import org.springframework.web.util.WebUtils;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Bearer Token Resolver prefers the HTTP Authorization Header and then evaluates the Authorization Cookie when found

Review comment:
       Thanks for the suggestion, what do you think about the following?
   
   > Bearer Token Resolver prefers the HTTP Authorization Header and then fall backs to the Authorization-Bearer cookie




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680403257



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/service/StandardVerificationKeyService.java
##########
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Standard Verification Key Service implemented using State Manager
+ */
+public class StandardVerificationKeyService implements VerificationKeyService {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardVerificationKeyService.class);
+
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());
+
+    private static final Scope SCOPE = Scope.LOCAL;
+
+    private final StateManager stateManager;
+
+    public StandardVerificationKeyService(final StateManager stateManager) {
+        this.stateManager = stateManager;
+    }
+
+    /**
+     * Find Key using specified Key Identifier
+     *
+     * @param id Key Identifier
+     * @return Optional Key
+     */
+    @Override
+    public Optional<Key> findById(final String id) {
+        final Optional<String> serializedKey = findSerializedKey(id);
+        return serializedKey.map(this::getVerificationKey).map(this::getKey);
+    }
+
+    /**
+     * Delete Expired Verification Keys is synchronized to avoid losing updates from other methods
+     */
+    @Override
+    public synchronized void deleteExpired() {
+        final Map<String, String> state = getStateMap().toMap();
+
+        final Instant now = Instant.now();
+        final Map<String, String> updatedState = state
+                .values()
+                .stream()
+                .map(this::getVerificationKey)
+                .filter(verificationKey -> verificationKey.getExpiration().isAfter(now))
+                .collect(Collectors.toMap(VerificationKey::getId, this::serializeVerificationKey));
+
+        if (updatedState.equals(state)) {
+            LOGGER.debug("Expired Verification Keys not found");
+        } else {
+            try {
+                stateManager.setState(updatedState, SCOPE);
+            } catch (final IOException e) {
+                throw new UncheckedIOException("Delete Expired Verification Keys Failed", e);
+            }
+            LOGGER.debug("Delete Expired Verification Keys: Before [{}] After [{}]", state.size(), updatedState.size());

Review comment:
       Thanks, will adjust the message.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] kevdoran commented on a change in pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
kevdoran commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r691268425



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       Just for my own understanding - is the jwt token value also used as the CSRF prevention token value?

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       This does not change existing functionality, so it is not a blocker to the PR, but just for my own understanding as I have not seen how NiFi does this - in NiFi, is the jwt token value also used as the CSRF prevention token value?

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       This does not change existing functionality, so it is not a blocker to the PR, but just for my own understanding as I have not seen how NiFi does this - in NiFi, is the JWT token value also used as the CSRF prevention token value?

##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       Thanks for confirming!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r681269241



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/revocation/JwtRevocationService.java
##########
@@ -14,36 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.admin.service;
+package org.apache.nifi.web.security.jwt.revocation;
 
-import org.apache.nifi.key.Key;
+import java.time.Instant;
 
 /**
- * Manages NiFi user keys.
+ * JSON Web Token Revocation Service
  */
-public interface KeyService {
-
+public interface JwtRevocationService {
     /**
-     * Gets a key for the specified user identity. Returns null if the user has not had a key issued
-     *
-     * @param id The key id
-     * @return The key or null
+     * Delete Expired Revocations
      */
-    Key getKey(int id);
+    void deleteExpired();
 
     /**
-     * Gets a key for the specified user identity. If a key does not exist, one will be created.
+     * Is JSON Web Token Identifier Revoked
      *
-     * @param identity The user identity
-     * @return The key
-     * @throws AdministrationException if it failed to get/create the key
+     * @param id JSON Web Token Identifier
+     * @return Revoked Status
      */
-    Key getOrCreateKey(String identity);
+    boolean isRevoked(String id);
 
     /**
-     * Deletes keys for the specified identity.
+     * Set Revoked Status using JSON Web Token Identifier
      *
-     * @param keyId The user's key ID
+     * @param id JSON Web Token Identifier
+     * @param expiration Expiration of Revocation Status

Review comment:
       The purpose of the expiration in this method is to track how long to keep the revocation status around. The expiration should match the expiration of the JWT. Revocation occurs when a user logs out of the user interface, so there should be no reason to remove revocation status. The JWT also becomes invalid based on its internal expiration claim property.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r683928100



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/revocation/JwtRevocationService.java
##########
@@ -14,36 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.admin.service;
+package org.apache.nifi.web.security.jwt.revocation;
 
-import org.apache.nifi.key.Key;
+import java.time.Instant;
 
 /**
- * Manages NiFi user keys.
+ * JSON Web Token Revocation Service
  */
-public interface KeyService {
-
+public interface JwtRevocationService {
     /**
-     * Gets a key for the specified user identity. Returns null if the user has not had a key issued
-     *
-     * @param id The key id
-     * @return The key or null
+     * Delete Expired Revocations
      */
-    Key getKey(int id);
+    void deleteExpired();
 
     /**
-     * Gets a key for the specified user identity. If a key does not exist, one will be created.
+     * Is JSON Web Token Identifier Revoked
      *
-     * @param identity The user identity
-     * @return The key
-     * @throws AdministrationException if it failed to get/create the key
+     * @param id JSON Web Token Identifier
+     * @return Revoked Status
      */
-    Key getOrCreateKey(String identity);
+    boolean isRevoked(String id);
 
     /**
-     * Deletes keys for the specified identity.
+     * Set Revoked Status using JSON Web Token Identifier
      *
-     * @param keyId The user's key ID
+     * @param id JSON Web Token Identifier
+     * @param expiration Expiration of Revocation Status

Review comment:
       Thanks for the suggestion, I have updated the documentation.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680406735



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/converter/StandardJwtAuthenticationConverter.java
##########
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.converter;
+
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
+import org.apache.nifi.authorization.util.UserGroupUtil;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Standard Converter from JSON Web Token to NiFi Authentication Token
+ */
+public class StandardJwtAuthenticationConverter implements Converter<Jwt, NiFiAuthenticationToken> {
+    private final Authorizer authorizer;
+
+    private final IdpUserGroupService idpUserGroupService;
+
+    private final List<IdentityMapping> identityMappings;
+
+    public StandardJwtAuthenticationConverter(final Authorizer authorizer, final IdpUserGroupService idpUserGroupService, final NiFiProperties properties) {
+        this.authorizer = authorizer;
+        this.idpUserGroupService = idpUserGroupService;
+        this.identityMappings = IdentityMappingUtil.getIdentityMappings(properties);
+    }
+
+    /**
+     * Convert JSON Web Token to NiFi Authentication Token
+     *
+     * @param jwt JSON Web Token
+     * @return NiFi Authentication Token
+     */
+    @Override
+    public NiFiAuthenticationToken convert(final Jwt jwt) {
+        final NiFiUser user = getUser(jwt);
+        return new NiFiAuthenticationToken(new NiFiUserDetails(user));
+    }
+
+    private NiFiUser getUser(final Jwt jwt) {
+        final String identity = IdentityMappingUtil.mapIdentity(jwt.getSubject(), identityMappings);
+
+        return new StandardNiFiUser.Builder()
+                .identity(identity)
+                .groups(UserGroupUtil.getUserGroups(authorizer, identity))
+                .identityProviderGroups(getIdentityProviderGroups(identity))
+                .build();
+    }
+
+    private Set<String> getIdentityProviderGroups(final String identity) {
+        return idpUserGroupService.getUserGroups(identity).stream()
+                .map(userGroup -> userGroup.getGroupName())

Review comment:
       The catch block was looking for `InvalidAuthenticationException`, which extends `AuthenticationException`, but did not include sub-classes such as `InvalidBearerTokenException`. Adjusting the catch to handle the generic `AuthenticationException` should handle a number of cases, and results in NiFi returning a response to remove the cookie. This can be verified without waiting for the expiration by logging in, stopping NiFi, clearing out the `state` directory, and restarting. Removing the contents of the `state` directory removes the persistent public verification key, which should result in the same `InvalidBearerTokenException`.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] kevdoran commented on a change in pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
kevdoran commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r691268425



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       This does not change existing functionality, so it is not a blocker to the PR, but just for my own understanding as I have not seen how NiFi does this - in NiFi, is the JWT token value also used as the CSRF prevention token value?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#issuecomment-901241731


   Thanks for the review and approval @kevdoran!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] kevdoran commented on a change in pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
kevdoran commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r691327647



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/CsrfCookieRequestMatcher.java
##########
@@ -35,6 +33,6 @@
      */
     @Override
     public boolean matches(final HttpServletRequest httpServletRequest) {
-        return WebUtils.getCookie(httpServletRequest, DEFAULT_CSRF_COOKIE_NAME) != null;
+        return WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null;

Review comment:
       Thanks for confirming!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r681267099



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/key/StandardVerificationKeySelector.java
##########
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.jwt.key;
+
+import org.apache.nifi.web.security.jwt.jws.SigningKeyListener;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Key;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Standard Verification Key Selector implements listener interfaces for updating Key status
+ */
+public class StandardVerificationKeySelector implements SigningKeyListener, VerificationKeyListener, VerificationKeySelector {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardVerificationKeySelector.class);
+
+    private final VerificationKeyService verificationKeyService;
+
+    private final Duration keyRotationPeriod;
+
+    public StandardVerificationKeySelector(final VerificationKeyService verificationKeyService, final Duration keyRotationPeriod) {
+        this.verificationKeyService = Objects.requireNonNull(verificationKeyService, "Verification Key Service required");
+        this.keyRotationPeriod = Objects.requireNonNull(keyRotationPeriod, "Key Rotation Period required");
+    }
+
+    /**
+     * On Verification Key Generated persist encoded Key with expiration
+     *
+     * @param keyIdentifier Key Identifier
+     * @param key Key
+     */
+    @Override
+    public void onVerificationKeyGenerated(final String keyIdentifier, final Key key) {
+        final Instant expiration = Instant.now().plus(keyRotationPeriod);
+        verificationKeyService.save(keyIdentifier, key, expiration);
+        LOGGER.debug("Verification Key Saved [{}] Expiration [{}]", keyIdentifier, expiration);
+    }
+
+    /**
+     * Get Verification Keys
+     *
+     * @param keyIdentifier Key Identifier
+     * @return List of Keys
+     */
+    @Override
+    public List<? extends Key> getVerificationKeys(final String keyIdentifier) {
+        final Optional<Key> key = verificationKeyService.findById(keyIdentifier);
+        final List<? extends Key> keys = key.map(Collections::singletonList).orElse(Collections.emptyList());
+        LOGGER.debug("Key Identifier [{}] Verification Keys Found [{}]", keyIdentifier, keys.size());
+        return keys;
+    }
+
+    /**
+     * On Signing Key Used set new expiration

Review comment:
       Yes, that is the purpose of setting a new expiration.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#issuecomment-890401207


   Thanks for the thorough review and detailed feedback @gresockj! I pushed an update in response to your comments, please let me know you have any additional notes.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on pull request #5262: NIFI-8766 Implemented Public Key Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#issuecomment-893981880


   Following additional review of [RFC 7518 Section 8](https://datatracker.ietf.org/doc/html/rfc7518#section-8.3), I adjusted the signing algorithm to `PS512` instead of `RS512` in order to use `RSASSA-PSS` instead of `RSASSA-PKCS1-v1_5`. The Nimbus JWT library supports both algorithms and as a self-contained implementation, this provides better security for signing with the same RSA Key Pair approach.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r681263397



##########
File path: nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
##########
@@ -354,6 +356,7 @@
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_TRUSTSTORE_STRATEGY = "JDK";
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_CONNECT_TIMEOUT = "30 secs";
     public static final String DEFAULT_SECURITY_USER_SAML_HTTP_CLIENT_READ_TIMEOUT = "30 secs";
+    private static final String DEFAULT_SECURITY_USER_JWS_KEY_ROTATION_PERIOD = "PT1H";

Review comment:
       Do you think this should be changed to used the time period format of other properties? It is a new property that stands alone, and it could provide a path forward for other new properties.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [nifi] exceptionfactory commented on a change in pull request #5262: NIFI-8766 Implemented RS512 Algorithm for JWT Signing

Posted by GitBox <gi...@apache.org>.
exceptionfactory commented on a change in pull request #5262:
URL: https://github.com/apache/nifi/pull/5262#discussion_r680403736



##########
File path: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
##########
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.configuration;
+
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import com.nimbusds.jwt.proc.JWTProcessor;
+import org.apache.nifi.admin.service.IdpUserGroupService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardJwtAuthenticationConverter;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+import org.apache.nifi.web.security.jwt.jws.StandardJwsSignerProvider;
+import org.apache.nifi.web.security.jwt.key.command.KeyExpirationCommand;
+import org.apache.nifi.web.security.jwt.key.command.KeyGenerationCommand;
+import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector;
+import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService;
+import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService;
+import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
+import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.JwtRevocationValidator;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtLogoutListener;
+import org.apache.nifi.web.security.jwt.revocation.StandardJwtRevocationService;
+import org.apache.nifi.web.security.jwt.revocation.command.RevocationExpirationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * JSON Web Token Configuration for Authentication Security
+ */
+@Configuration
+public class JwtAuthenticationSecurityConfiguration {
+    private static final Set<String> REQUIRED_CLAIMS = new HashSet<>(Arrays.asList("sub", "iss", "aud", "nbf", "iat", "exp", "jti"));

Review comment:
       That's a good point, will add entries to the `SupportedClaim` enum with references to RFC 7519.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org