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

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal-reqsign.git


The following commit(s) were added to refs/heads/main by this push:
     new 41f5df7  feat: Allow configure from default chain (#620)
41f5df7 is described below

commit 41f5df7fcb7ec79b253dccf93dae167e151b88a6
Author: Xuanwo <[email protected]>
AuthorDate: Thu Sep 11 17:25:37 2025 +0800

    feat: Allow configure from default chain (#620)
    
    **This PR was primarily authored with Codex using GPT-5 and then
    hand-reviewed by me. I AM responsible for every change made in this PR.
    I aimed to keep it aligned with our goals, though I may have missed
    minor issues. Please flag anything that feels off, I'll fix it
    quickly.**
    
    ---------
    
    Signed-off-by: Xuanwo <[email protected]>
---
 .../provide_credential/assume_role_with_oidc.rs    |  22 +-
 .../aliyun-oss/src/provide_credential/default.rs   |  86 ++++-
 services/aliyun-oss/src/provide_credential/env.rs  |   2 +-
 services/aliyun-oss/src/provide_credential/mod.rs  |   2 +-
 .../assume_role_with_web_identity.rs               |  14 +-
 services/aws-v4/src/provide_credential/default.rs  | 370 ++++++++++++++++++--
 services/aws-v4/src/provide_credential/env.rs      |   2 +-
 services/aws-v4/src/provide_credential/imds.rs     |  36 +-
 services/aws-v4/src/provide_credential/mod.rs      |   2 +-
 services/aws-v4/src/provide_credential/profile.rs  |   2 +-
 .../src/provide_credential/azure_cli.rs            |   2 +-
 .../src/provide_credential/client_secret.rs        |  29 +-
 .../src/provide_credential/default.rs              | 275 ++++++++++++++-
 .../azure-storage/src/provide_credential/imds.rs   |  14 +-
 .../azure-storage/src/provide_credential/mod.rs    |   2 +-
 .../src/provide_credential/workload_identity.rs    |  14 +-
 services/google/src/provide_credential/default.rs  | 376 ++++++++++++++-------
 services/google/src/provide_credential/mod.rs      |   3 +-
 .../google/src/provide_credential/vm_metadata.rs   |  15 +-
 .../tests/credential_providers/external_account.rs |  28 +-
 .../impersonated_service_account.rs                |  42 ++-
 .../src/provide_credential/default.rs              |  58 +++-
 .../huaweicloud-obs/src/provide_credential/env.rs  |   6 +-
 .../huaweicloud-obs/src/provide_credential/mod.rs  |   2 +-
 .../oracle/src/provide_credential/config_file.rs   |   6 +-
 services/oracle/src/provide_credential/default.rs  |  88 ++++-
 services/oracle/src/provide_credential/env.rs      |   6 +-
 services/oracle/src/provide_credential/mod.rs      |   2 +-
 .../assume_role_with_web_identity.rs               |   2 +-
 .../tencent-cos/src/provide_credential/default.rs  |  93 ++++-
 services/tencent-cos/src/provide_credential/env.rs |   6 +-
 services/tencent-cos/src/provide_credential/mod.rs |   2 +-
 32 files changed, 1342 insertions(+), 267 deletions(-)

diff --git 
a/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs 
b/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
index d8ff060..f30c751 100644
--- a/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
+++ b/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
@@ -29,17 +29,29 @@ use serde::Deserialize;
 /// - `ALIBABA_CLOUD_OIDC_PROVIDER_ARN`: The ARN of the OIDC provider
 /// - `ALIBABA_CLOUD_OIDC_TOKEN_FILE`: Path to the OIDC token file
 /// - `ALIBABA_CLOUD_STS_ENDPOINT`: Optional custom STS endpoint
-#[derive(Debug, Default)]
-pub struct AssumeRoleWithOidcCredentialProvider {}
+#[derive(Debug, Default, Clone)]
+pub struct AssumeRoleWithOidcCredentialProvider {
+    sts_endpoint: Option<String>,
+}
 
 impl AssumeRoleWithOidcCredentialProvider {
     /// Create a new `AssumeRoleWithOidcCredentialProvider` instance.
     /// This will read configuration from environment variables at runtime.
     pub fn new() -> Self {
-        Self {}
+        Self::default()
+    }
+
+    /// Set the STS endpoint.
+    pub fn with_sts_endpoint(mut self, endpoint: impl Into<String>) -> Self {
+        self.sts_endpoint = Some(endpoint.into());
+        self
     }
 
-    fn get_sts_endpoint(envs: &std::collections::HashMap<String, String>) -> 
String {
+    fn get_sts_endpoint(&self, envs: &std::collections::HashMap<String, 
String>) -> String {
+        if let Some(endpoint) = &self.sts_endpoint {
+            return endpoint.clone();
+        }
+
         match envs.get(ALIBABA_CLOUD_STS_ENDPOINT) {
             Some(endpoint) => format!("https://{endpoint}";),
             None => "https://sts.aliyuncs.com".to_string(),
@@ -71,7 +83,7 @@ impl ProvideCredential for 
AssumeRoleWithOidcCredentialProvider {
         // Construct request to Aliyun STS Service.
         let url = format!(
             
"{}/?Action=AssumeRoleWithOIDC&OIDCProviderArn={}&RoleArn={}&RoleSessionName={}&Format=JSON&Version=2015-04-01&Timestamp={}&OIDCToken={}",
-            Self::get_sts_endpoint(&envs),
+            self.get_sts_endpoint(&envs),
             provider_arn,
             role_arn,
             role_session_name,
diff --git a/services/aliyun-oss/src/provide_credential/default.rs 
b/services/aliyun-oss/src/provide_credential/default.rs
index 878d7af..d6f7190 100644
--- a/services/aliyun-oss/src/provide_credential/default.rs
+++ b/services/aliyun-oss/src/provide_credential/default.rs
@@ -38,13 +38,14 @@ impl Default for DefaultCredentialProvider {
 }
 
 impl DefaultCredentialProvider {
-    /// Create a new `DefaultCredentialProvider` instance.
-    pub fn new() -> Self {
-        let chain = ProvideCredentialChain::new()
-            .push(EnvCredentialProvider::new())
-            .push(AssumeRoleWithOidcCredentialProvider::new());
+    /// Create a builder to configure the default credential chain.
+    pub fn builder() -> DefaultCredentialProviderBuilder {
+        DefaultCredentialProviderBuilder::default()
+    }
 
-        Self { chain }
+    /// Create a new `DefaultCredentialProvider` instance using the default 
chain.
+    pub fn new() -> Self {
+        Self::builder().build()
     }
 
     /// Create with a custom credential chain.
@@ -74,6 +75,79 @@ impl DefaultCredentialProvider {
     }
 }
 
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_*` to customize providers and `disable_*(bool)` to control
+/// participation in the chain. Finally call `build()` to create the provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+    env: Option<EnvCredentialProvider>,
+    assume_role: Option<AssumeRoleWithOidcCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+    /// Create a new builder with default state.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Configure the environment credential provider.
+    pub fn configure_env<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+    {
+        let p = self.env.take().unwrap_or_default();
+        self.env = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the environment provider.
+    pub fn disable_env(mut self, disable: bool) -> Self {
+        if disable {
+            self.env = None;
+        } else if self.env.is_none() {
+            self.env = Some(EnvCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the OIDC assume-role credential provider.
+    pub fn configure_assume_role<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(AssumeRoleWithOidcCredentialProvider) -> 
AssumeRoleWithOidcCredentialProvider,
+    {
+        let p = self.assume_role.take().unwrap_or_default();
+        self.assume_role = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the assume-role provider.
+    pub fn disable_assume_role(mut self, disable: bool) -> Self {
+        if disable {
+            self.assume_role = None;
+        } else if self.assume_role.is_none() {
+            self.assume_role = 
Some(AssumeRoleWithOidcCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Build the `DefaultCredentialProvider` with the configured options.
+    pub fn build(self) -> DefaultCredentialProvider {
+        let mut chain = ProvideCredentialChain::new();
+        if let Some(p) = self.env {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(EnvCredentialProvider::new());
+        }
+        if let Some(p) = self.assume_role {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(AssumeRoleWithOidcCredentialProvider::new());
+        }
+        DefaultCredentialProvider::with_chain(chain)
+    }
+}
+
 #[async_trait]
 impl ProvideCredential for DefaultCredentialProvider {
     type Credential = Credential;
diff --git a/services/aliyun-oss/src/provide_credential/env.rs 
b/services/aliyun-oss/src/provide_credential/env.rs
index d0650d1..b360802 100644
--- a/services/aliyun-oss/src/provide_credential/env.rs
+++ b/services/aliyun-oss/src/provide_credential/env.rs
@@ -25,7 +25,7 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// - `ALIBABA_CLOUD_ACCESS_KEY_ID`: The Alibaba Cloud access key ID
 /// - `ALIBABA_CLOUD_ACCESS_KEY_SECRET`: The Alibaba Cloud access key secret
 /// - `ALIBABA_CLOUD_SECURITY_TOKEN`: The Alibaba Cloud security token 
(optional)
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
 pub struct EnvCredentialProvider;
 
 impl EnvCredentialProvider {
diff --git a/services/aliyun-oss/src/provide_credential/mod.rs 
b/services/aliyun-oss/src/provide_credential/mod.rs
index e7ffa1a..8bb7fe2 100644
--- a/services/aliyun-oss/src/provide_credential/mod.rs
+++ b/services/aliyun-oss/src/provide_credential/mod.rs
@@ -19,7 +19,7 @@ mod assume_role_with_oidc;
 pub use assume_role_with_oidc::AssumeRoleWithOidcCredentialProvider;
 
 mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
 
 mod env;
 pub use env::EnvCredentialProvider;
diff --git 
a/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs 
b/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
index ac87f41..7bef29f 100644
--- a/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
+++ b/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
@@ -31,7 +31,7 @@ use std::path::PathBuf;
 /// This provider reads configuration from:
 /// 1. Constructor parameters (if provided)
 /// 2. Environment variables (when constructor parameters are not set)
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
 pub struct AssumeRoleWithWebIdentityCredentialProvider {
     // Web Identity configuration
     role_arn: Option<String>,
@@ -60,6 +60,18 @@ impl AssumeRoleWithWebIdentityCredentialProvider {
         }
     }
 
+    /// Set the role ARN.
+    pub fn with_role_arn(mut self, role_arn: impl Into<String>) -> Self {
+        self.role_arn = Some(role_arn.into());
+        self
+    }
+
+    /// Set the web identity token file path.
+    pub fn with_web_identity_token_file(mut self, token_file: impl 
Into<PathBuf>) -> Self {
+        self.web_identity_token_file = Some(token_file.into());
+        self
+    }
+
     /// Set the role session name.
     pub fn with_role_session_name(mut self, name: String) -> Self {
         self.role_session_name = Some(name);
diff --git a/services/aws-v4/src/provide_credential/default.rs 
b/services/aws-v4/src/provide_credential/default.rs
index c8e43d4..0b388f4 100644
--- a/services/aws-v4/src/provide_credential/default.rs
+++ b/services/aws-v4/src/provide_credential/default.rs
@@ -48,29 +48,14 @@ impl Default for DefaultCredentialProvider {
 }
 
 impl DefaultCredentialProvider {
-    /// Create a new `DefaultCredentialProvider` instance.
-    pub fn new() -> Self {
-        let mut chain = ProvideCredentialChain::new()
-            .push(EnvCredentialProvider::new())
-            .push(ProfileCredentialProvider::new());
-
-        #[cfg(not(target_arch = "wasm32"))]
-        {
-            chain = chain.push(SSOCredentialProvider::new());
-        }
-
-        chain = chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
-
-        #[cfg(not(target_arch = "wasm32"))]
-        {
-            chain = chain.push(ProcessCredentialProvider::new());
-        }
-
-        chain = chain
-            .push(ECSCredentialProvider::new())
-            .push(IMDSv2CredentialProvider::new());
+    /// Create a builder to configure the default credential chain.
+    pub fn builder() -> DefaultCredentialProviderBuilder {
+        DefaultCredentialProviderBuilder::default()
+    }
 
-        Self { chain }
+    /// Create a new `DefaultCredentialProvider` instance using the default 
chain.
+    pub fn new() -> Self {
+        Self::builder().build()
     }
 
     /// Create with a custom credential chain.
@@ -100,6 +85,251 @@ impl DefaultCredentialProvider {
     }
 }
 
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_*` to customize provider behavior and `disable_*(bool)` to
+/// include or exclude providers from the default chain. Call `build()` to
+/// construct the provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+    env: Option<EnvCredentialProvider>,
+    profile: Option<ProfileCredentialProvider>,
+    #[cfg(not(target_arch = "wasm32"))]
+    sso: Option<SSOCredentialProvider>,
+    assume_role: Option<AssumeRoleWithWebIdentityCredentialProvider>,
+    #[cfg(not(target_arch = "wasm32"))]
+    process: Option<ProcessCredentialProvider>,
+    ecs: Option<ECSCredentialProvider>,
+    imds: Option<IMDSv2CredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+    /// Create a new builder with default state.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Configure the environment credential provider.
+    pub fn configure_env<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+    {
+        let p = self.env.take().unwrap_or_default();
+        self.env = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the environment provider.
+    pub fn disable_env(mut self, disable: bool) -> Self {
+        if disable {
+            self.env = None;
+        } else if self.env.is_none() {
+            self.env = Some(EnvCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the profile credential provider.
+    pub fn configure_profile<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(ProfileCredentialProvider) -> ProfileCredentialProvider,
+    {
+        let p = self.profile.take().unwrap_or_default();
+        self.profile = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the profile provider.
+    pub fn disable_profile(mut self, disable: bool) -> Self {
+        if disable {
+            self.profile = None;
+        } else if self.profile.is_none() {
+            self.profile = Some(ProfileCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the SSO credential provider.
+    ///
+    /// This customizes how AWS SSO (IAM Identity Center) credentials are
+    /// discovered and exchanged from local SSO caches. Typical use cases
+    /// include setting an alternative endpoint for testing, or overriding the
+    /// profile-derived values. This method is only available for non-wasm32
+    /// targets where process and filesystem access is supported.
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn configure_sso<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(SSOCredentialProvider) -> SSOCredentialProvider,
+    {
+        let p = self.sso.take().unwrap_or_default();
+        self.sso = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the SSO provider.
+    ///
+    /// Use this to explicitly remove SSO from the default credential
+    /// resolution chain (disable = true) or ensure it participates (disable = 
false).
+    /// This is useful in controlled environments (e.g., CI) or when SSO is
+    /// not configured. Only available on non-wasm32 targets.
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn disable_sso(mut self, disable: bool) -> Self {
+        if disable {
+            self.sso = None;
+        } else if self.sso.is_none() {
+            self.sso = Some(SSOCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the web-identity assume-role credential provider.
+    pub fn configure_assume_role<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(
+            AssumeRoleWithWebIdentityCredentialProvider,
+        ) -> AssumeRoleWithWebIdentityCredentialProvider,
+    {
+        let p = self.assume_role.take().unwrap_or_default();
+        self.assume_role = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the assume role provider.
+    pub fn disable_assume_role(mut self, disable: bool) -> Self {
+        if disable {
+            self.assume_role = None;
+        } else if self.assume_role.is_none() {
+            self.assume_role = 
Some(AssumeRoleWithWebIdentityCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the external process credential provider.
+    ///
+    /// This allows overriding the profile-derived command used to obtain
+    /// credentials via `credential_process`. Only available on non-wasm32
+    /// targets.
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn configure_process<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(ProcessCredentialProvider) -> ProcessCredentialProvider,
+    {
+        let p = self.process.take().unwrap_or_default();
+        self.process = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the process provider.
+    ///
+    /// Use this to explicitly remove the external process credential source
+    /// (disable = true) or ensure it participates (disable = false). This is
+    /// only meaningful on non-wasm32 targets.
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn disable_process(mut self, disable: bool) -> Self {
+        if disable {
+            self.process = None;
+        } else if self.process.is_none() {
+            self.process = Some(ProcessCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the ECS (container/task) credential provider.
+    pub fn configure_ecs<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(ECSCredentialProvider) -> ECSCredentialProvider,
+    {
+        let p = self.ecs.take().unwrap_or_default();
+        self.ecs = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the ECS provider.
+    pub fn disable_ecs(mut self, disable: bool) -> Self {
+        if disable {
+            self.ecs = None;
+        } else if self.ecs.is_none() {
+            self.ecs = Some(ECSCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the EC2 IMDSv2 credential provider.
+    pub fn configure_imds<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(IMDSv2CredentialProvider) -> IMDSv2CredentialProvider,
+    {
+        let p = self.imds.take().unwrap_or_default();
+        self.imds = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the IMDSv2 provider.
+    pub fn disable_imds(mut self, disable: bool) -> Self {
+        if disable {
+            self.imds = None;
+        } else if self.imds.is_none() {
+            self.imds = Some(IMDSv2CredentialProvider::new());
+        }
+        self
+    }
+
+    /// Build the `DefaultCredentialProvider` with the configured options.
+    pub fn build(self) -> DefaultCredentialProvider {
+        let mut chain = ProvideCredentialChain::new();
+
+        if let Some(p) = self.env {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(EnvCredentialProvider::new());
+        }
+
+        if let Some(p) = self.profile {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(ProfileCredentialProvider::new());
+        }
+
+        #[cfg(not(target_arch = "wasm32"))]
+        {
+            if let Some(p) = self.sso {
+                chain = chain.push(p);
+            } else {
+                chain = chain.push(SSOCredentialProvider::new());
+            }
+        }
+
+        if let Some(p) = self.assume_role {
+            chain = chain.push(p);
+        } else {
+            chain = 
chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
+        }
+
+        #[cfg(not(target_arch = "wasm32"))]
+        {
+            if let Some(p) = self.process {
+                chain = chain.push(p);
+            } else {
+                chain = chain.push(ProcessCredentialProvider::new());
+            }
+        }
+
+        if let Some(p) = self.ecs {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(ECSCredentialProvider::new());
+        }
+
+        if let Some(p) = self.imds {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(IMDSv2CredentialProvider::new());
+        }
+
+        DefaultCredentialProvider::with_chain(chain)
+    }
+}
+
 #[async_trait]
 impl ProvideCredential for DefaultCredentialProvider {
     type Credential = Credential;
@@ -280,6 +510,102 @@ mod tests {
         assert_eq!("static_secret_key", cred.secret_access_key);
     }
 
+    #[tokio::test]
+    async fn test_default_credential_provider_configure_imds() {
+        let _ = env_logger::builder().is_test(true).try_init();
+
+        let ctx = Context::new()
+            .with_file_read(TokioFileRead)
+            .with_http_send(ReqwestHttpSend::default())
+            .with_env(OsEnv);
+        let ctx = ctx.with_env(StaticEnv {
+            home_dir: None,
+            envs: HashMap::new(),
+        });
+
+        // Build a custom chain with IMDS disabled
+        let mut chain = ProvideCredentialChain::new()
+            .push(EnvCredentialProvider::new())
+            .push(ProfileCredentialProvider::new());
+
+        #[cfg(not(target_arch = "wasm32"))]
+        {
+            chain = chain.push(SSOCredentialProvider::new());
+        }
+
+        chain = chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
+
+        #[cfg(not(target_arch = "wasm32"))]
+        {
+            chain = chain.push(ProcessCredentialProvider::new());
+        }
+
+        chain = chain.push(ECSCredentialProvider::new());
+
+        let provider = DefaultCredentialProvider::with_chain(chain);
+
+        // Even though IMDS is the last provider, it should return None when 
disabled
+        let cred = provider
+            .provide_credential(&ctx)
+            .await
+            .expect("load must succeed");
+        assert!(cred.is_none());
+    }
+
+    #[tokio::test]
+    async fn test_default_credential_provider_configure_profile() {
+        let _ = env_logger::builder().is_test(true).try_init();
+
+        let ctx = Context::new()
+            .with_file_read(TokioFileRead)
+            .with_http_send(ReqwestHttpSend::default())
+            .with_env(OsEnv);
+        let ctx = ctx.with_env(StaticEnv {
+            home_dir: None,
+            envs: HashMap::new(),
+        });
+
+        // Build a custom chain with Profile provider using a custom config 
file
+        let custom_config = format!(
+            "{}/testdata/default_config",
+            env::current_dir()
+                .expect("current_dir must exist")
+                .to_string_lossy()
+        );
+
+        let mut chain = 
ProvideCredentialChain::new().push(EnvCredentialProvider::new());
+
+        chain = 
chain.push(ProfileCredentialProvider::new().with_config_file(custom_config));
+
+        #[cfg(not(target_arch = "wasm32"))]
+        {
+            chain = chain.push(SSOCredentialProvider::new());
+        }
+
+        chain = chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
+
+        #[cfg(not(target_arch = "wasm32"))]
+        {
+            chain = chain.push(ProcessCredentialProvider::new());
+        }
+
+        chain = chain
+            .push(ECSCredentialProvider::new())
+            .push(IMDSv2CredentialProvider::new());
+
+        let provider = DefaultCredentialProvider::with_chain(chain);
+
+        // Should load from the custom config
+        let cred = provider
+            .provide_credential(&ctx)
+            .await
+            .expect("load must succeed");
+        // The testdata/default_config has credentials
+        let cred = cred.expect("credential should exist");
+        assert_eq!("config_access_key_id", cred.access_key_id);
+        assert_eq!("config_secret_access_key", cred.secret_access_key);
+    }
+
     /// AWS_SHARED_CREDENTIALS_FILE should be taken first.
     #[tokio::test]
     async fn test_credential_profile_loader_from_both() {
diff --git a/services/aws-v4/src/provide_credential/env.rs 
b/services/aws-v4/src/provide_credential/env.rs
index 631ba7f..0256b3c 100644
--- a/services/aws-v4/src/provide_credential/env.rs
+++ b/services/aws-v4/src/provide_credential/env.rs
@@ -25,7 +25,7 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// - `AWS_ACCESS_KEY_ID`: The AWS access key ID
 /// - `AWS_SECRET_ACCESS_KEY`: The AWS secret access key
 /// - `AWS_SESSION_TOKEN`: The AWS session token (optional)
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
 pub struct EnvCredentialProvider;
 
 impl EnvCredentialProvider {
diff --git a/services/aws-v4/src/provide_credential/imds.rs 
b/services/aws-v4/src/provide_credential/imds.rs
index 5de8409..656e12e 100644
--- a/services/aws-v4/src/provide_credential/imds.rs
+++ b/services/aws-v4/src/provide_credential/imds.rs
@@ -28,14 +28,14 @@ use std::sync::{Arc, Mutex};
 
 #[derive(Debug, Clone)]
 pub struct IMDSv2CredentialProvider {
-    disabled: Option<bool>,
+    endpoint: Option<String>,
     token: Arc<Mutex<(String, DateTime)>>,
 }
 
 impl Default for IMDSv2CredentialProvider {
     fn default() -> Self {
         Self {
-            disabled: None,
+            endpoint: None,
             token: Arc::new(Mutex::new((String::new(), DateTime::default()))),
         }
     }
@@ -47,19 +47,22 @@ impl IMDSv2CredentialProvider {
         Self::default()
     }
 
-    /// Disable the provider.
-    pub fn disabled(mut self) -> Self {
-        self.disabled = Some(true);
+    /// Set the endpoint for the metadata service.
+    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
+        self.endpoint = Some(endpoint.into());
         self
     }
 }
 
 impl IMDSv2CredentialProvider {
     fn get_endpoint(&self, ctx: &Context) -> String {
-        ctx.env_vars()
-            .get("AWS_EC2_METADATA_SERVICE_ENDPOINT")
-            .cloned()
-            .unwrap_or_else(|| "http://169.254.169.254".into())
+        // First check configured endpoint, then environment, then default
+        self.endpoint.clone().unwrap_or_else(|| {
+            ctx.env_vars()
+                .get("AWS_EC2_METADATA_SERVICE_ENDPOINT")
+                .cloned()
+                .unwrap_or_else(|| "http://169.254.169.254".into())
+        })
     }
 
     async fn load_ec2_metadata_token(&self, ctx: &Context) -> Result<String> {
@@ -118,15 +121,14 @@ impl ProvideCredential for IMDSv2CredentialProvider {
     type Credential = Credential;
 
     async fn provide_credential(&self, ctx: &Context) -> 
Result<Option<Self::Credential>> {
-        // Check if disabled, first from config, then from environment
-        let disabled = self.disabled.unwrap_or_else(|| {
-            ctx.env_vars()
-                .get("AWS_EC2_METADATA_DISABLED")
-                .map(|v| v == "true")
-                .unwrap_or(false)
-        });
+        // Check if disabled via environment
+        let disabled_env = ctx
+            .env_vars()
+            .get("AWS_EC2_METADATA_DISABLED")
+            .map(|v| v == "true")
+            .unwrap_or(false);
 
-        if disabled {
+        if disabled_env {
             return Ok(None);
         }
 
diff --git a/services/aws-v4/src/provide_credential/mod.rs 
b/services/aws-v4/src/provide_credential/mod.rs
index 4c4a83d..90bd07a 100644
--- a/services/aws-v4/src/provide_credential/mod.rs
+++ b/services/aws-v4/src/provide_credential/mod.rs
@@ -25,7 +25,7 @@ mod cognito;
 pub use cognito::CognitoIdentityCredentialProvider;
 
 mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
 
 mod ecs;
 pub use ecs::ECSCredentialProvider;
diff --git a/services/aws-v4/src/provide_credential/profile.rs 
b/services/aws-v4/src/provide_credential/profile.rs
index 85005b4..7d9d6ae 100644
--- a/services/aws-v4/src/provide_credential/profile.rs
+++ b/services/aws-v4/src/provide_credential/profile.rs
@@ -37,7 +37,7 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// 1. The `AWS_PROFILE` environment variable
 /// 2. The profile specified via `with_profile()`
 /// 3. Default to "default"
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ProfileCredentialProvider {
     profile: String,
     config_file: Option<String>,
diff --git a/services/azure-storage/src/provide_credential/azure_cli.rs 
b/services/azure-storage/src/provide_credential/azure_cli.rs
index 850bc50..ce6dc82 100644
--- a/services/azure-storage/src/provide_credential/azure_cli.rs
+++ b/services/azure-storage/src/provide_credential/azure_cli.rs
@@ -30,7 +30,7 @@ pub struct AzureCliCredentialProvider {}
 
 impl AzureCliCredentialProvider {
     pub fn new() -> Self {
-        Self {}
+        Self::default()
     }
 
     /// Execute `az account get-access-token` command
diff --git a/services/azure-storage/src/provide_credential/client_secret.rs 
b/services/azure-storage/src/provide_credential/client_secret.rs
index 4c79695..7294f7c 100644
--- a/services/azure-storage/src/provide_credential/client_secret.rs
+++ b/services/azure-storage/src/provide_credential/client_secret.rs
@@ -26,13 +26,28 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// a client ID and client secret.
 ///
 /// Reference: 
<https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow>
-#[derive(Debug, Default)]
-pub struct ClientSecretCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct ClientSecretCredentialProvider {
+    tenant_id: Option<String>,
+    client_id: Option<String>,
+}
 
 impl ClientSecretCredentialProvider {
     /// Create a new client secret loader.
     pub fn new() -> Self {
-        Self
+        Self::default()
+    }
+
+    /// Set the tenant ID.
+    pub fn with_tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
+        self.tenant_id = Some(tenant_id.into());
+        self
+    }
+
+    /// Set the client ID.
+    pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
+        self.client_id = Some(client_id.into());
+        self
     }
 }
 
@@ -43,8 +58,12 @@ impl ProvideCredential for ClientSecretCredentialProvider {
     async fn provide_credential(&self, ctx: &Context) -> 
Result<Option<Self::Credential>> {
         let envs = ctx.env_vars();
 
-        // Check if all required parameters are available from environment
-        let tenant_id = match envs.get("AZURE_TENANT_ID") {
+        // Check if all required parameters are available from environment or 
config
+        let tenant_id = match self
+            .tenant_id
+            .as_ref()
+            .or_else(|| envs.get("AZURE_TENANT_ID"))
+        {
             Some(id) if !id.is_empty() => id,
             _ => return Ok(None),
         };
diff --git a/services/azure-storage/src/provide_credential/default.rs 
b/services/azure-storage/src/provide_credential/default.rs
index 06e6376..02ab0a9 100644
--- a/services/azure-storage/src/provide_credential/default.rs
+++ b/services/azure-storage/src/provide_credential/default.rs
@@ -35,29 +35,30 @@ use reqsign_core::{Context, ProvideCredential, 
ProvideCredentialChain, Result};
 /// 5. Azure Pipelines (workload identity)
 /// 6. Workload identity (federated credentials)
 /// 7. IMDS (Azure VM managed identity)
-#[derive(Debug, Default)]
+#[derive(Debug)]
 pub struct DefaultCredentialProvider {
     chain: ProvideCredentialChain<Credential>,
 }
 
+impl Default for DefaultCredentialProvider {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl DefaultCredentialProvider {
+    /// Create a builder to configure the default credential chain.
+    pub fn builder() -> DefaultCredentialProviderBuilder {
+        DefaultCredentialProviderBuilder::default()
+    }
+
     /// Create a new default loader.
     pub fn new() -> Self {
-        let mut chain = 
ProvideCredentialChain::new().push(EnvCredentialProvider::new());
-
-        #[cfg(not(target_arch = "wasm32"))]
-        {
-            chain = chain
-                .push(AzureCliCredentialProvider::new())
-                .push(ClientCertificateCredentialProvider::new());
-        }
-
-        chain = chain
-            .push(ClientSecretCredentialProvider::new())
-            .push(AzurePipelinesCredentialProvider::new())
-            .push(WorkloadIdentityCredentialProvider::new())
-            .push(ImdsCredentialProvider::new());
+        Self::builder().build()
+    }
 
+    /// Create with a custom credential chain.
+    pub fn with_chain(chain: ProvideCredentialChain<Credential>) -> Self {
         Self { chain }
     }
 
@@ -83,6 +84,250 @@ impl DefaultCredentialProvider {
     }
 }
 
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_*` to adjust provider behavior and `disable_*(bool)` to
+/// control participation in the chain. Then call `build()` to construct the
+/// final provider that resolves credentials in the documented order.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+    env: Option<EnvCredentialProvider>,
+    #[cfg(not(target_arch = "wasm32"))]
+    azure_cli: Option<AzureCliCredentialProvider>,
+    #[cfg(not(target_arch = "wasm32"))]
+    client_certificate: Option<ClientCertificateCredentialProvider>,
+    client_secret: Option<ClientSecretCredentialProvider>,
+    azure_pipelines: Option<AzurePipelinesCredentialProvider>,
+    workload_identity: Option<WorkloadIdentityCredentialProvider>,
+    imds: Option<ImdsCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn configure_env<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+    {
+        let p = self.env.take().unwrap_or_default();
+        self.env = Some(f(p));
+        self
+    }
+
+    pub fn disable_env(mut self, disable: bool) -> Self {
+        if disable {
+            self.env = None;
+        } else if self.env.is_none() {
+            self.env = Some(EnvCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the Azure CLI credential provider.
+    ///
+    /// This is typically used for local development where Azure CLI is
+    /// available and authenticated. You can tweak behavior (like alternative
+    /// token acquisition strategies) via the provided closure. Only available
+    /// on non-wasm32 targets.
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn configure_azure_cli<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(AzureCliCredentialProvider) -> AzureCliCredentialProvider,
+    {
+        let p = self.azure_cli.take().unwrap_or_default();
+        self.azure_cli = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the Azure CLI provider.
+    ///
+    /// Useful in CI or constrained environments where Azure CLI is not
+    /// present, or to explicitly opt out. Only available on non-wasm32 
targets.
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn disable_azure_cli(mut self, disable: bool) -> Self {
+        if disable {
+            self.azure_cli = None;
+        } else if self.azure_cli.is_none() {
+            self.azure_cli = Some(AzureCliCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the client certificate credential provider.
+    ///
+    /// Customize certificate-based SPN authentication (tenant/client/cert
+    /// parameters). Only available on non-wasm32 targets.
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn configure_client_certificate<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(ClientCertificateCredentialProvider) -> 
ClientCertificateCredentialProvider,
+    {
+        let p = self.client_certificate.take().unwrap_or_default();
+        self.client_certificate = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the client certificate 
provider.
+    ///
+    /// Use to explicitly remove certificate-based auth from the chain or to
+    /// ensure participation. Only available on non-wasm32 targets.
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn disable_client_certificate(mut self, disable: bool) -> Self {
+        if disable {
+            self.client_certificate = None;
+        } else if self.client_certificate.is_none() {
+            self.client_certificate = 
Some(ClientCertificateCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the client secret credential provider.
+    ///
+    /// Customize tenant/client parameters used to exchange a client secret
+    /// for an access token suitable for Azure Storage.
+    pub fn configure_client_secret<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(ClientSecretCredentialProvider) -> 
ClientSecretCredentialProvider,
+    {
+        let p = self.client_secret.take().unwrap_or_default();
+        self.client_secret = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the client secret provider.
+    pub fn disable_client_secret(mut self, disable: bool) -> Self {
+        if disable {
+            self.client_secret = None;
+        } else if self.client_secret.is_none() {
+            self.client_secret = Some(ClientSecretCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the Azure Pipelines workload identity provider.
+    ///
+    /// Allows customizing how OIDC tokens from Azure Pipelines are exchanged
+    /// for Azure AD access tokens.
+    pub fn configure_azure_pipelines<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(AzurePipelinesCredentialProvider) -> 
AzurePipelinesCredentialProvider,
+    {
+        let p = self.azure_pipelines.take().unwrap_or_default();
+        self.azure_pipelines = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the Azure Pipelines provider.
+    pub fn disable_azure_pipelines(mut self, disable: bool) -> Self {
+        if disable {
+            self.azure_pipelines = None;
+        } else if self.azure_pipelines.is_none() {
+            self.azure_pipelines = 
Some(AzurePipelinesCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the Kubernetes workload identity provider.
+    ///
+    /// Allows customizing tenant or other parameters used to exchange
+    /// federated tokens for Azure AD access tokens.
+    pub fn configure_workload_identity<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(WorkloadIdentityCredentialProvider) -> 
WorkloadIdentityCredentialProvider,
+    {
+        let p = self.workload_identity.take().unwrap_or_default();
+        self.workload_identity = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the workload identity 
provider.
+    pub fn disable_workload_identity(mut self, disable: bool) -> Self {
+        if disable {
+            self.workload_identity = None;
+        } else if self.workload_identity.is_none() {
+            self.workload_identity = 
Some(WorkloadIdentityCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the Azure IMDS provider.
+    ///
+    /// Allows setting an alternate metadata endpoint or other parameters used
+    /// to obtain managed identity tokens on Azure VMs.
+    pub fn configure_imds<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(ImdsCredentialProvider) -> ImdsCredentialProvider,
+    {
+        let p = self.imds.take().unwrap_or_default();
+        self.imds = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the IMDS provider.
+    pub fn disable_imds(mut self, disable: bool) -> Self {
+        if disable {
+            self.imds = None;
+        } else if self.imds.is_none() {
+            self.imds = Some(ImdsCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Build the `DefaultCredentialProvider` with the configured options.
+    pub fn build(self) -> DefaultCredentialProvider {
+        let mut chain = ProvideCredentialChain::new();
+
+        if let Some(p) = self.env {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(EnvCredentialProvider::new());
+        }
+
+        #[cfg(not(target_arch = "wasm32"))]
+        {
+            if let Some(p) = self.azure_cli {
+                chain = chain.push(p);
+            } else {
+                chain = chain.push(AzureCliCredentialProvider::new());
+            }
+
+            if let Some(p) = self.client_certificate {
+                chain = chain.push(p);
+            } else {
+                chain = chain.push(ClientCertificateCredentialProvider::new());
+            }
+        }
+
+        if let Some(p) = self.client_secret {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(ClientSecretCredentialProvider::new());
+        }
+
+        if let Some(p) = self.azure_pipelines {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(AzurePipelinesCredentialProvider::new());
+        }
+
+        if let Some(p) = self.workload_identity {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(WorkloadIdentityCredentialProvider::new());
+        }
+
+        if let Some(p) = self.imds {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(ImdsCredentialProvider::new());
+        }
+
+        DefaultCredentialProvider::with_chain(chain)
+    }
+}
+
 #[async_trait]
 impl ProvideCredential for DefaultCredentialProvider {
     type Credential = Credential;
diff --git a/services/azure-storage/src/provide_credential/imds.rs 
b/services/azure-storage/src/provide_credential/imds.rs
index ae79ab4..a09a41f 100644
--- a/services/azure-storage/src/provide_credential/imds.rs
+++ b/services/azure-storage/src/provide_credential/imds.rs
@@ -25,13 +25,21 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// which is available on Azure VMs and other Azure compute resources.
 ///
 /// Reference: 
<https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal,http#using-the-rest-protocol>
-#[derive(Debug, Default)]
-pub struct ImdsCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct ImdsCredentialProvider {
+    endpoint: Option<String>,
+}
 
 impl ImdsCredentialProvider {
     /// Create a new IMDS loader.
     pub fn new() -> Self {
-        Self
+        Self::default()
+    }
+
+    /// Set the IMDS endpoint.
+    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
+        self.endpoint = Some(endpoint.into());
+        self
     }
 }
 
diff --git a/services/azure-storage/src/provide_credential/mod.rs 
b/services/azure-storage/src/provide_credential/mod.rs
index fe046b4..90acf1f 100644
--- a/services/azure-storage/src/provide_credential/mod.rs
+++ b/services/azure-storage/src/provide_credential/mod.rs
@@ -22,7 +22,7 @@ mod static_provider;
 pub use static_provider::StaticCredentialProvider;
 
 mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
 
 mod imds;
 pub use imds::ImdsCredentialProvider;
diff --git a/services/azure-storage/src/provide_credential/workload_identity.rs 
b/services/azure-storage/src/provide_credential/workload_identity.rs
index 42a0983..17d0328 100644
--- a/services/azure-storage/src/provide_credential/workload_identity.rs
+++ b/services/azure-storage/src/provide_credential/workload_identity.rs
@@ -26,13 +26,21 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// using a federated token.
 ///
 /// Reference: 
<https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview>
-#[derive(Debug, Default)]
-pub struct WorkloadIdentityCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct WorkloadIdentityCredentialProvider {
+    tenant_id: Option<String>,
+}
 
 impl WorkloadIdentityCredentialProvider {
     /// Create a new workload identity loader.
     pub fn new() -> Self {
-        Self
+        Self::default()
+    }
+
+    /// Set the tenant ID.
+    pub fn with_tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
+        self.tenant_id = Some(tenant_id.into());
+        self
     }
 }
 
diff --git a/services/google/src/provide_credential/default.rs 
b/services/google/src/provide_credential/default.rs
index dac70af..7b74479 100644
--- a/services/google/src/provide_credential/default.rs
+++ b/services/google/src/provide_credential/default.rs
@@ -17,7 +17,7 @@
 
 use log::debug;
 
-use reqsign_core::{Context, ProvideCredential, Result};
+use reqsign_core::{Context, ProvideCredential, ProvideCredentialChain, Result};
 
 use crate::constants::{DEFAULT_SCOPE, GOOGLE_APPLICATION_CREDENTIALS, 
GOOGLE_SCOPE};
 use crate::credential::{Credential, CredentialFile};
@@ -29,71 +29,165 @@ use super::{
     vm_metadata::VmMetadataCredentialProvider,
 };
 
-/// DefaultCredentialProvider tries to load credentials from multiple sources 
in order.
+/// Default credential provider for Google Cloud Storage (GCS).
 ///
-/// It follows the Google Application Default Credentials (ADC) strategy:
-/// 1. GOOGLE_APPLICATION_CREDENTIALS environment variable
-/// 2. gcloud credential file 
(~/.config/gcloud/application_default_credentials.json)
-/// 3. Metadata server (for GCE/Cloud Functions/App Engine)
-///
-/// The provider automatically handles all credential types including:
-/// - Service Account
-/// - External Account (Workload Identity)
-/// - Impersonated Service Account
-/// - Authorized User (OAuth2)
-#[derive(Debug, Clone, Default)]
+/// Resolution order follows ADC (Application Default Credentials):
+/// 1. Env var `GOOGLE_APPLICATION_CREDENTIALS`
+/// 2. Well-known location 
(`~/.config/gcloud/application_default_credentials.json`)
+/// 3. VM metadata service (GCE / Cloud Functions / App Engine)
+#[derive(Debug)]
 pub struct DefaultCredentialProvider {
-    scope: Option<String>,
+    chain: ProvideCredentialChain<Credential>,
+}
+
+impl Default for DefaultCredentialProvider {
+    fn default() -> Self {
+        Self::new()
+    }
 }
 
 impl DefaultCredentialProvider {
-    /// Create a new DefaultCredentialProvider.
+    /// Create a builder to configure the default ADC chain for GCS.
+    pub fn builder() -> DefaultCredentialProviderBuilder {
+        DefaultCredentialProviderBuilder::default()
+    }
+
+    /// Create a new DefaultCredentialProvider with the default chain:
+    /// env ADC -> well-known ADC -> VM metadata
     pub fn new() -> Self {
-        Self { scope: None }
+        Self::builder().build()
     }
 
-    /// Set the OAuth2 scope.
-    pub fn with_scope(mut self, scope: impl Into<String>) -> Self {
-        self.scope = Some(scope.into());
-        self
+    /// Create with a custom credential chain.
+    pub fn with_chain(chain: ProvideCredentialChain<Credential>) -> Self {
+        Self { chain }
     }
 
     /// Add a credential provider to the front of the default chain.
-    ///
-    /// Note: Google's DefaultCredentialProvider doesn't use 
ProvideCredentialChain internally,
-    /// but this method is provided for API consistency with other providers.
-    /// The custom provider will be tried first before the default ADC flow.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// use reqsign_google::{DefaultCredentialProvider, 
StaticCredentialProvider};
-    ///
-    /// let provider = DefaultCredentialProvider::new()
-    ///     .push_front(StaticCredentialProvider::new("service_account_json"));
-    /// ```
     pub fn push_front(
-        self,
-        _provider: impl ProvideCredential<Credential = Credential> + 'static,
+        mut self,
+        provider: impl ProvideCredential<Credential = Credential> + 'static,
     ) -> Self {
-        // Note: This implementation would need refactoring to support 
chain-based approach
-        // For now, we keep the method for API consistency
-        log::warn!("push_front is not yet implemented for Google 
DefaultCredentialProvider");
+        self.chain = self.chain.push_front(provider);
         self
     }
 
-    /// Try to load credentials from GOOGLE_APPLICATION_CREDENTIALS 
environment variable.
-    async fn try_env_credentials(&self, ctx: &Context) -> 
Result<Option<Credential>> {
-        let Some(path) = ctx.env_var(GOOGLE_APPLICATION_CREDENTIALS) else {
+    /// Set the OAuth2 scope for ADC providers (deprecated).
+    ///
+    /// This helper configures the scope used by the environment and
+    /// well-known ADC providers, as well as the VM metadata provider, by
+    /// constructing a new chain with the provided scope. Prefer configuring
+    /// scope via specific providers (e.g., 
`VmMetadataCredentialProvider::with_scope`)
+    /// or using the `GOOGLE_SCOPE` environment variable.
+    #[deprecated(
+        since = "1.0.0",
+        note = "Configure scope via specific providers or GOOGLE_SCOPE env var"
+    )]
+    pub fn with_scope(self, scope: impl Into<String>) -> Self {
+        let s = scope.into();
+        let chain = ProvideCredentialChain::new()
+            .push(EnvAdcCredentialProvider::new().with_scope(s.clone()))
+            .push(WellKnownAdcCredentialProvider::new().with_scope(s.clone()))
+            .push(VmMetadataCredentialProvider::new().with_scope(s));
+        Self { chain }
+    }
+
+    #[deprecated(
+        since = "1.0.0",
+        note = "Use 
DefaultCredentialProvider::builder().disable_env(skip).build() instead"
+    )]
+    pub fn skip_env_credentials(self, skip: bool) -> Self {
+        DefaultCredentialProvider::builder()
+            .disable_env(skip)
+            .build()
+    }
+
+    #[deprecated(
+        since = "1.0.0",
+        note = "Use 
DefaultCredentialProvider::builder().disable_well_known(skip).build() instead"
+    )]
+    pub fn skip_well_known_location(self, skip: bool) -> Self {
+        DefaultCredentialProvider::builder()
+            .disable_well_known(skip)
+            .build()
+    }
+}
+
+#[async_trait::async_trait]
+impl ProvideCredential for DefaultCredentialProvider {
+    type Credential = Credential;
+
+    async fn provide_credential(&self, ctx: &Context) -> 
Result<Option<Self::Credential>> {
+        self.chain.provide_credential(ctx).await
+    }
+}
+
+#[derive(Default, Clone, Debug)]
+struct EnvAdcCredentialProvider {
+    disabled: Option<bool>,
+    scope: Option<String>,
+}
+
+impl EnvAdcCredentialProvider {
+    fn new() -> Self {
+        Self::default()
+    }
+
+    /// Set the OAuth2 scope to request when exchanging ADC credentials.
+    fn with_scope(mut self, scope: impl Into<String>) -> Self {
+        self.scope = Some(scope.into());
+        self
+    }
+}
+
+#[async_trait::async_trait]
+impl ProvideCredential for EnvAdcCredentialProvider {
+    type Credential = Credential;
+
+    async fn provide_credential(&self, ctx: &Context) -> 
Result<Option<Self::Credential>> {
+        if self.disabled.unwrap_or(false) {
             return Ok(None);
+        }
+
+        let path = match ctx.env_var(GOOGLE_APPLICATION_CREDENTIALS) {
+            Some(path) if !path.is_empty() => path,
+            _ => return Ok(None),
         };
 
         debug!("trying to load credential from env 
GOOGLE_APPLICATION_CREDENTIALS: {path}");
-        self.load_credential_from_path(ctx, &path).await
+
+        let content = ctx.file_read(&path).await?;
+        parse_credential_bytes(ctx, &content, self.scope.clone()).await
     }
+}
+
+#[derive(Default, Clone, Debug)]
+struct WellKnownAdcCredentialProvider {
+    disabled: Option<bool>,
+    scope: Option<String>,
+}
+
+impl WellKnownAdcCredentialProvider {
+    fn new() -> Self {
+        Self::default()
+    }
+
+    /// Set the OAuth2 scope to request when exchanging ADC credentials.
+    fn with_scope(mut self, scope: impl Into<String>) -> Self {
+        self.scope = Some(scope.into());
+        self
+    }
+}
+
+#[async_trait::async_trait]
+impl ProvideCredential for WellKnownAdcCredentialProvider {
+    type Credential = Credential;
+
+    async fn provide_credential(&self, ctx: &Context) -> 
Result<Option<Self::Credential>> {
+        if self.disabled.unwrap_or(false) {
+            return Ok(None);
+        }
 
-    /// Try to load credentials from gcloud default location.
-    async fn try_well_known_location(&self, ctx: &Context) -> 
Result<Option<Credential>> {
         let config_dir = if let Some(v) = ctx.env_var("APPDATA") {
             v
         } else if let Some(v) = ctx.env_var("XDG_CONFIG_HOME") {
@@ -107,94 +201,139 @@ impl DefaultCredentialProvider {
         let path = 
format!("{config_dir}/gcloud/application_default_credentials.json");
         debug!("trying to load credential from well-known location: {path}");
 
-        match self.load_credential_from_path(ctx, &path).await {
-            Ok(cred) => Ok(cred),
-            Err(_) => Ok(None), // Ignore errors for well-known location
+        let content = match ctx.file_read(&path).await {
+            Ok(v) => v,
+            Err(_) => return Ok(None),
+        };
+
+        match parse_credential_bytes(ctx, &content, self.scope.clone()).await {
+            Ok(v) => Ok(v),
+            Err(_) => Ok(None),
         }
     }
+}
 
-    /// Try to load credentials from metadata server.
-    async fn try_metadata_server(&self, ctx: &Context) -> 
Result<Option<Credential>> {
-        debug!("trying to load credential from metadata server");
+async fn parse_credential_bytes(
+    ctx: &Context,
+    content: &[u8],
+    scope_override: Option<String>,
+) -> Result<Option<Credential>> {
+    let cred_file = CredentialFile::from_slice(content)?;
+
+    let scope = scope_override
+        .or_else(|| ctx.env_var(GOOGLE_SCOPE))
+        .unwrap_or_else(|| DEFAULT_SCOPE.to_string());
+
+    match cred_file {
+        CredentialFile::ServiceAccount(sa) => {
+            debug!("loaded service account credential");
+            Ok(Some(Credential::with_service_account(sa)))
+        }
+        CredentialFile::ExternalAccount(ea) => {
+            debug!("loaded external account credential, exchanging for token");
+            let provider = 
ExternalAccountCredentialProvider::new(ea).with_scope(&scope);
+            provider.provide_credential(ctx).await
+        }
+        CredentialFile::ImpersonatedServiceAccount(isa) => {
+            debug!("loaded impersonated service account credential, exchanging 
for token");
+            let provider =
+                
ImpersonatedServiceAccountCredentialProvider::new(isa).with_scope(&scope);
+            provider.provide_credential(ctx).await
+        }
+        CredentialFile::AuthorizedUser(au) => {
+            debug!("loaded authorized user credential, exchanging for token");
+            let provider = AuthorizedUserCredentialProvider::new(au);
+            provider.provide_credential(ctx).await
+        }
+    }
+}
 
-        let provider = match &self.scope {
-            Some(scope) => 
VmMetadataCredentialProvider::new().with_scope(scope),
-            None => VmMetadataCredentialProvider::new(),
-        };
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_vm_metadata` to customize VM metadata behavior and
+/// `disable_env` / `disable_well_known` / `disable_vm_metadata` to control
+/// participation. Call `build()` to construct the provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+    env_adc: Option<EnvAdcCredentialProvider>,
+    well_known_adc: Option<WellKnownAdcCredentialProvider>,
+    vm_metadata: Option<VmMetadataCredentialProvider>,
+}
 
-        provider.provide_credential(ctx).await
-    }
-
-    /// Load credential from a file path and handle all credential types.
-    async fn load_credential_from_path(
-        &self,
-        ctx: &Context,
-        path: &str,
-    ) -> Result<Option<Credential>> {
-        let content = ctx.file_read(path).await.map_err(|err| {
-            debug!("failed to read credential file {path}: {err:?}");
-            err
-        })?;
-
-        let cred_file = CredentialFile::from_slice(&content).map_err(|err| {
-            debug!("failed to parse credential file {path}: {err:?}");
-            err
-        })?;
-
-        // Get scope from instance, environment, or use default
-        let scope = self
-            .scope
-            .clone()
-            .or_else(|| ctx.env_var(GOOGLE_SCOPE))
-            .unwrap_or_else(|| DEFAULT_SCOPE.to_string());
-
-        match cred_file {
-            CredentialFile::ServiceAccount(sa) => {
-                debug!("loaded service account credential");
-                Ok(Some(Credential::with_service_account(sa)))
-            }
-            CredentialFile::ExternalAccount(ea) => {
-                debug!("loaded external account credential, exchanging for 
token");
-                let provider = 
ExternalAccountCredentialProvider::new(ea).with_scope(&scope);
-                provider.provide_credential(ctx).await
-            }
-            CredentialFile::ImpersonatedServiceAccount(isa) => {
-                debug!("loaded impersonated service account credential, 
exchanging for token");
-                let provider =
-                    
ImpersonatedServiceAccountCredentialProvider::new(isa).with_scope(&scope);
-                provider.provide_credential(ctx).await
-            }
-            CredentialFile::AuthorizedUser(au) => {
-                debug!("loaded authorized user credential, exchanging for 
token");
-                let provider = AuthorizedUserCredentialProvider::new(au);
-                provider.provide_credential(ctx).await
-            }
+impl DefaultCredentialProviderBuilder {
+    /// Create a new builder with default state.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    // No global scope configurator; configure scope on specific providers if 
needed.
+
+    /// Configure the VM metadata provider.
+    ///
+    /// This allows setting a custom endpoint or other options for retrieving
+    /// tokens when running on Google Compute Engine or compatible 
environments.
+    pub fn configure_vm_metadata<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(VmMetadataCredentialProvider) -> 
VmMetadataCredentialProvider,
+    {
+        let p = self.vm_metadata.take().unwrap_or_default();
+        self.vm_metadata = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the env-based ADC provider.
+    pub fn disable_env(mut self, disable: bool) -> Self {
+        if disable {
+            self.env_adc = None;
+        } else if self.env_adc.is_none() {
+            self.env_adc = Some(EnvAdcCredentialProvider::new());
         }
+        self
     }
-}
 
-#[async_trait::async_trait]
-impl ProvideCredential for DefaultCredentialProvider {
-    type Credential = Credential;
+    /// Disable (true) or ensure enabled (false) the well-known ADC provider.
+    pub fn disable_well_known(mut self, disable: bool) -> Self {
+        if disable {
+            self.well_known_adc = None;
+        } else if self.well_known_adc.is_none() {
+            self.well_known_adc = Some(WellKnownAdcCredentialProvider::new());
+        }
+        self
+    }
 
-    async fn provide_credential(&self, ctx: &Context) -> 
Result<Option<Self::Credential>> {
-        // 1. Try environment variable
-        if let Some(cred) = self.try_env_credentials(ctx).await? {
-            return Ok(Some(cred));
+    /// Disable (true) or ensure enabled (false) the VM metadata provider.
+    pub fn disable_vm_metadata(mut self, disable: bool) -> Self {
+        if disable {
+            self.vm_metadata = None;
+        } else if self.vm_metadata.is_none() {
+            self.vm_metadata = Some(VmMetadataCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Build the `DefaultCredentialProvider` with the configured options.
+    pub fn build(self) -> DefaultCredentialProvider {
+        let mut chain = ProvideCredentialChain::new();
+
+        if let Some(p) = self.env_adc {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(EnvAdcCredentialProvider::new());
         }
 
-        // 2. Try well-known location
-        if let Some(cred) = self.try_well_known_location(ctx).await? {
-            return Ok(Some(cred));
+        if let Some(p) = self.well_known_adc {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(WellKnownAdcCredentialProvider::new());
         }
 
-        // 3. Try metadata server
-        if let Some(cred) = self.try_metadata_server(ctx).await? {
-            return Ok(Some(cred));
+        if let Some(p) = self.vm_metadata {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(VmMetadataCredentialProvider::new());
         }
 
-        debug!("no valid credential source found");
-        Ok(None)
+        DefaultCredentialProvider::with_chain(chain)
     }
 }
 
@@ -240,8 +379,7 @@ mod tests {
 
     #[tokio::test]
     async fn test_default_provider_with_scope() {
-        let provider = DefaultCredentialProvider::new()
-            
.with_scope("https://www.googleapis.com/auth/devstorage.read_only";);
+        let provider = DefaultCredentialProvider::builder().build();
 
         // Even without valid credentials, this should not panic
         let ctx = Context::new()
diff --git a/services/google/src/provide_credential/mod.rs 
b/services/google/src/provide_credential/mod.rs
index ecb5773..d181afe 100644
--- a/services/google/src/provide_credential/mod.rs
+++ b/services/google/src/provide_credential/mod.rs
@@ -16,7 +16,8 @@
 // under the License.
 
 mod default;
-pub use default::DefaultCredentialProvider;
+#[allow(unused_imports)]
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
 
 mod vm_metadata;
 pub use vm_metadata::VmMetadataCredentialProvider;
diff --git a/services/google/src/provide_credential/vm_metadata.rs 
b/services/google/src/provide_credential/vm_metadata.rs
index 0ddae76..c3993b7 100644
--- a/services/google/src/provide_credential/vm_metadata.rs
+++ b/services/google/src/provide_credential/vm_metadata.rs
@@ -33,12 +33,13 @@ struct VmMetadataTokenResponse {
 #[derive(Debug, Clone, Default)]
 pub struct VmMetadataCredentialProvider {
     scope: Option<String>,
+    endpoint: Option<String>,
 }
 
 impl VmMetadataCredentialProvider {
     /// Create a new VmMetadataCredentialProvider.
     pub fn new() -> Self {
-        Self { scope: None }
+        Self::default()
     }
 
     /// Set the OAuth2 scope.
@@ -46,6 +47,12 @@ impl VmMetadataCredentialProvider {
         self.scope = Some(scope.into());
         self
     }
+
+    /// Set the metadata endpoint.
+    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
+        self.endpoint = Some(endpoint.into());
+        self
+    }
 }
 
 #[async_trait::async_trait]
@@ -69,8 +76,10 @@ impl ProvideCredential for VmMetadataCredentialProvider {
         );
 
         // Allow overriding metadata host for testing
-        let metadata_host = ctx
-            .env_var("GCE_METADATA_HOST")
+        let metadata_host = self
+            .endpoint
+            .clone()
+            .or_else(|| ctx.env_var("GCE_METADATA_HOST"))
             .unwrap_or_else(|| "metadata.google.internal".to_string());
 
         let url = format!(
diff --git a/services/google/tests/credential_providers/external_account.rs 
b/services/google/tests/credential_providers/external_account.rs
index 7944151..0058f26 100644
--- a/services/google/tests/credential_providers/external_account.rs
+++ b/services/google/tests/credential_providers/external_account.rs
@@ -41,13 +41,15 @@ async fn test_external_account_credential_provider() -> 
Result<()> {
         "Credential file must be external_account type"
     );
 
-    let ctx = create_test_context_with_env(HashMap::from_iter([(
-        "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
-        cred_path,
-    )]));
+    let ctx = create_test_context_with_env(HashMap::from_iter([
+        ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+        (
+            "GOOGLE_SCOPE".to_string(),
+            
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+        ),
+    ]));
 
-    let provider = DefaultCredentialProvider::new()
-        .with_scope("https://www.googleapis.com/auth/devstorage.read_write";);
+    let provider = DefaultCredentialProvider::new();
 
     let credential = provider
         .provide_credential(&ctx)
@@ -83,13 +85,15 @@ async fn test_external_account_with_workload_identity() -> 
Result<()> {
         "Credential file must be external_account type for workload identity"
     );
 
-    let ctx = create_test_context_with_env(HashMap::from_iter([(
-        "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
-        cred_path,
-    )]));
+    let ctx = create_test_context_with_env(HashMap::from_iter([
+        ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+        (
+            "GOOGLE_SCOPE".to_string(),
+            
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+        ),
+    ]));
 
-    let provider = DefaultCredentialProvider::new()
-        .with_scope("https://www.googleapis.com/auth/devstorage.read_write";);
+    let provider = DefaultCredentialProvider::new();
 
     let credential = provider
         .provide_credential(&ctx)
diff --git 
a/services/google/tests/credential_providers/impersonated_service_account.rs 
b/services/google/tests/credential_providers/impersonated_service_account.rs
index bd2b090..dab620e 100644
--- a/services/google/tests/credential_providers/impersonated_service_account.rs
+++ b/services/google/tests/credential_providers/impersonated_service_account.rs
@@ -42,13 +42,15 @@ async fn 
test_impersonated_service_account_credential_provider() -> Result<()> {
         "Credential file must be impersonated_service_account type"
     );
 
-    let ctx = create_test_context_with_env(HashMap::from_iter([(
-        "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
-        cred_path,
-    )]));
+    let ctx = create_test_context_with_env(HashMap::from_iter([
+        ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+        (
+            "GOOGLE_SCOPE".to_string(),
+            
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+        ),
+    ]));
 
-    let provider = DefaultCredentialProvider::new()
-        .with_scope("https://www.googleapis.com/auth/devstorage.read_write";);
+    let provider = DefaultCredentialProvider::new();
 
     let credential = provider
         .provide_credential(&ctx)
@@ -88,13 +90,15 @@ async fn 
test_impersonated_service_account_with_real_credentials() -> Result<()>
         "Credential file must be impersonated_service_account type"
     );
 
-    let ctx = create_test_context_with_env(HashMap::from_iter([(
-        "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
-        cred_path,
-    )]));
+    let ctx = create_test_context_with_env(HashMap::from_iter([
+        ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+        (
+            "GOOGLE_SCOPE".to_string(),
+            
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+        ),
+    ]));
 
-    let provider = DefaultCredentialProvider::new()
-        .with_scope("https://www.googleapis.com/auth/devstorage.read_write";);
+    let provider = DefaultCredentialProvider::new();
 
     let credential = provider
         .provide_credential(&ctx)
@@ -134,13 +138,15 @@ async fn 
test_impersonated_service_account_with_delegates() -> Result<()> {
         "Credential file must contain delegation chain"
     );
 
-    let ctx = create_test_context_with_env(HashMap::from_iter([(
-        "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
-        cred_path,
-    )]));
+    let ctx = create_test_context_with_env(HashMap::from_iter([
+        ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+        (
+            "GOOGLE_SCOPE".to_string(),
+            
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+        ),
+    ]));
 
-    let provider = DefaultCredentialProvider::new()
-        .with_scope("https://www.googleapis.com/auth/devstorage.read_write";);
+    let provider = DefaultCredentialProvider::new();
 
     let credential = provider
         .provide_credential(&ctx)
diff --git a/services/huaweicloud-obs/src/provide_credential/default.rs 
b/services/huaweicloud-obs/src/provide_credential/default.rs
index 497cc53..f341041 100644
--- a/services/huaweicloud-obs/src/provide_credential/default.rs
+++ b/services/huaweicloud-obs/src/provide_credential/default.rs
@@ -39,11 +39,14 @@ impl Default for DefaultCredentialProvider {
 }
 
 impl DefaultCredentialProvider {
-    /// Create a new DefaultCredentialProvider
-    pub fn new() -> Self {
-        let chain = 
ProvideCredentialChain::new().push(EnvCredentialProvider::new());
+    /// Create a builder to configure the default credential chain.
+    pub fn builder() -> DefaultCredentialProviderBuilder {
+        DefaultCredentialProviderBuilder::default()
+    }
 
-        Self { chain }
+    /// Create a new DefaultCredentialProvider using the default chain.
+    pub fn new() -> Self {
+        Self::builder().build()
     }
 
     /// Create with a custom credential chain.
@@ -73,6 +76,53 @@ impl DefaultCredentialProvider {
     }
 }
 
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_env` to customize environment loading and
+/// `disable_env(bool)` to control participation, then `build()` to create the 
provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+    env: Option<EnvCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+    /// Create a new builder with default state.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Configure the environment credential provider.
+    pub fn configure_env<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+    {
+        let p = self.env.take().unwrap_or_default();
+        self.env = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the environment provider.
+    pub fn disable_env(mut self, disable: bool) -> Self {
+        if disable {
+            self.env = None;
+        } else if self.env.is_none() {
+            self.env = Some(EnvCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Build the `DefaultCredentialProvider` with the configured options.
+    pub fn build(self) -> DefaultCredentialProvider {
+        let mut chain = ProvideCredentialChain::new();
+        if let Some(p) = self.env {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(EnvCredentialProvider::new());
+        }
+        DefaultCredentialProvider::with_chain(chain)
+    }
+}
+
 #[async_trait]
 impl ProvideCredential for DefaultCredentialProvider {
     type Credential = Credential;
diff --git a/services/huaweicloud-obs/src/provide_credential/env.rs 
b/services/huaweicloud-obs/src/provide_credential/env.rs
index aebc9d5..38616f8 100644
--- a/services/huaweicloud-obs/src/provide_credential/env.rs
+++ b/services/huaweicloud-obs/src/provide_credential/env.rs
@@ -25,13 +25,13 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// - `HUAWEI_CLOUD_ACCESS_KEY_ID`: The Huawei Cloud access key ID
 /// - `HUAWEI_CLOUD_SECRET_ACCESS_KEY`: The Huawei Cloud secret access key
 /// - `HUAWEI_CLOUD_SECURITY_TOKEN`: The Huawei Cloud security token (optional)
-#[derive(Debug, Default)]
-pub struct EnvCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct EnvCredentialProvider {}
 
 impl EnvCredentialProvider {
     /// Create a new EnvCredentialProvider.
     pub fn new() -> Self {
-        Self
+        Self {}
     }
 }
 
diff --git a/services/huaweicloud-obs/src/provide_credential/mod.rs 
b/services/huaweicloud-obs/src/provide_credential/mod.rs
index 3a5102e..abb9bb3 100644
--- a/services/huaweicloud-obs/src/provide_credential/mod.rs
+++ b/services/huaweicloud-obs/src/provide_credential/mod.rs
@@ -24,7 +24,7 @@ mod config;
 pub use config::ConfigCredentialProvider;
 
 mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
 
 mod env;
 pub use env::EnvCredentialProvider;
diff --git a/services/oracle/src/provide_credential/config_file.rs 
b/services/oracle/src/provide_credential/config_file.rs
index 2b02a77..2ea4f2f 100644
--- a/services/oracle/src/provide_credential/config_file.rs
+++ b/services/oracle/src/provide_credential/config_file.rs
@@ -29,13 +29,13 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// The config file path and profile name can be overridden using environment 
variables:
 /// - `OCI_CONFIG_FILE`: Override the config file path
 /// - `OCI_PROFILE`: Override the profile name (default is "DEFAULT")
-#[derive(Debug, Default)]
-pub struct ConfigFileCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct ConfigFileCredentialProvider {}
 
 impl ConfigFileCredentialProvider {
     /// Create a new ConfigFileCredentialProvider.
     pub fn new() -> Self {
-        Self
+        Self {}
     }
 }
 
diff --git a/services/oracle/src/provide_credential/default.rs 
b/services/oracle/src/provide_credential/default.rs
index ae86c29..83c7c3c 100644
--- a/services/oracle/src/provide_credential/default.rs
+++ b/services/oracle/src/provide_credential/default.rs
@@ -31,12 +31,18 @@ pub struct DefaultCredentialProvider {
 }
 
 impl DefaultCredentialProvider {
-    /// Create a new DefaultCredentialProvider
+    /// Create a builder to configure the default credential chain.
+    pub fn builder() -> DefaultCredentialProviderBuilder {
+        DefaultCredentialProviderBuilder::default()
+    }
+
+    /// Create a new DefaultCredentialProvider using the default chain.
     pub fn new() -> Self {
-        let chain = ProvideCredentialChain::new()
-            .push(EnvCredentialProvider::new())
-            .push(ConfigFileCredentialProvider::new());
+        Self::builder().build()
+    }
 
+    /// Create with a custom credential chain.
+    pub fn with_chain(chain: ProvideCredentialChain<Credential>) -> Self {
         Self { chain }
     }
 
@@ -62,6 +68,80 @@ impl DefaultCredentialProvider {
     }
 }
 
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_env` / `configure_config_file` to customize providers, and
+/// `disable_env(bool)` / `disable_config_file(bool)` to control participation.
+/// Finish with `build()` to construct the provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+    env: Option<EnvCredentialProvider>,
+    config_file: Option<ConfigFileCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+    /// Create a new builder with default state.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Configure the environment credential provider.
+    pub fn configure_env<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+    {
+        let p = self.env.take().unwrap_or_default();
+        self.env = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the environment provider.
+    pub fn disable_env(mut self, disable: bool) -> Self {
+        if disable {
+            self.env = None;
+        } else if self.env.is_none() {
+            self.env = Some(EnvCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the config-file credential provider.
+    pub fn configure_config_file<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(ConfigFileCredentialProvider) -> 
ConfigFileCredentialProvider,
+    {
+        let p = self.config_file.take().unwrap_or_default();
+        self.config_file = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the config-file provider.
+    pub fn disable_config_file(mut self, disable: bool) -> Self {
+        if disable {
+            self.config_file = None;
+        } else if self.config_file.is_none() {
+            self.config_file = Some(ConfigFileCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Build the `DefaultCredentialProvider` with the configured options.
+    pub fn build(self) -> DefaultCredentialProvider {
+        let mut chain = ProvideCredentialChain::new();
+        if let Some(p) = self.env {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(EnvCredentialProvider::new());
+        }
+        if let Some(p) = self.config_file {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(ConfigFileCredentialProvider::new());
+        }
+        DefaultCredentialProvider::with_chain(chain)
+    }
+}
+
 #[async_trait]
 impl ProvideCredential for DefaultCredentialProvider {
     type Credential = Credential;
diff --git a/services/oracle/src/provide_credential/env.rs 
b/services/oracle/src/provide_credential/env.rs
index 05357b6..f92dbd8 100644
--- a/services/oracle/src/provide_credential/env.rs
+++ b/services/oracle/src/provide_credential/env.rs
@@ -26,13 +26,13 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// - `OCI_TENANCY`: The Oracle Cloud tenancy ID
 /// - `OCI_KEY_FILE`: The path to the private key file
 /// - `OCI_FINGERPRINT`: The fingerprint of the key
-#[derive(Debug, Default)]
-pub struct EnvCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct EnvCredentialProvider {}
 
 impl EnvCredentialProvider {
     /// Create a new EnvCredentialProvider.
     pub fn new() -> Self {
-        Self
+        Self {}
     }
 }
 
diff --git a/services/oracle/src/provide_credential/mod.rs 
b/services/oracle/src/provide_credential/mod.rs
index 67f98f0..fb5b63f 100644
--- a/services/oracle/src/provide_credential/mod.rs
+++ b/services/oracle/src/provide_credential/mod.rs
@@ -32,4 +32,4 @@ mod config_file;
 pub use config_file::ConfigFileCredentialProvider;
 
 mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
diff --git 
a/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs 
b/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
index 61e3ea0..c0ee0dc 100644
--- 
a/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
+++ 
b/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
@@ -26,7 +26,7 @@ use reqsign_core::{Context, ProvideCredential};
 use serde::{Deserialize, Serialize};
 
 /// Loader that loads credential via AssumeRoleWithWebIdentity.
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
 pub struct AssumeRoleWithWebIdentityCredentialProvider {}
 
 impl AssumeRoleWithWebIdentityCredentialProvider {
diff --git a/services/tencent-cos/src/provide_credential/default.rs 
b/services/tencent-cos/src/provide_credential/default.rs
index 9e1fbbe..1dcf191 100644
--- a/services/tencent-cos/src/provide_credential/default.rs
+++ b/services/tencent-cos/src/provide_credential/default.rs
@@ -15,6 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use crate::provide_credential::{
+    AssumeRoleWithWebIdentityCredentialProvider, EnvCredentialProvider,
+};
 use crate::Credential;
 use async_trait::async_trait;
 use reqsign_core::{Context, ProvideCredential, ProvideCredentialChain, Result};
@@ -36,13 +39,14 @@ impl Default for DefaultCredentialProvider {
 }
 
 impl DefaultCredentialProvider {
-    /// Create a new DefaultCredentialProvider
-    pub fn new() -> Self {
-        let chain = ProvideCredentialChain::new()
-            .push(super::EnvCredentialProvider::new())
-            .push(super::AssumeRoleWithWebIdentityCredentialProvider::new());
+    /// Create a builder to configure the default credential chain.
+    pub fn builder() -> DefaultCredentialProviderBuilder {
+        DefaultCredentialProviderBuilder::default()
+    }
 
-        Self { chain }
+    /// Create a new DefaultCredentialProvider using the default chain.
+    pub fn new() -> Self {
+        Self::builder().build()
     }
 
     /// Create with a custom credential chain.
@@ -72,6 +76,83 @@ impl DefaultCredentialProvider {
     }
 }
 
+/// Builder for `DefaultCredentialProvider`.
+///
+/// - Use `configure_*` to customize a provider.
+/// - Use `disable_*(bool)` to disable (true) or ensure enabled (false).
+/// - Call `build()` to construct the provider in the default order.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+    env: Option<EnvCredentialProvider>,
+    assume_role: Option<AssumeRoleWithWebIdentityCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+    /// Create a new builder with default state.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Configure the environment credential provider.
+    pub fn configure_env<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+    {
+        let p = self.env.take().unwrap_or_default();
+        self.env = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the environment provider.
+    pub fn disable_env(mut self, disable: bool) -> Self {
+        if disable {
+            self.env = None;
+        } else if self.env.is_none() {
+            self.env = Some(EnvCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Configure the web-identity assume-role credential provider.
+    pub fn configure_assume_role<F>(mut self, f: F) -> Self
+    where
+        F: FnOnce(
+            AssumeRoleWithWebIdentityCredentialProvider,
+        ) -> AssumeRoleWithWebIdentityCredentialProvider,
+    {
+        let p = self.assume_role.take().unwrap_or_default();
+        self.assume_role = Some(f(p));
+        self
+    }
+
+    /// Disable (true) or ensure enabled (false) the web-identity assume-role 
provider.
+    pub fn disable_assume_role(mut self, disable: bool) -> Self {
+        if disable {
+            self.assume_role = None;
+        } else if self.assume_role.is_none() {
+            self.assume_role = 
Some(AssumeRoleWithWebIdentityCredentialProvider::new());
+        }
+        self
+    }
+
+    /// Build the `DefaultCredentialProvider` with the configured options.
+    pub fn build(self) -> DefaultCredentialProvider {
+        let mut chain = ProvideCredentialChain::new();
+        if let Some(p) = self.env {
+            chain = chain.push(p);
+        } else {
+            chain = chain.push(EnvCredentialProvider::new());
+        }
+        if let Some(p) = self.assume_role {
+            chain = chain.push(p);
+        } else {
+            chain = 
chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
+        }
+
+        DefaultCredentialProvider::with_chain(chain)
+    }
+}
+
 #[async_trait]
 impl ProvideCredential for DefaultCredentialProvider {
     type Credential = Credential;
diff --git a/services/tencent-cos/src/provide_credential/env.rs 
b/services/tencent-cos/src/provide_credential/env.rs
index 3617bce..ba61f6f 100644
--- a/services/tencent-cos/src/provide_credential/env.rs
+++ b/services/tencent-cos/src/provide_credential/env.rs
@@ -25,13 +25,13 @@ use reqsign_core::{Context, ProvideCredential, Result};
 /// - `TENCENTCLOUD_SECRET_ID` or `TKE_SECRET_ID`: The Tencent Cloud secret ID
 /// - `TENCENTCLOUD_SECRET_KEY` or `TKE_SECRET_KEY`: The Tencent Cloud secret 
key
 /// - `TENCENTCLOUD_TOKEN`, `TENCENTCLOUD_SECURITY_TOKEN`, or 
`QCLOUD_SECRET_TOKEN`: The security token (optional)
-#[derive(Debug, Default)]
-pub struct EnvCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct EnvCredentialProvider {}
 
 impl EnvCredentialProvider {
     /// Create a new EnvCredentialProvider.
     pub fn new() -> Self {
-        Self
+        Self {}
     }
 }
 
diff --git a/services/tencent-cos/src/provide_credential/mod.rs 
b/services/tencent-cos/src/provide_credential/mod.rs
index ecc9142..e5171b9 100644
--- a/services/tencent-cos/src/provide_credential/mod.rs
+++ b/services/tencent-cos/src/provide_credential/mod.rs
@@ -24,7 +24,7 @@ mod config;
 pub use config::ConfigCredentialProvider;
 
 mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
 
 mod assume_role_with_web_identity;
 pub use 
assume_role_with_web_identity::AssumeRoleWithWebIdentityCredentialProvider;

Reply via email to