This is an automated email from the ASF dual-hosted git repository.
bbende pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 335f08e5a4 NIFI-5302 Add Support for Client Credentials Flow with OIDC
(#8532)
335f08e5a4 is described below
commit 335f08e5a474d3b78abf687c801d1e4e179549ec
Author: David Handermann <[email protected]>
AuthorDate: Thu Apr 25 11:33:32 2024 -0500
NIFI-5302 Add Support for Client Credentials Flow with OIDC (#8532)
* NIFI-5302 Added Support for Client Credentials Flow with OIDC
- Added JwtDecoder implementation supporting delegation based on Issuer
* NIFI-5302 Added Client Credentials Grant Type section to OIDC docs
* NIFI-5302 Replaced deprecated OkHttp3ClientHttpRequestFactory
---
.../src/main/asciidoc/administration-guide.adoc | 13 ++
.../AuthenticationSecurityConfiguration.java | 2 +
.../ClientRegistrationConfiguration.java | 153 +++++++++++++++++
.../JwtAuthenticationSecurityConfiguration.java | 176 +++++++++----------
.../configuration/JwtDecoderConfiguration.java | 186 +++++++++++++++++++++
.../configuration/OidcSecurityConfiguration.java | 161 +++---------------
.../jwt/converter/StandardIssuerJwtDecoder.java | 139 +++++++++++++++
.../authentication/AccessTokenDecoderFactory.java | 50 ++++++
.../StandardOidcIdTokenDecoderFactory.java | 26 +--
.../converter/StandardIssuerJwtDecoderTest.java | 142 ++++++++++++++++
10 files changed, 805 insertions(+), 243 deletions(-)
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index b2fbeb50c4..92a3119ea5 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -549,6 +549,19 @@ Access Token has expired, indicating that the Resource
Owner has terminated the
termination occurs when the user closes the browser without initiating the
logout process. The scheduled process avoids
extended storage of Refresh Tokens for users who are no longer interacting
with the application.
+The OpenID Connect implementation also supports the OAuth 2 Client Credentials
Grant Type as described in
+link:https://www.rfc-editor.org/rfc/rfc6749#section-4.4[RFC 6749 Section
4.4^]. With OpenID Connect integration enabled,
+NiFi evaluates the JSON Web Token Issuer Claim named `iss` and delegates to
either the configured Authorization Server
+or internal processing for signature verification. When the `iss` claim value
matches the `issuer` from the OpenID
+Connect Discovery Configuration, NiFi uses the JSON Web Keys from the
Authorization Server for signature verification.
+In all other cases, NiFi verifies JSON Web Token signatures using an internal
public key.
+
+The Client Credentials Grant Type enables machine-to-machine authentication
and requires token request processing outside
+of NiFi itself to obtain an Access Token. NiFi must also be configured to
authorize requests based on the identity
+defined in a signed Access Token. Access Tokens obtained using the Client
Credentials Grant Type do not include the
+standard `email`, which requires configuring a fallback claim to identify the
machine user. The most common claim for
+identification is the Subject Claim named `sub`, which contains the Client ID.
+
OpenID Connect integration supports the following settings in
_nifi.properties_.
[options="header"]
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/AuthenticationSecurityConfiguration.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/AuthenticationSecurityConfiguration.java
index 33d4d74717..da18a01ec1 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/AuthenticationSecurityConfiguration.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/AuthenticationSecurityConfiguration.java
@@ -34,7 +34,9 @@ import
org.springframework.security.authentication.AuthenticationManager;
*/
@Configuration
@Import({
+ ClientRegistrationConfiguration.class,
JwtAuthenticationSecurityConfiguration.class,
+ JwtDecoderConfiguration.class,
KerberosAuthenticationSecurityConfiguration.class,
KnoxAuthenticationSecurityConfiguration.class,
OidcSecurityConfiguration.class,
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/ClientRegistrationConfiguration.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/ClientRegistrationConfiguration.java
new file mode 100644
index 0000000000..6205f32931
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/ClientRegistrationConfiguration.java
@@ -0,0 +1,153 @@
+/*
+ * 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 org.apache.nifi.security.util.SslContextFactory;
+import org.apache.nifi.security.util.StandardTlsConfiguration;
+import org.apache.nifi.security.util.TlsConfiguration;
+import org.apache.nifi.security.util.TlsException;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.oidc.OidcConfigurationException;
+import
org.apache.nifi.web.security.oidc.registration.ClientRegistrationProvider;
+import
org.apache.nifi.web.security.oidc.registration.DisabledClientRegistrationRepository;
+import
org.apache.nifi.web.security.oidc.registration.StandardClientRegistrationProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.JdkClientHttpRequestFactory;
+import org.springframework.http.converter.FormHttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import
org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
+import
org.springframework.security.oauth2.client.registration.ClientRegistration;
+import
org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import
org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
+import
org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
+import org.springframework.web.client.RestOperations;
+import org.springframework.web.client.RestTemplate;
+
+import javax.net.ssl.SSLContext;
+import java.net.http.HttpClient;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * OpenID Connect Client Registration configuration with supporting components
+ */
+@Configuration
+public class ClientRegistrationConfiguration {
+
+ private static final Duration DEFAULT_SOCKET_TIMEOUT =
Duration.ofSeconds(5);
+
+ private static final String NIFI_TRUSTSTORE_STRATEGY = "NIFI";
+
+ private final NiFiProperties properties;
+
+ @Autowired
+ public ClientRegistrationConfiguration(final NiFiProperties properties) {
+ this.properties = Objects.requireNonNull(properties, "Application
properties required");
+ }
+
+ /**
+ * Client Registration Repository for OpenID Connect Discovery
+ *
+ * @return Client Registration Repository
+ */
+ @Bean
+ public ClientRegistrationRepository clientRegistrationRepository() {
+ final ClientRegistrationRepository clientRegistrationRepository;
+ if (properties.isOidcEnabled()) {
+ final ClientRegistrationProvider clientRegistrationProvider = new
StandardClientRegistrationProvider(properties, oidcRestOperations());
+ final ClientRegistration clientRegistration =
clientRegistrationProvider.getClientRegistration();
+ clientRegistrationRepository = new
InMemoryClientRegistrationRepository(clientRegistration);
+ } else {
+ clientRegistrationRepository = new
DisabledClientRegistrationRepository();
+ }
+ return clientRegistrationRepository;
+ }
+
+
+ /**
+ * OpenID Connect REST Operations for communication with Authorization
Servers
+ *
+ * @return REST Operations
+ */
+ @Bean
+ public RestOperations oidcRestOperations() {
+ final RestTemplate restTemplate = new
RestTemplate(oidcClientHttpRequestFactory());
+ restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
+ restTemplate.setMessageConverters(
+ Arrays.asList(
+ new FormHttpMessageConverter(),
+ new OAuth2AccessTokenResponseHttpMessageConverter(),
+ new StringHttpMessageConverter(),
+ new MappingJackson2HttpMessageConverter()
+ )
+ );
+ return restTemplate;
+ }
+
+ /**
+ * OpenID Connect Client HTTP Request Factory for communication with
Authorization Servers
+ *
+ * @return Client HTTP Request Factory
+ */
+ @Bean
+ public ClientHttpRequestFactory oidcClientHttpRequestFactory() {
+ final HttpClient httpClient = getHttpClient();
+ final JdkClientHttpRequestFactory clientHttpRequestFactory = new
JdkClientHttpRequestFactory(httpClient);
+ final Duration readTimeout =
getTimeout(properties.getOidcReadTimeout());
+ clientHttpRequestFactory.setReadTimeout(readTimeout);
+ return clientHttpRequestFactory;
+ }
+
+ private HttpClient getHttpClient() {
+ final Duration connectTimeout =
getTimeout(properties.getOidcConnectTimeout());
+ final HttpClient.Builder builder =
HttpClient.newBuilder().connectTimeout(connectTimeout);
+
+ if
(NIFI_TRUSTSTORE_STRATEGY.equals(properties.getOidcClientTruststoreStrategy()))
{
+ setSslSocketFactory(builder);
+ }
+
+ return builder.build();
+ }
+
+ private Duration getTimeout(final String timeoutExpression) {
+ try {
+ final double duration =
FormatUtils.getPreciseTimeDuration(timeoutExpression, TimeUnit.MILLISECONDS);
+ final long rounded = Math.round(duration);
+ return Duration.ofMillis(rounded);
+ } catch (final RuntimeException e) {
+ return DEFAULT_SOCKET_TIMEOUT;
+ }
+ }
+
+ private void setSslSocketFactory(final HttpClient.Builder builder) {
+ final TlsConfiguration tlsConfiguration =
StandardTlsConfiguration.fromNiFiProperties(properties);
+
+ try {
+ final SSLContext sslContext =
Objects.requireNonNull(SslContextFactory.createSslContext(tlsConfiguration),
"SSLContext required");
+ builder.sslContext(sslContext);
+ } catch (final TlsException e) {
+ throw new OidcConfigurationException("OpenID Connect HTTP TLS
configuration failed", e);
+ }
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
index 100e500cad..1057c50a1e 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java
@@ -16,79 +16,54 @@
*/
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.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.StandardAuthenticationEntryPoint;
-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.IssuerProvider;
+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.provider.StandardIssuerProvider;
-import org.apache.nifi.web.security.jwt.provider.SupportedClaim;
import org.apache.nifi.web.security.jwt.resolver.StandardBearerTokenResolver;
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.authentication.AuthenticationManager;
-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
org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import
org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import
org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import java.time.Duration;
-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(
- SupportedClaim.ISSUER.getClaim(),
- SupportedClaim.SUBJECT.getClaim(),
- SupportedClaim.AUDIENCE.getClaim(),
- SupportedClaim.EXPIRATION.getClaim(),
- SupportedClaim.NOT_BEFORE.getClaim(),
- SupportedClaim.ISSUED_AT.getClaim(),
- SupportedClaim.JWT_ID.getClaim(),
- SupportedClaim.GROUPS.getClaim()
- ));
private final NiFiProperties niFiProperties;
private final Authorizer authorizer;
- private final StateManagerProvider stateManagerProvider;
+ private final JwtDecoder jwtDecoder;
+
+ private final JwtRevocationService jwtRevocationService;
+
+ private final StandardVerificationKeySelector verificationKeySelector;
+
+ private final VerificationKeyService verificationKeyService;
private final Duration keyRotationPeriod;
@@ -96,15 +71,26 @@ public class JwtAuthenticationSecurityConfiguration {
public JwtAuthenticationSecurityConfiguration(
final NiFiProperties niFiProperties,
final Authorizer authorizer,
- final StateManagerProvider stateManagerProvider
+ final JwtDecoder jwtDecoder,
+ final JwtRevocationService jwtRevocationService,
+ final StandardVerificationKeySelector
standardVerificationKeySelector,
+ final VerificationKeyService verificationKeyService
) {
this.niFiProperties = niFiProperties;
this.authorizer = authorizer;
- this.stateManagerProvider = stateManagerProvider;
+ this.jwtDecoder = jwtDecoder;
+ this.jwtRevocationService = jwtRevocationService;
+ this.verificationKeySelector = standardVerificationKeySelector;
+ this.verificationKeyService = verificationKeyService;
this.keyRotationPeriod =
niFiProperties.getSecurityUserJwsKeyRotationPeriod();
}
-
+ /**
+ * Bearer Token Authentication Filter responsible for reading and
authenticating Bearer JSON Web Tokens from HTTP Requests
+ *
+ * @param authenticationManager Authentication Manager configured with JWT
Authentication Provider
+ * @return Bearer Token Authentication Filter
+ */
@Bean
public BearerTokenAuthenticationFilter
bearerTokenAuthenticationFilter(final AuthenticationManager
authenticationManager) {
final BearerTokenAuthenticationFilter bearerTokenAuthenticationFilter
= new BearerTokenAuthenticationFilter(authenticationManager);
@@ -113,74 +99,64 @@ public class JwtAuthenticationSecurityConfiguration {
return bearerTokenAuthenticationFilter;
}
+ /**
+ * Bearer Token Resolver responsible for reading Bearer JSON Web Tokens
from HTTP headers or cookies
+ *
+ * @return Standard implementation of Bearer Token Resolver
+ */
@Bean
public BearerTokenResolver bearerTokenResolver() {
return new StandardBearerTokenResolver();
}
+ /**
+ * Authentication Entry Point delegating to Bearer Token Entry Point for
returning headers on authentication failures
+ *
+ * @return Authentication Entry Point
+ */
@Bean
public StandardAuthenticationEntryPoint authenticationEntryPoint() {
final BearerTokenAuthenticationEntryPoint
bearerTokenAuthenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
return new
StandardAuthenticationEntryPoint(bearerTokenAuthenticationEntryPoint);
}
+ /**
+ * JSON Web Token Authentication Provider responsible for decoding and
verifying Bearer Tokens from HTTP Requests
+ *
+ * @return JSON Web Token Authentication Provider
+ */
@Bean
public JwtAuthenticationProvider jwtAuthenticationProvider() {
- final JwtAuthenticationProvider jwtAuthenticationProvider = new
JwtAuthenticationProvider(jwtDecoder());
+ 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);
- }
-
+ /**
+ * JSON Web Token Logout Listener responsible for revoking application
Bearer Tokens after logout completion
+ *
+ * @return JSON Web Token Logout Listener using Revocation Service for
tracking
+ */
@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);
+ return new StandardJwtLogoutListener(jwtDecoder, jwtRevocationService);
}
+ /**
+ * JSON Web Token Authentication Converter provides application User
objects
+ *
+ * @return Authentication Converter from JSON Web Tokens to User objects
+ */
@Bean
public StandardJwtAuthenticationConverter jwtAuthenticationConverter() {
return new StandardJwtAuthenticationConverter(authorizer,
niFiProperties);
}
+ /**
+ * Application Bearer Token Provider responsible for signing and encoding
new JSON Web Tokens
+ *
+ * @return Application Bearer Token Provider
+ */
@Bean
public BearerTokenProvider bearerTokenProvider() {
return new StandardBearerTokenProvider(jwsSignerProvider(),
issuerProvider());
@@ -191,43 +167,57 @@ public class JwtAuthenticationSecurityConfiguration {
return new
StandardIssuerProvider(niFiProperties.getProperty(NiFiProperties.WEB_HTTPS_HOST),
niFiProperties.getConfiguredHttpOrHttpsPort());
}
+ /**
+ * JSON Web Signature Signer Provider responsible for managing Bearer
Token signing key pairs
+ *
+ * @return JSON Web Signature Signer Provider
+ */
@Bean
public StandardJwsSignerProvider jwsSignerProvider() {
- return new StandardJwsSignerProvider(verificationKeySelector());
- }
-
- @Bean
- public StandardVerificationKeySelector verificationKeySelector() {
- return new StandardVerificationKeySelector(verificationKeyService(),
keyRotationPeriod);
- }
-
- @Bean
- public VerificationKeyService verificationKeyService() {
- final StateManager stateManager =
stateManagerProvider.getStateManager(StandardVerificationKeyService.class.getName());
- return new StandardVerificationKeyService(stateManager);
+ return new StandardJwsSignerProvider(verificationKeySelector);
}
+ /**
+ * Key Generation Command responsible for rotating JSON Web Signature key
pairs based on configuration
+ *
+ * @return Key Generation Command scheduled according to application
properties
+ */
@Bean
public KeyGenerationCommand keyGenerationCommand() {
- final KeyGenerationCommand command = new
KeyGenerationCommand(jwsSignerProvider(), verificationKeySelector());
+ final KeyGenerationCommand command = new
KeyGenerationCommand(jwsSignerProvider(), verificationKeySelector);
commandScheduler().scheduleAtFixedRate(command, keyRotationPeriod);
return command;
}
+ /**
+ * Key Expiration Command responsible for removing expired signing key
pairs
+ *
+ * @return Key Expiration Command scheduled according to application
properties
+ */
@Bean
public KeyExpirationCommand keyExpirationCommand() {
- final KeyExpirationCommand command = new
KeyExpirationCommand(verificationKeyService());
+ final KeyExpirationCommand command = new
KeyExpirationCommand(verificationKeyService);
commandScheduler().scheduleAtFixedRate(command, keyRotationPeriod);
return command;
}
+ /**
+ * Revocation Expiration Command responsible for removing expired
application Bearer Token revocation records
+ *
+ * @return Revocation Expiration Command scheduled according to
application properties
+ */
@Bean
public RevocationExpirationCommand revocationExpirationCommand() {
- final RevocationExpirationCommand command = new
RevocationExpirationCommand(jwtRevocationService());
+ final RevocationExpirationCommand command = new
RevocationExpirationCommand(jwtRevocationService);
commandScheduler().scheduleAtFixedRate(command, keyRotationPeriod);
return command;
}
+ /**
+ * Command Scheduler responsible for running commands in background thread
+ *
+ * @return Thread Pool Task Scheduler with named threads
+ */
@Bean
public ThreadPoolTaskScheduler commandScheduler() {
final ThreadPoolTaskScheduler scheduler = new
ThreadPoolTaskScheduler();
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtDecoderConfiguration.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtDecoderConfiguration.java
new file mode 100644
index 0000000000..8da3e70f7b
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtDecoderConfiguration.java
@@ -0,0 +1,186 @@
+/*
+ * 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.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.jwt.converter.StandardIssuerJwtDecoder;
+import org.apache.nifi.web.security.jwt.jws.StandardJWSKeySelector;
+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.SupportedClaim;
+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.StandardJwtRevocationService;
+import
org.apache.nifi.web.security.oidc.authentication.AccessTokenDecoderFactory;
+import
org.apache.nifi.web.security.oidc.authentication.StandardOidcIdTokenDecoderFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import
org.springframework.security.oauth2.client.registration.ClientRegistration;
+import
org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+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.JwtDecoderFactory;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.web.client.RestOperations;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * JSON Web Token Decoder Configuration with component supporting Bearer Token
parsing and verification
+ */
+@Configuration
+public class JwtDecoderConfiguration {
+ private static final Set<String> REQUIRED_CLAIMS = new
HashSet<>(Arrays.asList(
+ SupportedClaim.ISSUER.getClaim(),
+ SupportedClaim.SUBJECT.getClaim(),
+ SupportedClaim.AUDIENCE.getClaim(),
+ SupportedClaim.EXPIRATION.getClaim(),
+ SupportedClaim.NOT_BEFORE.getClaim(),
+ SupportedClaim.ISSUED_AT.getClaim(),
+ SupportedClaim.JWT_ID.getClaim(),
+ SupportedClaim.GROUPS.getClaim()
+ ));
+
+ private final NiFiProperties properties;
+
+ private final ClientRegistrationRepository clientRegistrationRepository;
+
+ private final RestOperations oidcRestOperations;
+
+ private final StateManagerProvider stateManagerProvider;
+
+ private final Duration keyRotationPeriod;
+
+ @Autowired
+ public JwtDecoderConfiguration(
+ final NiFiProperties properties,
+ final ClientRegistrationRepository clientRegistrationRepository,
+ @Qualifier("oidcRestOperations")
+ final RestOperations oidcRestOperations,
+ final StateManagerProvider stateManagerProvider
+ ) {
+ this.properties = Objects.requireNonNull(properties, "Application
properties required");
+ this.clientRegistrationRepository =
Objects.requireNonNull(clientRegistrationRepository, "Client Registration
Repository required");
+ this.oidcRestOperations = Objects.requireNonNull(oidcRestOperations,
"OIDC REST Operations required");
+ this.stateManagerProvider =
Objects.requireNonNull(stateManagerProvider, "State Manager Provider required");
+ this.keyRotationPeriod =
properties.getSecurityUserJwsKeyRotationPeriod();
+ }
+
+ /**
+ * JWT Decoder responsible for parsing and verifying Bearer Tokens from
application or OIDC Identity Provider
+ *
+ * @return JWT Decoder delegating to OpenID Connect JWT Decoder on
matching Issuer claims
+ */
+ @Bean
+ public JwtDecoder jwtDecoder() {
+ final NimbusJwtDecoder applicationJwtDecoder = new
NimbusJwtDecoder(jwtProcessor());
+ applicationJwtDecoder.setJwtValidator(jwtTokenValidator());
+ final AccessTokenDecoderFactory accessTokenDecoderFactory = new
AccessTokenDecoderFactory(properties.getOidcPreferredJwsAlgorithm(),
oidcRestOperations);
+ return new StandardIssuerJwtDecoder(applicationJwtDecoder,
accessTokenDecoderFactory, clientRegistrationRepository);
+ }
+
+ /**
+ * JSON Web Token Processor supporting application Bearer Token Decoder
with configured Signing Key Selector
+ *
+ * @return Application JSON Web Token Processor for verification
+ */
+ @Bean
+ public JWTProcessor<SecurityContext> jwtProcessor() {
+ final DefaultJWTProcessor<SecurityContext> jwtProcessor = new
DefaultJWTProcessor<>();
+ final JWSKeySelector<SecurityContext> jwsKeySelector = new
StandardJWSKeySelector<>(verificationKeySelector());
+ jwtProcessor.setJWSKeySelector(jwsKeySelector);
+
+ final JWTClaimsSetVerifier<SecurityContext> claimsSetVerifier = new
DefaultJWTClaimsVerifier<>(null, REQUIRED_CLAIMS);
+ jwtProcessor.setJWTClaimsSetVerifier(claimsSetVerifier);
+ return jwtProcessor;
+ }
+
+ /**
+ * Token Validator responsible for validating JWT claims after parsing and
verification based on matching Issuer
+ *
+ * @return Token Validator supporting application Bearer Tokens
+ */
+ @Bean
+ public OAuth2TokenValidator<Jwt> jwtTokenValidator() {
+ final OAuth2TokenValidator<Jwt> jwtRevocationValidator = new
JwtRevocationValidator(jwtRevocationService());
+ return new DelegatingOAuth2TokenValidator<>(
+ JwtValidators.createDefault(),
+ jwtRevocationValidator
+ );
+ }
+
+ /**
+ * OpenID Connect Identifier Token Decoder with configured JWS Algorithm
for verification
+ *
+ * @return OpenID Connect Identifier Token Decoder
+ */
+ @Bean
+ public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
+ final String preferredJwdAlgorithm =
properties.getOidcPreferredJwsAlgorithm();
+ return new StandardOidcIdTokenDecoderFactory(preferredJwdAlgorithm,
oidcRestOperations);
+ }
+
+ /**
+ * JWT Revocation Service with backing local State Manager for tracking
revoked application Bearer Tokens
+ *
+ * @return JWT Revocation Service using local State Manager
+ */
+ @Bean
+ public JwtRevocationService jwtRevocationService() {
+ final StateManager stateManager =
stateManagerProvider.getStateManager(StandardJwtRevocationService.class.getName());
+ return new StandardJwtRevocationService(stateManager);
+ }
+
+ /**
+ * Verification Key Selector with configured key rotation period
+ *
+ * @return Verification Key Selector supporting JSON Web Token signature
verification
+ */
+ @Bean
+ public StandardVerificationKeySelector verificationKeySelector() {
+ return new StandardVerificationKeySelector(verificationKeyService(),
keyRotationPeriod);
+ }
+
+ /**
+ * Verification Key Service using local State Manager for storing public
keys
+ *
+ * @return Standard Verification Key Service with local State Manager
+ */
+ @Bean
+ public VerificationKeyService verificationKeyService() {
+ final StateManager stateManager =
stateManagerProvider.getStateManager(StandardVerificationKeyService.class.getName());
+ return new StandardVerificationKeyService(stateManager);
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
index fcc3c87f29..b4f13ec91c 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
@@ -18,23 +18,16 @@ package org.apache.nifi.web.security.configuration;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
-import okhttp3.OkHttpClient;
import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.components.state.StateManager;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.encrypt.PropertyEncryptor;
-import org.apache.nifi.security.util.SslContextFactory;
-import org.apache.nifi.security.util.StandardTlsConfiguration;
-import org.apache.nifi.security.util.TlsConfiguration;
-import org.apache.nifi.security.util.TlsException;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.StandardAuthenticationEntryPoint;
import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
import org.apache.nifi.web.security.logout.LogoutRequestManager;
-import org.apache.nifi.web.security.oidc.OidcConfigurationException;
import org.apache.nifi.web.security.oidc.OidcUrlPath;
-import
org.apache.nifi.web.security.oidc.authentication.StandardOidcIdTokenDecoderFactory;
import
org.apache.nifi.web.security.oidc.client.web.AuthorizedClientExpirationCommand;
import
org.apache.nifi.web.security.oidc.client.web.OidcBearerTokenRefreshFilter;
import
org.apache.nifi.web.security.oidc.client.web.StandardOAuth2AuthorizationRequestResolver;
@@ -45,41 +38,31 @@ import
org.apache.nifi.web.security.oidc.client.web.converter.StandardAuthorized
import
org.apache.nifi.web.security.oidc.client.web.StandardOidcAuthorizedClientRepository;
import org.apache.nifi.web.security.oidc.logout.OidcLogoutFilter;
import org.apache.nifi.web.security.oidc.logout.OidcLogoutSuccessHandler;
-import
org.apache.nifi.web.security.oidc.registration.ClientRegistrationProvider;
-import
org.apache.nifi.web.security.oidc.registration.DisabledClientRegistrationRepository;
-import
org.apache.nifi.web.security.oidc.registration.StandardClientRegistrationProvider;
import
org.apache.nifi.web.security.oidc.revocation.StandardTokenRevocationResponseClient;
import
org.apache.nifi.web.security.oidc.revocation.TokenRevocationResponseClient;
import org.apache.nifi.web.security.oidc.userinfo.StandardOidcUserService;
import
org.apache.nifi.web.security.oidc.web.authentication.OidcAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
-import org.springframework.http.converter.FormHttpMessageConverter;
-import org.springframework.http.converter.StringHttpMessageConverter;
-import
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.security.authentication.AuthenticationManager;
import
org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import
org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
import
org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import
org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
-import
org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import
org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider;
import
org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import
org.springframework.security.oauth2.client.registration.ClientRegistration;
import
org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
-import
org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import
org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import
org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import
org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
import
org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import
org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import
org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
-import
org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import
org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
@@ -89,15 +72,9 @@ import
org.springframework.security.web.authentication.session.NullAuthenticated
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.web.client.RestOperations;
-import org.springframework.web.client.RestTemplate;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
import java.time.Duration;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -111,10 +88,6 @@ public class OidcSecurityConfiguration {
private static final long AUTHORIZATION_REQUEST_CACHE_SIZE = 1000;
- private static final Duration DEFAULT_SOCKET_TIMEOUT =
Duration.ofSeconds(5);
-
- private static final String NIFI_TRUSTSTORE_STRATEGY = "NIFI";
-
private static final RequestCache nullRequestCache = new
NullRequestCache();
private final Duration keyRotationPeriod;
@@ -129,8 +102,14 @@ public class OidcSecurityConfiguration {
private final BearerTokenResolver bearerTokenResolver;
+ private final ClientRegistrationRepository clientRegistrationRepository;
+
private final JwtDecoder jwtDecoder;
+ private final JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory;
+
+ private final RestOperations oidcRestOperations;
+
private final LogoutRequestManager logoutRequestManager;
@Autowired
@@ -140,7 +119,11 @@ public class OidcSecurityConfiguration {
final PropertyEncryptor propertyEncryptor,
final BearerTokenProvider bearerTokenProvider,
final BearerTokenResolver bearerTokenResolver,
+ final ClientRegistrationRepository clientRegistrationRepository,
final JwtDecoder jwtDecoder,
+ final JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory,
+ @Qualifier("oidcRestOperations")
+ final RestOperations oidcRestOperations,
final LogoutRequestManager logoutRequestManager
) {
this.properties = Objects.requireNonNull(properties, "Properties
required");
@@ -148,7 +131,10 @@ public class OidcSecurityConfiguration {
this.propertyEncryptor = Objects.requireNonNull(propertyEncryptor,
"Property Encryptor required");
this.bearerTokenProvider = Objects.requireNonNull(bearerTokenProvider,
"Bearer Token Provider required");
this.bearerTokenResolver = Objects.requireNonNull(bearerTokenResolver,
"Bearer Token Resolver required");
+ this.clientRegistrationRepository =
Objects.requireNonNull(clientRegistrationRepository, "Registration Repository
required");
this.jwtDecoder = Objects.requireNonNull(jwtDecoder, "JWT Decoder
required");
+ this.idTokenDecoderFactory =
Objects.requireNonNull(idTokenDecoderFactory, "ID Token Decoder Factory
required");
+ this.oidcRestOperations = Objects.requireNonNull(oidcRestOperations,
"OIDC REST Operations required");
this.logoutRequestManager =
Objects.requireNonNull(logoutRequestManager, "Logout Request Manager required");
this.keyRotationPeriod =
properties.getSecurityUserJwsKeyRotationPeriod();
}
@@ -163,7 +149,7 @@ public class OidcSecurityConfiguration {
@Bean
public OAuth2AuthorizationCodeGrantFilter
oAuth2AuthorizationCodeGrantFilter(final AuthenticationManager
authenticationManager) {
final OAuth2AuthorizationCodeGrantFilter filter = new
OAuth2AuthorizationCodeGrantFilter(
- clientRegistrationRepository(),
+ clientRegistrationRepository,
authorizedClientRepository(),
authenticationManager
);
@@ -180,7 +166,7 @@ public class OidcSecurityConfiguration {
*/
@Bean
public OAuth2AuthorizationRequestRedirectFilter
oAuth2AuthorizationRequestRedirectFilter() {
- final StandardOAuth2AuthorizationRequestResolver
authorizationRequestResolver = new
StandardOAuth2AuthorizationRequestResolver(clientRegistrationRepository());
+ final StandardOAuth2AuthorizationRequestResolver
authorizationRequestResolver = new
StandardOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
final OAuth2AuthorizationRequestRedirectFilter filter = new
OAuth2AuthorizationRequestRedirectFilter(authorizationRequestResolver);
filter.setAuthorizationRequestRepository(authorizationRequestRepository());
filter.setRequestCache(nullRequestCache);
@@ -197,7 +183,7 @@ public class OidcSecurityConfiguration {
@Bean
public OAuth2LoginAuthenticationFilter
oAuth2LoginAuthenticationFilter(final AuthenticationManager
authenticationManager, final StandardAuthenticationEntryPoint
authenticationEntryPoint) {
final OAuth2LoginAuthenticationFilter filter = new
OAuth2LoginAuthenticationFilter(
- clientRegistrationRepository(),
+ clientRegistrationRepository,
authorizedClientRepository(),
OidcUrlPath.CALLBACK.getPath()
);
@@ -222,7 +208,7 @@ public class OidcSecurityConfiguration {
@Bean
public OidcBearerTokenRefreshFilter oidcBearerTokenRefreshFilter() {
final DefaultRefreshTokenTokenResponseClient
refreshTokenResponseClient = new DefaultRefreshTokenTokenResponseClient();
- refreshTokenResponseClient.setRestOperations(oidcRestOperations());
+ refreshTokenResponseClient.setRestOperations(oidcRestOperations);
final String refreshWindowProperty =
properties.getOidcTokenRefreshWindow();
final double refreshWindowSeconds =
FormatUtils.getPreciseTimeDuration(refreshWindowProperty, TimeUnit.SECONDS);
@@ -257,7 +243,7 @@ public class OidcSecurityConfiguration {
public LogoutSuccessHandler oidcLogoutSuccessHandler() {
return new OidcLogoutSuccessHandler(
logoutRequestManager,
- clientRegistrationRepository(),
+ clientRegistrationRepository,
authorizedClientRepository(),
tokenRevocationResponseClient()
);
@@ -274,7 +260,7 @@ public class OidcSecurityConfiguration {
accessTokenResponseClient(),
oidcUserService()
);
- provider.setJwtDecoderFactory(idTokenDecoderFactory());
+ provider.setJwtDecoderFactory(idTokenDecoderFactory);
return provider;
}
@@ -286,7 +272,7 @@ public class OidcSecurityConfiguration {
@Bean
public
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
accessTokenResponseClient() {
final DefaultAuthorizationCodeTokenResponseClient
accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
- accessTokenResponseClient.setRestOperations(oidcRestOperations());
+ accessTokenResponseClient.setRestOperations(oidcRestOperations);
return accessTokenResponseClient;
}
@@ -302,7 +288,7 @@ public class OidcSecurityConfiguration {
IdentityMappingUtil.getIdentityMappings(properties)
);
final DefaultOAuth2UserService userService = new
DefaultOAuth2UserService();
- userService.setRestOperations(oidcRestOperations());
+ userService.setRestOperations(oidcRestOperations);
oidcUserService.setOauth2UserService(userService);
return oidcUserService;
}
@@ -344,7 +330,7 @@ public class OidcSecurityConfiguration {
*/
@Bean
public AuthorizedClientConverter authorizedClientConverter() {
- return new StandardAuthorizedClientConverter(propertyEncryptor,
clientRegistrationRepository());
+ return new StandardAuthorizedClientConverter(propertyEncryptor,
clientRegistrationRepository);
}
/**
@@ -362,17 +348,6 @@ public class OidcSecurityConfiguration {
return new StandardAuthorizationRequestRepository(cache);
}
- /**
- * OpenID Connect Identifier Token Decoder with configured JWS Algorithm
for verification
- *
- * @return OpenID Connect Identifier Token Decoder
- */
- @Bean
- public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
- final String preferredJwdAlgorithm =
properties.getOidcPreferredJwsAlgorithm();
- return new StandardOidcIdTokenDecoderFactory(preferredJwdAlgorithm,
oidcRestOperations());
- }
-
/**
* Token Revocation Response Client responsible for transmitting Refresh
Token revocation requests to the Provider
*
@@ -380,95 +355,7 @@ public class OidcSecurityConfiguration {
*/
@Bean
public TokenRevocationResponseClient tokenRevocationResponseClient() {
- return new StandardTokenRevocationResponseClient(oidcRestOperations(),
clientRegistrationRepository());
- }
-
- /**
- * Client Registration Repository for OpenID Connect Discovery
- *
- * @return Client Registration Repository
- */
- @Bean
- public ClientRegistrationRepository clientRegistrationRepository() {
- final ClientRegistrationRepository clientRegistrationRepository;
- if (properties.isOidcEnabled()) {
- final ClientRegistrationProvider clientRegistrationProvider = new
StandardClientRegistrationProvider(properties, oidcRestOperations());
- final ClientRegistration clientRegistration =
clientRegistrationProvider.getClientRegistration();
- clientRegistrationRepository = new
InMemoryClientRegistrationRepository(clientRegistration);
- } else {
- clientRegistrationRepository = new
DisabledClientRegistrationRepository();
- }
- return clientRegistrationRepository;
- }
-
- /**
- * OpenID Connect REST Operations for communication with Authorization
Servers
- *
- * @return REST Operations
- */
- @Bean
- public RestOperations oidcRestOperations() {
- final RestTemplate restTemplate = new
RestTemplate(oidcClientHttpRequestFactory());
- restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
- restTemplate.setMessageConverters(
- Arrays.asList(
- new FormHttpMessageConverter(),
- new OAuth2AccessTokenResponseHttpMessageConverter(),
- new StringHttpMessageConverter(),
- new MappingJackson2HttpMessageConverter()
- )
- );
- return restTemplate;
- }
-
- /**
- * OpenID Connect Client HTTP Request Factory for communication with
Authorization Servers
- *
- * @return Client HTTP Request Factory
- */
- @Bean
- public ClientHttpRequestFactory oidcClientHttpRequestFactory() {
- final OkHttpClient httpClient = getHttpClient();
- return new OkHttp3ClientHttpRequestFactory(httpClient);
- }
-
- private OkHttpClient getHttpClient() {
- final Duration connectTimeout =
getTimeout(properties.getOidcConnectTimeout());
- final Duration readTimeout =
getTimeout(properties.getOidcReadTimeout());
-
- final OkHttpClient.Builder builder = new OkHttpClient.Builder()
- .connectTimeout(connectTimeout)
- .readTimeout(readTimeout);
-
- if
(NIFI_TRUSTSTORE_STRATEGY.equals(properties.getOidcClientTruststoreStrategy()))
{
- setSslSocketFactory(builder);
- }
-
- return builder.build();
- }
-
- private Duration getTimeout(final String timeoutExpression) {
- try {
- final double duration =
FormatUtils.getPreciseTimeDuration(timeoutExpression, TimeUnit.MILLISECONDS);
- final long rounded = Math.round(duration);
- return Duration.ofMillis(rounded);
- } catch (final RuntimeException e) {
- return DEFAULT_SOCKET_TIMEOUT;
- }
- }
-
- private void setSslSocketFactory(final OkHttpClient.Builder builder) {
- final TlsConfiguration tlsConfiguration =
StandardTlsConfiguration.fromNiFiProperties(properties);
-
- try {
- final X509TrustManager trustManager =
Objects.requireNonNull(SslContextFactory.getX509TrustManager(tlsConfiguration),
"TrustManager required");
- final TrustManager[] trustManagers = new TrustManager[] {
trustManager };
- final SSLContext sslContext =
Objects.requireNonNull(SslContextFactory.createSslContext(tlsConfiguration,
trustManagers), "SSLContext required");
- final SSLSocketFactory sslSocketFactory =
sslContext.getSocketFactory();
- builder.sslSocketFactory(sslSocketFactory, trustManager);
- } catch (final TlsException e) {
- throw new OidcConfigurationException("OpenID Connect HTTP TLS
configuration failed", e);
- }
+ return new StandardTokenRevocationResponseClient(oidcRestOperations,
clientRegistrationRepository);
}
private OidcAuthenticationSuccessHandler getAuthenticationSuccessHandler()
{
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/converter/StandardIssuerJwtDecoder.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/converter/StandardIssuerJwtDecoder.java
new file mode 100644
index 0000000000..288a2e2199
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/converter/StandardIssuerJwtDecoder.java
@@ -0,0 +1,139 @@
+/*
+ * 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 com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.jwt.PlainJWT;
+import org.apache.nifi.web.security.oidc.client.web.OidcRegistrationProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import
org.springframework.security.oauth2.client.registration.ClientRegistration;
+import
org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.jwt.BadJwtException;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
+import org.springframework.security.oauth2.jwt.JwtException;
+
+import java.util.Objects;
+
+/**
+ * JSON Web Token Decoder capable of delegating to specific Decoder based on
Issuer claims for OpenID Connect Clients
+ */
+public class StandardIssuerJwtDecoder implements JwtDecoder {
+ private static final Logger logger =
LoggerFactory.getLogger(StandardIssuerJwtDecoder.class);
+
+ private final JwtDecoder applicationJwtDecoder;
+
+ private final ClientRegistration clientRegistration;
+
+ private final JwtDecoder clientRegistrationJwtDecoder;
+
+ /**
+ * Standard constructor with default application JWT Decoder and factory
providing OpenID Connect JWT Decoder
+ * @param applicationJwtDecoder Default JSON Web Token Decoder used when
matching Issuer claim not found
+ * @param jwtDecoderFactory OpenID Connect JWT Decoder Factory
+ * @param clientRegistrationRepository OpenID Connect Client Registration
Repository
+ */
+ public StandardIssuerJwtDecoder(
+ final JwtDecoder applicationJwtDecoder,
+ final JwtDecoderFactory<ClientRegistration> jwtDecoderFactory,
+ final ClientRegistrationRepository clientRegistrationRepository
+ ) {
+ this.applicationJwtDecoder =
Objects.requireNonNull(applicationJwtDecoder, "Application JWT Decoder
required");
+ this.clientRegistration =
clientRegistrationRepository.findByRegistrationId(OidcRegistrationProperty.REGISTRATION_ID.getProperty());
+ if (clientRegistration == null) {
+ logger.debug("OIDC Client Registration not configured for JWT
Decoder");
+ this.clientRegistrationJwtDecoder = null;
+ } else {
+ Objects.requireNonNull(jwtDecoderFactory, "JWT Decoder Factory
required");
+ this.clientRegistrationJwtDecoder =
jwtDecoderFactory.createDecoder(clientRegistration);
+ }
+ }
+
+ /**
+ * Decode JSON Web Token using OpenID Connect Decoder for matched Issuer
or default application JWT Decoder
+ *
+ * @param token JSON Web Token serialized and encoded
+ * @return Decoded and validated JSON Web Token
+ * @throws JwtException Thrown on malformed tokens or decoding failures
+ */
+ @Override
+ public Jwt decode(final String token) throws JwtException {
+ final Jwt decoded;
+
+ if (clientRegistration == null) {
+ decoded = applicationJwtDecoder.decode(token);
+ } else {
+ final JWT parsed = parse(token);
+ final String tokenIssuer = getTokenIssuer(parsed);
+
+ if (isIssuerRegistered(tokenIssuer)) {
+ decoded = clientRegistrationJwtDecoder.decode(token);
+ } else {
+ decoded = applicationJwtDecoder.decode(token);
+ }
+ }
+
+ return decoded;
+ }
+
+ private boolean isIssuerRegistered(final String tokenIssuer) {
+ final boolean registered;
+
+ if (clientRegistration == null) {
+ registered = false;
+ } else {
+ final ClientRegistration.ProviderDetails providerDetails =
clientRegistration.getProviderDetails();
+ final String issuerUri = providerDetails.getIssuerUri();
+ registered = issuerUri.equals(tokenIssuer);
+ }
+
+ return registered;
+ }
+
+ private String getTokenIssuer(final JWT parsed) {
+ try {
+ final JWTClaimsSet claimsSet = parsed.getJWTClaimsSet();
+ final String issuer = claimsSet.getIssuer();
+ if (issuer == null || issuer.isEmpty()) {
+ throw new BadJwtException("Token Issuer claim not found");
+ }
+ return issuer;
+ } catch (final Exception e) {
+ throw new BadJwtException("Token Issuer parsing failed", e);
+ }
+ }
+
+ private JWT parse(final String token) {
+ if (token == null || token.isEmpty()) {
+ throw new BadJwtException("Token not found");
+ }
+
+ try {
+ final JWT parsed = JWTParser.parse(token);
+ if (parsed instanceof PlainJWT) {
+ throw new BadJwtException("Unsigned Token not supported");
+ }
+ return parsed;
+ } catch (final Exception e) {
+ throw new BadJwtException("Token parsing failed", e);
+ }
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/authentication/AccessTokenDecoderFactory.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/authentication/AccessTokenDecoderFactory.java
new file mode 100644
index 0000000000..9de1f24ed6
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/authentication/AccessTokenDecoderFactory.java
@@ -0,0 +1,50 @@
+/*
+ * 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.oidc.authentication;
+
+import
org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.web.client.RestOperations;
+
+/**
+ * Access Token Decoder Factory provides Token Validation based on expected
Access Token claims instead of ID Token Claims
+ */
+public class AccessTokenDecoderFactory extends
StandardOidcIdTokenDecoderFactory {
+ /**
+ * Standard constructor with optional JWS Algorithm and required REST
Operations for retrieving JSON Web Keys
+ *
+ * @param preferredJwsAlgorithm Preferred JSON Web Signature Algorithm
default to RS256 when not provided
+ * @param restOperations REST Operations required for retrieving JSON Web
Key Set with Signature Algorithms
+ */
+ public AccessTokenDecoderFactory(final String preferredJwsAlgorithm, final
RestOperations restOperations) {
+ super(preferredJwsAlgorithm, restOperations);
+ }
+
+ /**
+ * Get OAuth2 Token Validator with Timestamp and Issuer Validators
+ *
+ * @param clientRegistration Client Registration
+ * @return OAuth2 Token Validator with Timestamp and Issuer Validators
+ */
+ @Override
+ protected OAuth2TokenValidator<Jwt> getTokenValidator(final
ClientRegistration clientRegistration) {
+ final String issuerUri =
clientRegistration.getProviderDetails().getIssuerUri();
+ return JwtValidators.createDefaultWithIssuer(issuerUri);
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/authentication/StandardOidcIdTokenDecoderFactory.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/authentication/StandardOidcIdTokenDecoderFactory.java
index ed8ff9ee70..22389f468f 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/authentication/StandardOidcIdTokenDecoderFactory.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/authentication/StandardOidcIdTokenDecoderFactory.java
@@ -104,6 +104,19 @@ public class StandardOidcIdTokenDecoderFactory implements
JwtDecoderFactory<Clie
});
}
+ /**
+ * Get OAuth2 Token Validator based on Spring Security
DefaultOidcIdTokenValidatorFactory
+ *
+ * @param clientRegistration Client Registration
+ * @return OAuth2 Token Validator with Timestamp and OpenID Connect ID
Token Validators
+ */
+ protected OAuth2TokenValidator<Jwt> getTokenValidator(final
ClientRegistration clientRegistration) {
+ return new DelegatingOAuth2TokenValidator<>(
+ new JwtTimestampValidator(),
+ new OidcIdTokenValidator(clientRegistration)
+ );
+ }
+
private NimbusJwtDecoder buildDecoder(final ClientRegistration
clientRegistration) {
final NimbusJwtDecoder decoder;
@@ -143,19 +156,6 @@ public class StandardOidcIdTokenDecoderFactory implements
JwtDecoderFactory<Clie
return decoder;
}
- /**
- * Get OAuth2 Token Validator based on Spring Security
DefaultOidcIdTokenValidatorFactory
- *
- * @param clientRegistration Client Registration
- * @return OAuth2 Token Validator with Timestamp and OpenID Connect ID
Token Validators
- */
- private OAuth2TokenValidator<Jwt> getTokenValidator(final
ClientRegistration clientRegistration) {
- return new DelegatingOAuth2TokenValidator<>(
- new JwtTimestampValidator(),
- new OidcIdTokenValidator(clientRegistration)
- );
- }
-
private JwsAlgorithm getJwsAlgorithm(final String preferredJwsAlgorithm) {
final JwsAlgorithm jwsAlgorithm;
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/converter/StandardIssuerJwtDecoderTest.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/converter/StandardIssuerJwtDecoderTest.java
new file mode 100644
index 0000000000..b49a8f1ea9
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/converter/StandardIssuerJwtDecoderTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import
org.springframework.security.oauth2.client.registration.ClientRegistration;
+import
org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.jwt.BadJwtException;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class StandardIssuerJwtDecoderTest {
+ private static final String HEADER_PAYLOAD =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuaWZpIiwiaXNzIjoiaHR0cHM6Ly9uaWZpLmFwYWNoZS5vcmcifQ";
+
+ private static final String TOKEN_VALUE =
String.format("%s.cqEFAyICNyF5kDbYtsSgA73auanainaO44_q1GEDXeQ", HEADER_PAYLOAD);
+
+ private static final String ISSUER = "https://nifi.apache.org";
+
+ private static final String LOCALHOST_ISSUER = "https://localhost";
+
+ private static final String TYPE_FIELD = "typ";
+
+ private static final String JWT_TYPE = "JWT";
+
+ @Mock
+ private JwtDecoder applicationJwtDecoder;
+
+ @Mock
+ private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory;
+
+ @Mock
+ private ClientRegistrationRepository clientRegistrationRepository;
+
+ @Mock
+ private ClientRegistration clientRegistration;
+
+ @Mock
+ private ClientRegistration.ProviderDetails providerDetails;
+
+ @Mock
+ private JwtDecoder clientRegistrationDecoder;
+
+ @Test
+ void testClientRegistrationNotConfigured() {
+
when(clientRegistrationRepository.findByRegistrationId(anyString())).thenReturn(null);
+ final StandardIssuerJwtDecoder decoder = new
StandardIssuerJwtDecoder(applicationJwtDecoder, jwtDecoderFactory,
clientRegistrationRepository);
+
+ final Jwt jwt = getJwt();
+ when(applicationJwtDecoder.decode(eq(TOKEN_VALUE))).thenReturn(jwt);
+
+ final Jwt decoded = decoder.decode(TOKEN_VALUE);
+
+ assertEquals(jwt, decoded);
+ }
+
+ @Test
+ void testClientRegistrationConfiguredIssuerFound() {
+ setClientRegistration();
+ final StandardIssuerJwtDecoder decoder = new
StandardIssuerJwtDecoder(applicationJwtDecoder, jwtDecoderFactory,
clientRegistrationRepository);
+
+
when(clientRegistration.getProviderDetails()).thenReturn(providerDetails);
+ when(providerDetails.getIssuerUri()).thenReturn(ISSUER);
+ final Jwt jwt = getJwt();
+
when(clientRegistrationDecoder.decode(eq(TOKEN_VALUE))).thenReturn(jwt);
+
+ final Jwt decoded = decoder.decode(TOKEN_VALUE);
+
+ assertEquals(jwt, decoded);
+ }
+
+ @Test
+ void testClientRegistrationConfiguredIssuerNotFound() {
+ setClientRegistration();
+ final StandardIssuerJwtDecoder decoder = new
StandardIssuerJwtDecoder(applicationJwtDecoder, jwtDecoderFactory,
clientRegistrationRepository);
+
+
when(clientRegistration.getProviderDetails()).thenReturn(providerDetails);
+ when(providerDetails.getIssuerUri()).thenReturn(LOCALHOST_ISSUER);
+ final Jwt jwt = getJwt();
+ when(applicationJwtDecoder.decode(eq(TOKEN_VALUE))).thenReturn(jwt);
+
+ final Jwt decoded = decoder.decode(TOKEN_VALUE);
+
+ assertEquals(jwt, decoded);
+ }
+
+ @Test
+ void testClientRegistrationConfiguredTokenNotFound() {
+ setClientRegistration();
+ final StandardIssuerJwtDecoder decoder = new
StandardIssuerJwtDecoder(applicationJwtDecoder, jwtDecoderFactory,
clientRegistrationRepository);
+
+ assertThrows(BadJwtException.class, () -> decoder.decode(null));
+ }
+
+ @Test
+ void testClientRegistrationConfiguredTokenNotValid() {
+ setClientRegistration();
+ final StandardIssuerJwtDecoder decoder = new
StandardIssuerJwtDecoder(applicationJwtDecoder, jwtDecoderFactory,
clientRegistrationRepository);
+
+ assertThrows(BadJwtException.class, () ->
decoder.decode(String.class.getSimpleName()));
+ }
+
+ private void setClientRegistration() {
+
when(clientRegistrationRepository.findByRegistrationId(anyString())).thenReturn(clientRegistration);
+
when(jwtDecoderFactory.createDecoder(eq(clientRegistration))).thenReturn(clientRegistrationDecoder);
+ }
+
+ private Jwt getJwt() {
+ return Jwt.withTokenValue(TOKEN_VALUE)
+ .header(TYPE_FIELD, JWT_TYPE)
+ .issuedAt(Instant.now())
+ .expiresAt(Instant.now().plus(Duration.ofHours(1)))
+ .build();
+ }
+}