This is an automated email from the ASF dual-hosted git repository.

exceptionfactory 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 26fa6a2587d NIFI-16387 Support Microsoft Entra Access Token 
authentication for SQL Server in DBCP Services (#11722)
26fa6a2587d is described below

commit 26fa6a2587da88cc7ec53370d75870e1ecfc5785
Author: Pierre Villard <[email protected]>
AuthorDate: Tue Sep 29 23:26:02 2026 +0200

    NIFI-16387 Support Microsoft Entra Access Token authentication for SQL 
Server in DBCP Services (#11722)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../rds/AwsRdsIamDatabasePasswordProviderTest.java |   3 +
 .../azure/AzureEntraDatabasePasswordProvider.java  |  90 +++++++-
 .../additionalDetails.md                           |  62 ++++--
 .../AzureEntraDatabasePasswordProviderTest.java    |  58 ++++-
 .../nifi/dbcp/ProviderAwareBasicDataSource.java    |  60 ++++-
 .../nifi/dbcp/AbstractDBCPConnectionPoolTest.java  | 248 +++++++++++++++++++--
 ...GcpCloudSqlIamDatabasePasswordProviderTest.java |   2 +
 ...ovider.java => DatabaseCredentialProperty.java} |  38 +++-
 .../nifi/dbcp/api/DatabasePasswordProvider.java    |  17 +-
 9 files changed, 504 insertions(+), 74 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/rds/AwsRdsIamDatabasePasswordProviderTest.java
 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/rds/AwsRdsIamDatabasePasswordProviderTest.java
index 502da342ad1..f17965f49d2 100644
--- 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/rds/AwsRdsIamDatabasePasswordProviderTest.java
+++ 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/rds/AwsRdsIamDatabasePasswordProviderTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.processors.aws.rds;
 
+import org.apache.nifi.dbcp.api.DatabaseCredentialProperty;
 import org.apache.nifi.dbcp.api.DatabasePasswordProvider;
 import org.apache.nifi.dbcp.api.DatabasePasswordRequestContext;
 import org.apache.nifi.processor.exception.ProcessException;
@@ -32,6 +33,7 @@ import static 
org.apache.nifi.processors.aws.credentials.provider.service.AWSCre
 import static 
org.apache.nifi.processors.aws.rds.AwsRdsIamDatabasePasswordProvider.AWS_CREDENTIALS_PROVIDER_SERVICE;
 import static 
org.apache.nifi.processors.aws.rds.AwsRdsIamDatabasePasswordProvider.TOKEN_REQUEST_ENDPOINT;
 import static org.apache.nifi.processors.aws.region.RegionUtil.REGION;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -76,6 +78,7 @@ class AwsRdsIamDatabasePasswordProviderTest {
                 .build();
 
         final String token = new String(service.getPassword(context));
+        assertEquals(DatabaseCredentialProperty.PASSWORD, 
service.getDatabaseCredentialProperty());
         assertTrue(token.startsWith("%s:%d/".formatted(HOSTNAME, PORT)));
         assertTrue(token.contains("DBUser=" + DB_USER));
     }
diff --git 
a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/services/azure/AzureEntraDatabasePasswordProvider.java
 
b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/services/azure/AzureEntraDatabasePasswordProvider.java
index 53c4acf3e1e..ec675650e45 100644
--- 
a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/services/azure/AzureEntraDatabasePasswordProvider.java
+++ 
b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/java/org/apache/nifi/services/azure/AzureEntraDatabasePasswordProvider.java
@@ -26,10 +26,12 @@ import org.apache.nifi.annotation.lifecycle.OnDisabled;
 import org.apache.nifi.annotation.lifecycle.OnEnabled;
 import org.apache.nifi.components.ConfigVerificationResult;
 import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.components.DescribedValue;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.controller.AbstractControllerService;
 import org.apache.nifi.controller.ConfigurationContext;
 import org.apache.nifi.controller.VerifiableControllerService;
+import org.apache.nifi.dbcp.api.DatabaseCredentialProperty;
 import org.apache.nifi.dbcp.api.DatabasePasswordProvider;
 import org.apache.nifi.dbcp.api.DatabasePasswordRequestContext;
 import org.apache.nifi.logging.ComponentLog;
@@ -41,16 +43,16 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
-@Tags({"azure", "microsoft entra", "jdbc", "database password", "postgresql", 
"mysql", "authentication"})
+@Tags({"azure", "microsoft entra", "jdbc", "database password", "sql server", 
"authentication"})
 @CapabilityDescription("""
-        Acquires short-lived Microsoft Entra database passwords for JDBC 
authentication to Azure Database for PostgreSQL Flexible Server and
-        Azure Database for MySQL Flexible Server. Only Azure Database for 
PostgreSQL Flexible Server and Azure Database for MySQL Flexible
-        Server are supported.
+        Acquires short-lived Microsoft Entra database credentials for JDBC 
authentication. Depending on the configured database target,
+        the credential is supplied to DBCP as either a database password or a 
JDBC access token.
         """)
 public class AzureEntraDatabasePasswordProvider extends 
AbstractControllerService
         implements DatabasePasswordProvider, VerifiableControllerService {
 
     static final String OSS_RDBMS_SCOPE = 
"https://ossrdbms-aad.database.windows.net/.default";;
+    static final String SQL_SERVER_SCOPE = 
"https://database.windows.net/.default";;
     static final String FAILED_CREDENTIAL_RESOLUTION_MESSAGE = "Failed to 
resolve Azure credentials for Microsoft Entra database password.";
     static final String FAILED_TOKEN_ACQUISITION_MESSAGE = "Failed to acquire 
a valid Microsoft Entra database access token.";
     static final String VERIFY_CREDENTIALS_STEP = "Resolve Azure credentials";
@@ -66,12 +68,64 @@ public class AzureEntraDatabasePasswordProvider extends 
AbstractControllerServic
             .required(true)
             .build();
 
+    static final PropertyDescriptor DATABASE_TARGET = new 
PropertyDescriptor.Builder()
+            .name("Database Target")
+            .description("Selects the Microsoft Entra token scope and JDBC 
credential placement used for the target database.")
+            .allowableValues(DatabaseTarget.class)
+            .defaultValue(DatabaseTarget.AZURE_OSS_RDBMS)
+            .required(true)
+            .build();
+
     private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
List.of(
-            AZURE_CREDENTIALS_SERVICE
+            AZURE_CREDENTIALS_SERVICE,
+            DATABASE_TARGET
     );
 
     private final Duration tokenAcquisitionTimeout;
     private volatile AzureCredentialsService azureCredentialsService;
+    private volatile DatabaseTarget databaseTarget;
+
+    enum DatabaseTarget implements DescribedValue {
+        AZURE_OSS_RDBMS(OSS_RDBMS_SCOPE, DatabaseCredentialProperty.PASSWORD, 
"Azure OSS Database",
+                "Azure Database for PostgreSQL and Azure Database for MySQL. 
Requests the Azure OSS RDBMS scope and supplies the token as the JDBC 
password."),
+        SQL_SERVER(SQL_SERVER_SCOPE, DatabaseCredentialProperty.ACCESS_TOKEN, 
"Microsoft SQL Server",
+                "Requests the SQL Server scope and supplies the token using 
the JDBC accessToken property.");
+
+        private final String scope;
+        private final DatabaseCredentialProperty credentialProperty;
+        private final String displayName;
+        private final String description;
+
+        DatabaseTarget(final String scope, final DatabaseCredentialProperty 
credentialProperty, final String displayName, final String description) {
+            this.scope = scope;
+            this.credentialProperty = credentialProperty;
+            this.displayName = displayName;
+            this.description = description;
+        }
+
+        @Override
+        public String getValue() {
+            return name();
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public String getDescription() {
+            return description;
+        }
+
+        private String getScope() {
+            return scope;
+        }
+
+        private DatabaseCredentialProperty getCredentialProperty() {
+            return credentialProperty;
+        }
+    }
 
     public AzureEntraDatabasePasswordProvider() {
         this(DEFAULT_TOKEN_ACQUISITION_TIMEOUT);
@@ -89,11 +143,19 @@ public class AzureEntraDatabasePasswordProvider extends 
AbstractControllerServic
     @OnEnabled
     public void onEnabled(final ConfigurationContext context) {
         azureCredentialsService = resolveAzureCredentialsService(context);
+        databaseTarget = getDatabaseTarget(context);
     }
 
     @OnDisabled
     public void onDisabled() {
         azureCredentialsService = null;
+        databaseTarget = null;
+    }
+
+    @Override
+    public DatabaseCredentialProperty getDatabaseCredentialProperty() {
+        final DatabaseTarget configuredDatabaseTarget = databaseTarget;
+        return configuredDatabaseTarget == null ? 
DatabaseCredentialProperty.PASSWORD : 
configuredDatabaseTarget.getCredentialProperty();
     }
 
     @Override
@@ -101,6 +163,7 @@ public class AzureEntraDatabasePasswordProvider extends 
AbstractControllerServic
         Objects.requireNonNull(requestContext, "Database Password Request 
Context required");
 
         final AzureCredentialsService configuredCredentialsService = 
azureCredentialsService;
+        final DatabaseTarget configuredDatabaseTarget = databaseTarget == null 
? DatabaseTarget.AZURE_OSS_RDBMS : databaseTarget;
         if (configuredCredentialsService == null) {
             throw new ProcessException(FAILED_CREDENTIAL_RESOLUTION_MESSAGE);
         }
@@ -118,7 +181,7 @@ public class AzureEntraDatabasePasswordProvider extends 
AbstractControllerServic
 
         final AccessToken accessToken;
         try {
-            accessToken = 
credential.getToken(createTokenRequestContext()).block(tokenAcquisitionTimeout);
+            accessToken = 
credential.getToken(createTokenRequestContext(configuredDatabaseTarget)).block(tokenAcquisitionTimeout);
         } catch (final RuntimeException e) {
             throw new ProcessException(FAILED_TOKEN_ACQUISITION_MESSAGE);
         }
@@ -146,7 +209,7 @@ public class AzureEntraDatabasePasswordProvider extends 
AbstractControllerServic
                 Outcome.SUCCESSFUL,
                 "Resolved Azure credentials service and current 
TokenCredential."
         ));
-        results.add(verifyAccessToken(verificationCredential, 
verificationLogger));
+        results.add(verifyAccessToken(verificationCredential, 
verificationLogger, getDatabaseTarget(context)));
         return results;
     }
 
@@ -164,10 +227,11 @@ public class AzureEntraDatabasePasswordProvider extends 
AbstractControllerServic
         }
     }
 
-    private ConfigVerificationResult verifyAccessToken(final TokenCredential 
credential, final ComponentLog verificationLogger) {
+    private ConfigVerificationResult verifyAccessToken(final TokenCredential 
credential, final ComponentLog verificationLogger,
+                                                       final DatabaseTarget 
databaseTarget) {
         final AccessToken accessToken;
         try {
-            accessToken = 
credential.getToken(createTokenRequestContext()).block(tokenAcquisitionTimeout);
+            accessToken = 
credential.getToken(createTokenRequestContext(databaseTarget)).block(tokenAcquisitionTimeout);
         } catch (final RuntimeException e) {
             verificationLogger.error(VERIFY_TOKEN_ACQUISITION_FAILED);
             return buildVerificationResult(VERIFY_TOKEN_STEP, Outcome.FAILED, 
VERIFY_TOKEN_ACQUISITION_FAILED);
@@ -189,8 +253,12 @@ public class AzureEntraDatabasePasswordProvider extends 
AbstractControllerServic
         return 
context.getProperty(AZURE_CREDENTIALS_SERVICE).asControllerService(AzureCredentialsService.class);
     }
 
-    private TokenRequestContext createTokenRequestContext() {
-        return new TokenRequestContext().addScopes(OSS_RDBMS_SCOPE);
+    private DatabaseTarget getDatabaseTarget(final ConfigurationContext 
context) {
+        return 
context.getProperty(DATABASE_TARGET).asAllowableValue(DatabaseTarget.class);
+    }
+
+    private TokenRequestContext createTokenRequestContext(final DatabaseTarget 
databaseTarget) {
+        return new TokenRequestContext().addScopes(databaseTarget.getScope());
     }
 
     private Duration requirePositiveDuration(final Duration 
tokenAcquisitionTimeout) {
diff --git 
a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/resources/docs/org.apache.nifi.services.azure.AzureEntraDatabasePasswordProvider/additionalDetails.md
 
b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/resources/docs/org.apache.nifi.services.azure.AzureEntraDatabasePasswordProvider/additionalDetails.md
index 84e3988f5e2..d2de1a2da33 100644
--- 
a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/resources/docs/org.apache.nifi.services.azure.AzureEntraDatabasePasswordProvider/additionalDetails.md
+++ 
b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/main/resources/docs/org.apache.nifi.services.azure.AzureEntraDatabasePasswordProvider/additionalDetails.md
@@ -16,21 +16,20 @@
 ## Summary
 
 `AzureEntraDatabasePasswordProvider` acquires a short-lived Microsoft Entra 
access token and supplies it as the
-database password for a DBCP service. Use it to connect NiFi to Azure Database 
for PostgreSQL Flexible Server or Azure
-Database for MySQL Flexible Server without storing a long-lived database 
password in NiFi. Other database products and
-sovereign Azure clouds are not supported.
+database credential for a DBCP service. The configured **Database Target** 
selects both the Microsoft Entra scope and
+how DBCP applies the credential when it creates a new physical JDBC connection.
 
-The provider supplies a password when DBCP creates a new physical connection. 
Existing pooled connections are not
-reauthenticated when the token expires.
+Existing pooled connections are not reauthenticated when the token expires.
 
 ## Usage
 
 1. Configure an Azure Credentials Service that can obtain credentials for the 
public Azure cloud.
 2. Create and enable `AzureEntraDatabasePasswordProvider`.
 3. Set **Azure Credentials Service** to the configured credentials service.
-4. Configure the DBCP service with the JDBC URL, driver, database user, and 
**Database Password Provider** set to
+4. Set **Database Target** to the desired JDBC credential behavior.
+5. Configure the DBCP service with the JDBC URL, driver, database user, and 
**Database Password Provider** set to
    `AzureEntraDatabasePasswordProvider`.
-5. Run **Verify** on the provider, then run **Verify** on the DBCP service.
+6. Run **Verify** on the provider, then run **Verify** on the DBCP service.
 
 Create the Microsoft Entra principal in the database separately and grant the 
database privileges required by NiFi.
 
@@ -41,16 +40,25 @@ set to **Identity Federation** and select a 
`StandardAzureIdentityFederationToke
 provider accepts an OAuth2 access token provider that supplies the external 
client assertion. The federated identity
 credential in Microsoft Entra must match the assertion issuer, subject, and 
audience.
 
-The password provider requests the public-cloud Azure OSS RDBMS resource. It 
does not require the Azure JDBC
-authentication plugins and does not depend on a particular external assertion 
issuer.
+This provider does not depend on a particular external assertion issuer.
 
-## PostgreSQL Configuration
+## Database Targets
 
-Configure Microsoft Entra authentication on Azure Database for PostgreSQL 
Flexible Server and create the database
-role that corresponds to the Entra principal. Set the DBCP **Database User** 
to that mapped role name.
+| Database Target | Scope | DBCP placement |
+|---|---|---|
+| `AZURE_OSS_RDBMS` | `https://ossrdbms-aad.database.windows.net/.default` | 
Supplies the token as the JDBC password |
+| `SQL_SERVER` | `https://database.windows.net/.default` | Supplies the token 
using JDBC property `accessToken` |
 
-The token is sent using PostgreSQL cleartext-password authentication and must 
be protected by TLS. Use direct port
-5432 as the baseline and set `sslmode=verify-full`, with the client configured 
to trust the server certificate chain.
+For `AZURE_OSS_RDBMS`, DBCP supplies the acquired token as the JDBC password 
and otherwise leaves the configured user
+and dynamic properties unchanged.
+
+### PostgreSQL Configuration
+
+Configure Microsoft Entra authentication on Azure Database for PostgreSQL 
Flexible Server and create the database role
+that corresponds to the Entra principal. Set the DBCP **Database User** to 
that mapped role name.
+
+The token is sent using PostgreSQL cleartext-password authentication and must 
be protected by TLS. Use direct port 5432
+as the baseline and set `sslmode=verify-full`, with the client configured to 
trust the server certificate chain.
 
 | Setting | Value |
 |---|---|
@@ -62,7 +70,7 @@ Azure's built-in PgBouncer on port 6432 must be validated 
separately. Large Entr
 can exceed intermediary protocol buffers even when the same token works 
against direct port 5432. This provider does
 not claim unrestricted PgBouncer compatibility.
 
-## MySQL Configuration
+### MySQL Configuration
 
 Configure Microsoft Entra authentication on Azure Database for MySQL Flexible 
Server and create an Entra user or alias
 for the principal. Set the DBCP **Database User** to the mapped alias. MySQL 
user names are limited to 32 characters, so
@@ -80,13 +88,27 @@ required when this provider supplies the token.
 | JDBC URL | 
`jdbc:mysql://<SERVER>.mysql.database.azure.com:3306/<DATABASE>?sslMode=VERIFY_IDENTITY&allowCleartextPasswords=true`
 |
 | Database User | Microsoft Entra principal's mapped MySQL user or alias |
 
+For `SQL_SERVER`, DBCP applies the token only to the physical connection 
attempt being created. It removes copied JDBC
+properties whose names equal `user`, `userName`, or `password` 
case-insensitively, sets canonical `user` and
+`password` properties to empty strings, and sets the token on JDBC property 
`accessToken`.
+
+When `SQL_SERVER` is selected, DBCP preserves configured `authentication` and 
`integratedSecurity` properties so the
+JDBC driver remains responsible for accepting or rejecting conflicting 
settings.
+
+Do not configure an unprefixed dynamic property named `accessToken`. Commons 
DBCP diagnostic redaction recognizes
+user and password fields, but not arbitrary access token properties. This 
provider supplies the token at physical
+connection time so it does not need to be stored in retained DBCP 
configuration.
+
 ## Verify and Troubleshooting
 
-`AzureEntraDatabasePasswordProvider` **Verify** checks that the configured 
Azure Credentials Service can acquire a
-nonblank token for the Azure OSS RDBMS resource. It does not validate the JDBC 
URL, database server, network
-path, TLS configuration, driver, principal mapping, or database grants. DBCP 
**Verify** checks the actual database
-connection using those settings.
+`AzureEntraDatabasePasswordProvider` **Verify** checks that the configured 
Azure Credentials Service can resolve
+credentials and acquire a nonblank token for the selected **Database Target** 
scope. It does not validate the JDBC
+URL, database server, network path, TLS configuration, driver, principal 
mapping, or database grants.
+
+DBCP **Verify** checks the actual database connection using the configured 
JDBC URL, driver, database user, and any
+other DBCP properties.
 
 If provider **Verify** fails, confirm that the Azure Credentials Service and 
any upstream identity-federation services
 are enabled and configured for the intended Entra application and tenant. If 
provider **Verify** succeeds but DBCP
-**Verify** fails, investigate the JDBC connection, network, TLS, driver, 
database principal mapping, and grants.
\ No newline at end of file
+**Verify** fails, investigate the JDBC connection, network, TLS, driver, 
database principal mapping, grants, and any
+driver-specific conflicts involving `authentication` or `integratedSecurity`.
\ No newline at end of file
diff --git 
a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/test/java/org/apache/nifi/services/azure/AzureEntraDatabasePasswordProviderTest.java
 
b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/test/java/org/apache/nifi/services/azure/AzureEntraDatabasePasswordProviderTest.java
index aa7861f75aa..5dfbf1c1ebe 100644
--- 
a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/test/java/org/apache/nifi/services/azure/AzureEntraDatabasePasswordProviderTest.java
+++ 
b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/src/test/java/org/apache/nifi/services/azure/AzureEntraDatabasePasswordProviderTest.java
@@ -22,6 +22,7 @@ import com.azure.core.credential.TokenRequestContext;
 import org.apache.nifi.components.ConfigVerificationResult;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.controller.AbstractControllerService;
+import org.apache.nifi.dbcp.api.DatabaseCredentialProperty;
 import org.apache.nifi.dbcp.api.DatabasePasswordProvider;
 import org.apache.nifi.dbcp.api.DatabasePasswordRequestContext;
 import org.apache.nifi.processor.exception.ProcessException;
@@ -95,13 +96,28 @@ class AzureEntraDatabasePasswordProviderTest {
         final AzureEntraDatabasePasswordProvider provider = new 
AzureEntraDatabasePasswordProvider();
         final List<PropertyDescriptor> descriptors = 
provider.getSupportedPropertyDescriptors();
 
-        assertEquals(1, descriptors.size());
+        assertEquals(2, descriptors.size());
         
assertEquals(AzureEntraDatabasePasswordProvider.AZURE_CREDENTIALS_SERVICE, 
descriptors.get(0));
+        assertEquals(AzureEntraDatabasePasswordProvider.DATABASE_TARGET, 
descriptors.get(1));
         assertTrue(descriptors.get(0).isRequired());
+        assertTrue(descriptors.get(1).isRequired());
+        
assertEquals(AzureEntraDatabasePasswordProvider.DatabaseTarget.AZURE_OSS_RDBMS.name(),
 descriptors.get(1).getDefaultValue());
+        assertEquals(2, descriptors.get(1).getAllowableValues().size());
+        assertEquals("Azure OSS Database", 
AzureEntraDatabasePasswordProvider.DatabaseTarget.AZURE_OSS_RDBMS.getDisplayName());
+        
assertTrue(AzureEntraDatabasePasswordProvider.DatabaseTarget.AZURE_OSS_RDBMS.getDescription().contains("PostgreSQL"));
+        
assertTrue(AzureEntraDatabasePasswordProvider.DatabaseTarget.AZURE_OSS_RDBMS.getDescription().contains("MySQL"));
+        assertEquals("Microsoft SQL Server", 
AzureEntraDatabasePasswordProvider.DatabaseTarget.SQL_SERVER.getDisplayName());
         final TestRunner runner = 
TestRunners.newTestRunner(NoOpProcessor.class);
 
         runner.addControllerService(PASSWORD_PROVIDER_ID, provider);
         runner.assertNotValid(provider);
+
+        final TestAzureCredentialsService credentialsService = new 
TestAzureCredentialsService(new StaticTokenCredential(validToken(TOKEN_VALUE)));
+        runner.addControllerService(CREDENTIALS_SERVICE_ID, 
credentialsService);
+        runner.enableControllerService(credentialsService);
+        runner.setProperty(provider, 
AzureEntraDatabasePasswordProvider.AZURE_CREDENTIALS_SERVICE, 
CREDENTIALS_SERVICE_ID);
+        runner.setProperty(provider, 
AzureEntraDatabasePasswordProvider.DATABASE_TARGET, "NOT_A_TARGET");
+        runner.assertNotValid(provider);
     }
 
     @Test
@@ -125,12 +141,26 @@ class AzureEntraDatabasePasswordProviderTest {
 
     @Test
     void testGetPasswordRequestsExactOssRdbmsScope() throws Exception {
-        final RecordingTokenCredential credential = new 
RecordingTokenCredential(Mono.just(validToken(TOKEN_VALUE)));
-        final DatabasePasswordProvider provider = 
getProvider(configureRunner(new TestAzureCredentialsService(credential)));
+        final RecordingTokenCredential ossCredential = new 
RecordingTokenCredential(Mono.just(validToken(TOKEN_VALUE)));
+        final DatabasePasswordProvider ossProvider = 
getProvider(configureRunner(new TestAzureCredentialsService(ossCredential)));
+
+        ossProvider.getPassword(requestContext());
 
-        provider.getPassword(requestContext());
+        assertEquals(DatabaseCredentialProperty.PASSWORD, 
ossProvider.getDatabaseCredentialProperty());
+        
assertEquals(List.of(AzureEntraDatabasePasswordProvider.OSS_RDBMS_SCOPE), 
ossCredential.getLastRequestedScopes());
 
-        
assertEquals(List.of(AzureEntraDatabasePasswordProvider.OSS_RDBMS_SCOPE), 
credential.getLastRequestedScopes());
+        final AzureEntraDatabasePasswordProvider 
sqlServerProviderImplementation = new AzureEntraDatabasePasswordProvider();
+        final RecordingTokenCredential sqlServerCredential = new 
RecordingTokenCredential(Mono.just(validToken(REFRESHED_TOKEN_VALUE)));
+        final TestRunner sqlServerRunner = 
configureRunner(sqlServerProviderImplementation, new 
TestAzureCredentialsService(sqlServerCredential), false);
+        sqlServerRunner.setProperty(sqlServerProviderImplementation, 
AzureEntraDatabasePasswordProvider.DATABASE_TARGET,
+                
AzureEntraDatabasePasswordProvider.DatabaseTarget.SQL_SERVER.name());
+        
sqlServerRunner.enableControllerService(sqlServerProviderImplementation);
+        final DatabasePasswordProvider sqlServerProvider = 
getProvider(sqlServerRunner);
+
+        sqlServerProvider.getPassword(requestContext());
+
+        assertEquals(DatabaseCredentialProperty.ACCESS_TOKEN, 
sqlServerProvider.getDatabaseCredentialProperty());
+        
assertEquals(List.of(AzureEntraDatabasePasswordProvider.SQL_SERVER_SCOPE), 
sqlServerCredential.getLastRequestedScopes());
     }
 
     @Test
@@ -254,12 +284,22 @@ class AzureEntraDatabasePasswordProviderTest {
 
     @Test
     void testVerifyUsesExactOssRdbmsScope() throws Exception {
-        final RecordingTokenCredential credential = new 
RecordingTokenCredential(Mono.just(validToken(TOKEN_VALUE)));
-        final TestRunner runner = configureRunner(new 
TestAzureCredentialsService(credential), false);
+        final RecordingTokenCredential ossCredential = new 
RecordingTokenCredential(Mono.just(validToken(TOKEN_VALUE)));
+        final TestRunner ossRunner = configureRunner(new 
TestAzureCredentialsService(ossCredential), false);
+
+        ossRunner.verify(getProviderImplementation(ossRunner), Map.of());
+
+        
assertEquals(List.of(AzureEntraDatabasePasswordProvider.OSS_RDBMS_SCOPE), 
ossCredential.getLastRequestedScopes());
+
+        final AzureEntraDatabasePasswordProvider sqlServerProvider = new 
AzureEntraDatabasePasswordProvider();
+        final RecordingTokenCredential sqlServerCredential = new 
RecordingTokenCredential(Mono.just(validToken(REFRESHED_TOKEN_VALUE)));
+        final TestRunner sqlServerRunner = configureRunner(sqlServerProvider, 
new TestAzureCredentialsService(sqlServerCredential), false);
+        sqlServerRunner.setProperty(sqlServerProvider, 
AzureEntraDatabasePasswordProvider.DATABASE_TARGET,
+                
AzureEntraDatabasePasswordProvider.DatabaseTarget.SQL_SERVER.name());
 
-        runner.verify(getProviderImplementation(runner), Map.of());
+        sqlServerRunner.verify(sqlServerProvider, Map.of());
 
-        
assertEquals(List.of(AzureEntraDatabasePasswordProvider.OSS_RDBMS_SCOPE), 
credential.getLastRequestedScopes());
+        
assertEquals(List.of(AzureEntraDatabasePasswordProvider.SQL_SERVER_SCOPE), 
sqlServerCredential.getLastRequestedScopes());
     }
 
     @Test
diff --git 
a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/ProviderAwareBasicDataSource.java
 
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/ProviderAwareBasicDataSource.java
index 2c1d86acd6b..67e094452ee 100644
--- 
a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/ProviderAwareBasicDataSource.java
+++ 
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/ProviderAwareBasicDataSource.java
@@ -20,17 +20,24 @@ import org.apache.commons.dbcp2.BasicDataSource;
 import org.apache.commons.dbcp2.ConnectionFactory;
 import org.apache.commons.dbcp2.Constants;
 import org.apache.commons.dbcp2.DriverConnectionFactory;
+import org.apache.nifi.dbcp.api.DatabaseCredentialProperty;
 import org.apache.nifi.dbcp.api.DatabasePasswordProvider;
 import org.apache.nifi.dbcp.api.DatabasePasswordRequestContext;
 
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.Arrays;
+import java.util.Locale;
+import java.util.Properties;
 
 /**
  * Extension of {@link BasicDataSource} that supports obtaining database 
passwords from a {@link DatabasePasswordProvider}.
  */
 public class ProviderAwareBasicDataSource extends BasicDataSource {
+    private static final String USER_PROPERTY = "user";
+    private static final String USER_NAME_PROPERTY = "username";
+    private static final String ACCESS_TOKEN_PROPERTY = "accessToken";
+
     private volatile DatabasePasswordProvider databasePasswordProvider;
     private volatile DatabasePasswordRequestContext passwordRequestContext;
 
@@ -73,6 +80,7 @@ public class ProviderAwareBasicDataSource extends 
BasicDataSource {
 
         @Override
         public Connection createConnection() throws SQLException {
+            final DatabaseCredentialProperty credentialProperty = 
passwordProvider.getDatabaseCredentialProperty();
             final char[] passwordCharacters;
             try {
                 passwordCharacters = 
passwordProvider.getPassword(passwordRequestContext);
@@ -84,12 +92,54 @@ public class ProviderAwareBasicDataSource extends 
BasicDataSource {
                 throw new SQLException("Database Password Provider returned an 
empty password");
             }
 
-            final String password = new String(passwordCharacters);
-            Arrays.fill(passwordCharacters, '\0');
+            final String credentialValue;
+            try {
+                credentialValue = new String(passwordCharacters);
+            } finally {
+                Arrays.fill(passwordCharacters, '\0');
+            }
+
+            final Properties connectionProperties = copyConnectionProperties();
+            final String credentialPropertyName = 
configureCredentialProperty(connectionProperties, credentialProperty, 
credentialValue);
+            try {
+                return getDriver().connect(getConnectionString(), 
connectionProperties);
+            } finally {
+                connectionProperties.remove(credentialPropertyName);
+            }
+        }
+
+        private Properties copyConnectionProperties() {
+            final Properties connectionProperties = new Properties();
+            connectionProperties.putAll(getProperties());
+            return connectionProperties;
+        }
+
+        private String configureCredentialProperty(final Properties 
connectionProperties,
+                                                   final 
DatabaseCredentialProperty credentialProperty,
+                                                   final String 
credentialValue) {
+            if (credentialProperty == DatabaseCredentialProperty.ACCESS_TOKEN) 
{
+                removeAccessTokenConflicts(connectionProperties);
+                connectionProperties.put(USER_PROPERTY, "");
+                connectionProperties.put(Constants.KEY_PASSWORD, "");
+                connectionProperties.put(ACCESS_TOKEN_PROPERTY, 
credentialValue);
+                return ACCESS_TOKEN_PROPERTY;
+            }
+
+            connectionProperties.put(Constants.KEY_PASSWORD, credentialValue);
+            return Constants.KEY_PASSWORD;
+        }
+
+        private void removeAccessTokenConflicts(final Properties 
connectionProperties) {
+            connectionProperties.keySet().removeIf(key -> {
+                if (!(key instanceof final String propertyName)) {
+                    return false;
+                }
 
-            // DBCP expects the password value to be in the connection 
properties when creating a new connection
-            getProperties().put(Constants.KEY_PASSWORD, password);
-            return super.createConnection();
+                final String normalizedPropertyName = 
propertyName.toLowerCase(Locale.ROOT);
+                return USER_PROPERTY.equals(normalizedPropertyName)
+                        || USER_NAME_PROPERTY.equals(normalizedPropertyName)
+                        || 
Constants.KEY_PASSWORD.equals(normalizedPropertyName);
+            });
         }
     }
 }
diff --git 
a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/test/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPoolTest.java
 
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/test/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPoolTest.java
index 9ab138751fa..93fda331f0e 100644
--- 
a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/test/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPoolTest.java
+++ 
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/test/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPoolTest.java
@@ -16,11 +16,15 @@
  */
 package org.apache.nifi.dbcp;
 
+import org.apache.commons.dbcp2.ConnectionFactory;
 import org.apache.nifi.components.ConfigVerificationResult;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.controller.AbstractControllerService;
 import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.dbcp.api.DatabaseCredentialProperty;
 import org.apache.nifi.dbcp.api.DatabasePasswordProvider;
+import org.apache.nifi.dbcp.api.DatabasePasswordRequestContext;
 import org.apache.nifi.dbcp.utils.DBCPProperties;
 import org.apache.nifi.dbcp.utils.DataSourceConfiguration;
 import org.apache.nifi.kerberos.KerberosUserService;
@@ -28,26 +32,38 @@ import org.apache.nifi.logging.ComponentLog;
 import org.apache.nifi.util.MockPropertyValue;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import java.sql.Connection;
 import java.sql.Driver;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -55,10 +71,30 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 class AbstractDBCPConnectionPoolTest {
 
-    private static final int MAX_TOTAL = 1;
+    private static final int MAX_TOTAL = 2;
 
     private static final int TIMEOUT = 0;
 
+    private static final String POSTGRESQL_URL = "jdbc:postgresql://example";
+
+    private static final String POSTGRESQL_DRIVER = "org.postgresql.Driver";
+
+    private static final String POSTGRESQL_USER = "dbuser";
+
+    private static final String SQL_SERVER_URL = 
"jdbc:sqlserver://example.database.windows.net:1433;databaseName=test";
+
+    private static final String SQL_SERVER_USER = "configured-user";
+
+    private static final String CONFIGURED_PASSWORD = "configured-password";
+
+    private static final Map<String, String> SQL_SERVER_CONNECTION_PROPERTIES 
= Map.of(
+            "USER", "dynamic-user",
+            "UserName", "dynamic-user-name",
+            "PASSWORD", "dynamic-password",
+            "authentication", "ActiveDirectoryManagedIdentity",
+            "integratedSecurity", "true"
+    );
+
     @Mock
     Driver driver;
 
@@ -122,6 +158,29 @@ class AbstractDBCPConnectionPoolTest {
     @Test
     void testVerifyUsesDatabasePasswordProvider() throws SQLException {
         final AbstractDBCPConnectionPool connectionPool = new 
MockDBCPConnectionPool();
+        final ExposedProviderAwareBasicDataSource passwordDataSource = new 
ExposedProviderAwareBasicDataSource();
+        final ExposedProviderAwareBasicDataSource accessTokenDataSource = new 
ExposedProviderAwareBasicDataSource();
+        final AtomicReference<DatabaseCredentialProperty> credentialProperty = 
new AtomicReference<>(DatabaseCredentialProperty.PASSWORD);
+        final List<char[]> issuedCredentials = new ArrayList<>();
+        final AtomicInteger issuedCredentialIndex = new AtomicInteger();
+        final String[] credentialValues = {
+                "password-token-1",
+                "password-token-2",
+                "access-token-success",
+                "access-token-failure"
+        };
+        final CountDownLatch passwordConnectEntered = new CountDownLatch(2);
+        final CountDownLatch releasePasswordConnect = new CountDownLatch(1);
+        final List<Properties> passwordSnapshots = new 
CopyOnWriteArrayList<>();
+        final List<Properties> passwordLiveReferences = new 
CopyOnWriteArrayList<>();
+        final AtomicReference<Properties> accessTokenSuccessSnapshot = new 
AtomicReference<>();
+        final AtomicReference<Properties> accessTokenFailureSnapshot = new 
AtomicReference<>();
+        final AtomicReference<Properties> accessTokenSuccessLiveReference = 
new AtomicReference<>();
+        final AtomicReference<Properties> accessTokenFailureLiveReference = 
new AtomicReference<>();
+        final AtomicReference<String> connectionMode = new 
AtomicReference<>("VERIFY");
+        final AtomicInteger accessTokenAttempt = new AtomicInteger();
+
+        assertEquals(DatabaseCredentialProperty.PASSWORD, new 
MockDatabasePasswordProvider().getDatabaseCredentialProperty());
 
         mockContextDatabaseProperties();
         mockDataSourceConfigurationDefaults();
@@ -132,16 +191,162 @@ class AbstractDBCPConnectionPoolTest {
         
when(configurationContext.getProperty(eq(DBCPProperties.PASSWORD_SOURCE))).thenReturn(propertyValue(DBCPProperties.PASSWORD_SOURCE,
                 DBCPProperties.PasswordSource.PASSWORD_PROVIDER.getValue()));
         when(connection.isValid(eq(TIMEOUT))).thenReturn(true);
-        
when(databasePasswordProvider.getPassword(any())).thenAnswer(invocation -> 
"token".toCharArray());
-
-        final ArgumentCaptor<Properties> propertiesCaptor = 
ArgumentCaptor.forClass(Properties.class);
-        when(driver.connect(any(), 
propertiesCaptor.capture())).thenReturn(connection);
+        
when(databasePasswordProvider.getDatabaseCredentialProperty()).thenAnswer(invocation
 -> credentialProperty.get());
+        
when(databasePasswordProvider.getPassword(any())).thenAnswer(invocation -> {
+            final char[] credential = 
credentialValues[issuedCredentialIndex.getAndIncrement()].toCharArray();
+            issuedCredentials.add(credential);
+            return credential;
+        });
+        when(driver.connect(any(), any())).thenAnswer(invocation -> {
+            final Properties properties = invocation.getArgument(1);
+            final Properties snapshot = new Properties();
+            snapshot.putAll(properties);
+
+            if ("VERIFY".equals(connectionMode.get())) {
+                return connection;
+            }
+
+            if ("PASSWORD".equals(connectionMode.get())) {
+                passwordSnapshots.add(snapshot);
+                passwordLiveReferences.add(properties);
+                passwordConnectEntered.countDown();
+                assertTrue(releasePasswordConnect.await(5, TimeUnit.SECONDS));
+                return connection;
+            }
+
+            if (accessTokenAttempt.getAndIncrement() == 0) {
+                accessTokenSuccessSnapshot.set(snapshot);
+                accessTokenSuccessLiveReference.set(properties);
+                return connection;
+            }
+
+            accessTokenFailureSnapshot.set(snapshot);
+            accessTokenFailureLiveReference.set(properties);
+            throw new SQLException("driver failure");
+        });
 
         connectionPool.verify(configurationContext, componentLog, 
Collections.emptyMap());
+        issuedCredentialIndex.set(0);
+        issuedCredentials.clear();
+        connectionMode.set("PASSWORD");
 
-        final List<Properties> capturedProperties = 
propertiesCaptor.getAllValues();
-        assertTrue(capturedProperties.stream().anyMatch(props -> 
"token".equals(props.getProperty("password"))));
         verify(databasePasswordProvider, atLeastOnce()).getPassword(any());
+        verify(databasePasswordProvider, 
atLeastOnce()).getDatabaseCredentialProperty();
+
+        passwordDataSource.setDriver(driver);
+        passwordDataSource.setUrl(POSTGRESQL_URL);
+        passwordDataSource.setUsername(POSTGRESQL_USER);
+        passwordDataSource.setPassword(CONFIGURED_PASSWORD);
+        passwordDataSource.setMaxTotal(MAX_TOTAL);
+        passwordDataSource.addConnectionProperty("ssl", "true");
+        
passwordDataSource.setDatabasePasswordProvider(databasePasswordProvider, 
DatabasePasswordRequestContext.builder()
+                .jdbcUrl(POSTGRESQL_URL)
+                .driverClassName(POSTGRESQL_DRIVER)
+                .databaseUser(POSTGRESQL_USER)
+                .connectionProperties(Map.of("ssl", "true"))
+                .build());
+
+        final ConnectionFactory passwordConnectionFactory = 
passwordDataSource.callCreateConnectionFactory();
+        final ExecutorService executorService = 
Executors.newFixedThreadPool(2);
+        try {
+            final Future<Connection> firstConnection = 
executorService.submit(passwordConnectionFactory::createConnection);
+            final Future<Connection> secondConnection = 
executorService.submit(passwordConnectionFactory::createConnection);
+
+            assertTrue(passwordConnectEntered.await(5, TimeUnit.SECONDS));
+            releasePasswordConnect.countDown();
+
+            firstConnection.get(5, TimeUnit.SECONDS).close();
+            secondConnection.get(5, TimeUnit.SECONDS).close();
+        } catch (final Exception e) {
+            throw new AssertionError(e);
+        } finally {
+            executorService.shutdownNow();
+            passwordDataSource.close();
+        }
+
+        assertEquals(2, passwordSnapshots.size());
+        assertNotSame(passwordLiveReferences.get(0), 
passwordLiveReferences.get(1));
+        assertTrue(passwordSnapshots.stream().allMatch(properties -> 
"true".equals(properties.getProperty("ssl"))));
+        assertTrue(passwordSnapshots.stream().anyMatch(properties -> 
"password-token-1".equals(properties.getProperty("password"))));
+        assertTrue(passwordSnapshots.stream().anyMatch(properties -> 
"password-token-2".equals(properties.getProperty("password"))));
+        assertTrue(passwordLiveReferences.stream().allMatch(properties -> 
properties.getProperty("password") == null));
+
+        credentialProperty.set(DatabaseCredentialProperty.ACCESS_TOKEN);
+        connectionMode.set("ACCESS");
+        accessTokenDataSource.setDriver(driver);
+        accessTokenDataSource.setUrl(SQL_SERVER_URL);
+        accessTokenDataSource.setUsername(SQL_SERVER_USER);
+        accessTokenDataSource.setPassword(CONFIGURED_PASSWORD);
+        
SQL_SERVER_CONNECTION_PROPERTIES.forEach(accessTokenDataSource::addConnectionProperty);
+        
accessTokenDataSource.setDatabasePasswordProvider(databasePasswordProvider, 
DatabasePasswordRequestContext.builder()
+                .jdbcUrl(SQL_SERVER_URL)
+                
.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
+                .databaseUser(SQL_SERVER_USER)
+                .connectionProperties(SQL_SERVER_CONNECTION_PROPERTIES)
+                .build());
+
+        final ConnectionFactory accessTokenConnectionFactory = 
accessTokenDataSource.callCreateConnectionFactory();
+        try {
+            accessTokenConnectionFactory.createConnection().close();
+            final SQLException exception = assertThrows(SQLException.class, 
accessTokenConnectionFactory::createConnection);
+            assertEquals("driver failure", exception.getMessage());
+        } finally {
+            accessTokenDataSource.close();
+        }
+
+        final Properties successfulAccessTokenSnapshot = 
accessTokenSuccessSnapshot.get();
+        final Properties failedAccessTokenSnapshot = 
accessTokenFailureSnapshot.get();
+        assertNotNull(successfulAccessTokenSnapshot);
+        assertNotNull(failedAccessTokenSnapshot);
+        assertEquals("access-token-success", 
successfulAccessTokenSnapshot.getProperty("accessToken"));
+        assertEquals("access-token-failure", 
failedAccessTokenSnapshot.getProperty("accessToken"));
+        assertEquals("", successfulAccessTokenSnapshot.getProperty("user"));
+        assertEquals("", 
successfulAccessTokenSnapshot.getProperty("password"));
+        assertEquals("", failedAccessTokenSnapshot.getProperty("user"));
+        assertEquals("", failedAccessTokenSnapshot.getProperty("password"));
+        assertNull(successfulAccessTokenSnapshot.getProperty("USER"));
+        assertNull(successfulAccessTokenSnapshot.getProperty("UserName"));
+        assertNull(successfulAccessTokenSnapshot.getProperty("PASSWORD"));
+        assertNull(failedAccessTokenSnapshot.getProperty("USER"));
+        assertNull(failedAccessTokenSnapshot.getProperty("UserName"));
+        assertNull(failedAccessTokenSnapshot.getProperty("PASSWORD"));
+        assertEquals("ActiveDirectoryManagedIdentity", 
successfulAccessTokenSnapshot.getProperty("authentication"));
+        assertEquals("true", 
successfulAccessTokenSnapshot.getProperty("integratedSecurity"));
+        assertEquals("ActiveDirectoryManagedIdentity", 
failedAccessTokenSnapshot.getProperty("authentication"));
+        assertEquals("true", 
failedAccessTokenSnapshot.getProperty("integratedSecurity"));
+        
assertNull(accessTokenSuccessLiveReference.get().getProperty("accessToken"));
+        
assertNull(accessTokenFailureLiveReference.get().getProperty("accessToken"));
+        assertEquals("", 
accessTokenSuccessLiveReference.get().getProperty("user"));
+        assertEquals("", 
accessTokenSuccessLiveReference.get().getProperty("password"));
+        assertEquals("", 
accessTokenFailureLiveReference.get().getProperty("user"));
+        assertEquals("", 
accessTokenFailureLiveReference.get().getProperty("password"));
+
+        assertTrue(issuedCredentials.stream().allMatch(characters -> {
+            for (final char character : characters) {
+                if (character != '\0') {
+                    return false;
+                }
+            }
+            return true;
+        }));
+
+        doThrow(new IllegalStateException("provider 
failure")).when(databasePasswordProvider).getPassword(any());
+        final ProviderAwareBasicDataSource failureDataSource = new 
ProviderAwareBasicDataSource();
+        failureDataSource.setDriver(driver);
+        failureDataSource.setUrl(POSTGRESQL_URL);
+        
failureDataSource.setDatabasePasswordProvider(databasePasswordProvider, 
DatabasePasswordRequestContext.builder()
+                .jdbcUrl(POSTGRESQL_URL)
+                .driverClassName(POSTGRESQL_DRIVER)
+                .databaseUser(POSTGRESQL_USER)
+                .build());
+        try {
+            final SQLException exception = assertThrows(SQLException.class, 
failureDataSource::getConnection);
+            assertTrue(exception.getMessage().contains("Failed to obtain 
database password from provider"));
+            assertTrue(exception.getCause().getMessage().contains("Failed to 
obtain database password from provider"));
+            assertEquals("provider failure", 
exception.getCause().getCause().getMessage());
+        } finally {
+            failureDataSource.close();
+        }
     }
 
     @Test
@@ -152,9 +357,9 @@ class AbstractDBCPConnectionPoolTest {
     }
 
     private void mockDataSourceConfigurationDefaults() {
-        
when(dataSourceConfiguration.getUrl()).thenReturn("jdbc:postgresql://example");
-        
when(dataSourceConfiguration.getDriverName()).thenReturn("org.postgresql.Driver");
-        when(dataSourceConfiguration.getUserName()).thenReturn("dbuser");
+        when(dataSourceConfiguration.getUrl()).thenReturn(POSTGRESQL_URL);
+        
when(dataSourceConfiguration.getDriverName()).thenReturn(POSTGRESQL_DRIVER);
+        
when(dataSourceConfiguration.getUserName()).thenReturn(POSTGRESQL_USER);
         when(dataSourceConfiguration.getPassword()).thenReturn("secret");
         when(dataSourceConfiguration.getValidationQuery()).thenReturn(null);
         when(dataSourceConfiguration.getMaxWaitMillis()).thenReturn(1000L);
@@ -169,10 +374,10 @@ class AbstractDBCPConnectionPoolTest {
 
     private void mockContextDatabaseProperties() {
         
when(configurationContext.getProperties()).thenReturn(Collections.emptyMap());
-        
lenient().when(configurationContext.getProperty(eq(DBCPProperties.DATABASE_URL))).thenReturn(propertyValue(DBCPProperties.DATABASE_URL,
 "jdbc:postgresql://example"));
-        
lenient().when(configurationContext.getProperty(eq(DBCPProperties.DB_DRIVERNAME))).thenReturn(propertyValue(DBCPProperties.DB_DRIVERNAME,
 "org.postgresql.Driver"));
+        
lenient().when(configurationContext.getProperty(eq(DBCPProperties.DATABASE_URL))).thenReturn(propertyValue(DBCPProperties.DATABASE_URL,
 POSTGRESQL_URL));
+        
lenient().when(configurationContext.getProperty(eq(DBCPProperties.DB_DRIVERNAME))).thenReturn(propertyValue(DBCPProperties.DB_DRIVERNAME,
 POSTGRESQL_DRIVER));
         
lenient().when(configurationContext.getProperty(eq(DBCPProperties.DB_DRIVER_LOCATION))).thenReturn(propertyValue(DBCPProperties.DB_DRIVER_LOCATION,
 ""));
-        
lenient().when(configurationContext.getProperty(eq(DBCPProperties.DB_USER))).thenReturn(propertyValue(DBCPProperties.DB_USER,
 "dbuser"));
+        
lenient().when(configurationContext.getProperty(eq(DBCPProperties.DB_USER))).thenReturn(propertyValue(DBCPProperties.DB_USER,
 POSTGRESQL_USER));
         
lenient().when(configurationContext.getProperty(eq(DBCPProperties.DB_PASSWORD))).thenReturn(propertyValue(DBCPProperties.DB_PASSWORD,
 "secret"));
         
lenient().when(configurationContext.getProperty(eq(DBCPProperties.PASSWORD_SOURCE))).thenReturn(propertyValue(DBCPProperties.PASSWORD_SOURCE,
                 DBCPProperties.PasswordSource.PASSWORD.getValue()));
@@ -198,4 +403,19 @@ class AbstractDBCPConnectionPoolTest {
             return getDatabasePasswordProvider(context);
         }
     }
+
+    private static class ExposedProviderAwareBasicDataSource extends 
ProviderAwareBasicDataSource {
+
+        private ConnectionFactory callCreateConnectionFactory() throws 
SQLException {
+            return createConnectionFactory();
+        }
+    }
+
+    private static class MockDatabasePasswordProvider extends 
AbstractControllerService implements DatabasePasswordProvider {
+
+        @Override
+        public char[] getPassword(final DatabasePasswordRequestContext 
requestContext) {
+            return new char[0];
+        }
+    }
 }
diff --git 
a/nifi-extension-bundles/nifi-gcp-bundle/nifi-gcp-processors/src/test/java/org/apache/nifi/processors/gcp/cloudsql/GcpCloudSqlIamDatabasePasswordProviderTest.java
 
b/nifi-extension-bundles/nifi-gcp-bundle/nifi-gcp-processors/src/test/java/org/apache/nifi/processors/gcp/cloudsql/GcpCloudSqlIamDatabasePasswordProviderTest.java
index 8b689aa6992..4af23d82b0d 100644
--- 
a/nifi-extension-bundles/nifi-gcp-bundle/nifi-gcp-processors/src/test/java/org/apache/nifi/processors/gcp/cloudsql/GcpCloudSqlIamDatabasePasswordProviderTest.java
+++ 
b/nifi-extension-bundles/nifi-gcp-bundle/nifi-gcp-processors/src/test/java/org/apache/nifi/processors/gcp/cloudsql/GcpCloudSqlIamDatabasePasswordProviderTest.java
@@ -25,6 +25,7 @@ import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.PropertyValue;
 import org.apache.nifi.controller.AbstractControllerService;
 import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.dbcp.api.DatabaseCredentialProperty;
 import org.apache.nifi.dbcp.api.DatabasePasswordProvider;
 import org.apache.nifi.dbcp.api.DatabasePasswordRequestContext;
 import org.apache.nifi.gcp.credentials.service.GCPCredentialsService;
@@ -121,6 +122,7 @@ class GcpCloudSqlIamDatabasePasswordProviderTest {
         assertEquals(1, rootCredentials.getCreateScopedCount());
         assertEquals(List.of(SQLSERVICE_LOGIN_SCOPE), 
rootCredentials.getLastRequestedScopes());
         assertSame(scopedCredentials, getScopedCredentials(provider));
+        assertEquals(DatabaseCredentialProperty.PASSWORD, 
provider.getDatabaseCredentialProperty());
         assertEquals(TOKEN_VALUE, new 
String(provider.getPassword(requestContext())));
         assertEquals(TOKEN_VALUE, new 
String(provider.getPassword(requestContext())));
         assertEquals(0, scopedCredentials.getRefreshAccessTokenCount());
diff --git 
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabasePasswordProvider.java
 
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabaseCredentialProperty.java
similarity index 52%
copy from 
nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabasePasswordProvider.java
copy to 
nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabaseCredentialProperty.java
index 3826b5ecc0d..f30165c83a7 100644
--- 
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabasePasswordProvider.java
+++ 
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabaseCredentialProperty.java
@@ -16,18 +16,34 @@
  */
 package org.apache.nifi.dbcp.api;
 
-import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.components.DescribedValue;
 
 /**
- * Supplies database passwords on-demand for Controller Services that 
establish JDBC connections.
+ * Describes the JDBC property used to supply a database credential when 
establishing a connection.
  */
-public interface DatabasePasswordProvider extends ControllerService {
-
-    /**
-     * Returns a password to be used when establishing a database connection.
-     *
-     * @param requestContext context for the database password request
-     * @return password characters
-     */
-    char[] getPassword(DatabasePasswordRequestContext requestContext);
+public enum DatabaseCredentialProperty implements DescribedValue {
+
+    PASSWORD("Supply the credential using the JDBC password property."),
+    ACCESS_TOKEN("Supply the credential using a JDBC access token property.");
+
+    private final String description;
+
+    DatabaseCredentialProperty(final String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String getValue() {
+        return name();
+    }
+
+    @Override
+    public String getDisplayName() {
+        return name();
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
 }
diff --git 
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabasePasswordProvider.java
 
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabasePasswordProvider.java
index 3826b5ecc0d..07ddc54d219 100644
--- 
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabasePasswordProvider.java
+++ 
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-api/src/main/java/org/apache/nifi/dbcp/api/DatabasePasswordProvider.java
@@ -19,15 +19,24 @@ package org.apache.nifi.dbcp.api;
 import org.apache.nifi.controller.ControllerService;
 
 /**
- * Supplies database passwords on-demand for Controller Services that 
establish JDBC connections.
+ * Supplies database credentials on-demand for Controller Services that 
establish JDBC connections.
  */
 public interface DatabasePasswordProvider extends ControllerService {
 
     /**
-     * Returns a password to be used when establishing a database connection.
+     * Returns the JDBC property used to supply the credential when 
establishing a database connection.
      *
-     * @param requestContext context for the database password request
-     * @return password characters
+     * @return credential property used for connection attempts
+     */
+    default DatabaseCredentialProperty getDatabaseCredentialProperty() {
+        return DatabaseCredentialProperty.PASSWORD;
+    }
+
+    /**
+     * Returns credential characters to be used when establishing a database 
connection.
+     *
+     * @param requestContext context for the database credential request
+     * @return credential characters
      */
     char[] getPassword(DatabasePasswordRequestContext requestContext);
 }

Reply via email to