You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by ki...@apache.org on 2026/04/13 08:57:58 UTC

(doris) branch master updated: [feat](plugin) Support multi-root auth plugin loading and normalize OIDC access token auth (#62159)

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

kirs pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 3de6d44e433 [feat](plugin) Support multi-root auth plugin loading and normalize OIDC access token auth (#62159)
3de6d44e433 is described below

commit 3de6d44e43305e559f2e0f38ec377a210cf6fb2c
Author: Calvin Kirs <gu...@selectdb.com>
AuthorDate: Mon Apr 13 16:57:49 2026 +0800

    [feat](plugin) Support multi-root auth plugin loading and normalize OIDC access token auth (#62159)
    
    https://github.com/apache/doris/issues/60361
    
      Problem Summary:
    FE authentication plugin loading currently assumes a single plugin root
    in several runtime paths, which makes
    external plugin deployment less flexible and inconsistent across
    callers.
    
    In addition, OIDC token-based authentication uses inconsistent
    credential typing across MySQL authentication and
    authentication_chain fallback flows, and the client-visible error
    message handling is not aligned with access-token-
      specific failures.
    
    This change makes plugin root parsing reusable across FE runtime paths,
    allows configuring multiple authentication
    or authorization plugin roots with a comma-separated list, normalizes
    OIDC access token requests to use
    `OAUTH_TOKEN`, broadens OIDC request detection in fallback
    authentication flow, and aligns access-token-related
    failure messages. It also marks `authentication_chain` as mutable so the
    fallback chain can be adjusted dynamically.
---
 .../main/java/org/apache/doris/common/Config.java  |  8 ++-
 .../AuthenticationIntegrationRuntime.java          |  9 ++-
 .../apache/doris/common/util/ClassLoaderUtils.java | 80 ++++++++++++++--------
 .../mysql/authenticate/AuthenticatorManager.java   |  6 +-
 .../MysqlAuthPacketCredentialExtractor.java        |  2 +-
 .../AuthenticationIntegrationAuthenticator.java    |  8 ++-
 .../plugin/AuthenticationPluginAuthenticator.java  | 16 +++--
 .../AuthenticationIntegrationRuntimeTest.java      | 25 +++++++
 .../authenticate/AuthenticatorManagerTest.java     |  8 +--
 .../MysqlAuthPacketCredentialExtractorTest.java    |  4 +-
 .../AuthenticationPluginAuthenticatorTest.java     | 26 ++++++-
 11 files changed, 136 insertions(+), 56 deletions(-)

diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index f503b2a05fa..724d1eb044d 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -2797,7 +2797,7 @@ public class Config extends ConfigBase {
             options = {"default", "password", "ldap", "<plugin_name>"})
     public static String authentication_type = "default";
 
-    @ConfField(description = {
+    @ConfField(mutable = true, description = {
             "Specifies the authentication chain used after primary authentication failure, "
                     + "multiple integration names are comma-separated"})
     public static String authentication_chain = "";
@@ -3463,10 +3463,12 @@ public class Config extends ConfigBase {
                     + "detailed information of all replicas of the tablet will be printed."})
     public static boolean sql_block_rule_ignore_admin = false;
 
-    @ConfField(description = {"Authentication plugin directory."})
+    @ConfField(description = {"Authentication plugin root directories. Use a comma-separated list to configure "
+            + "multiple roots."})
     public static String authentication_plugins_dir = EnvUtils.getDorisHome() + "/plugins/authentication";
 
-    @ConfField(description = {"Authorization plugin directory."})
+    @ConfField(description = {"Authorization plugin root directories. Use a comma-separated list to configure "
+            + "multiple roots."})
     public static String authorization_plugins_dir = EnvUtils.getDorisHome() + "/plugins/authorization";
 
     @ConfField(description = {"Security plugin directory."})
diff --git a/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationRuntime.java b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationRuntime.java
index 9d9a63fb695..d3eb2a1747a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationRuntime.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationRuntime.java
@@ -25,6 +25,7 @@ import org.apache.doris.authentication.rolemapping.RoleMappingEvaluator;
 import org.apache.doris.authentication.spi.AuthenticationPlugin;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.common.Config;
+import org.apache.doris.common.util.ClassLoaderUtils;
 
 import com.google.common.base.Strings;
 import org.apache.logging.log4j.LogManager;
@@ -32,9 +33,6 @@ import org.apache.logging.log4j.Logger;
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -293,8 +291,9 @@ public class AuthenticationIntegrationRuntime {
         }
 
         try {
-            Path pluginRoot = Paths.get(Config.authentication_plugins_dir);
-            pluginManager.loadAll(Collections.singletonList(pluginRoot), getClass().getClassLoader());
+            pluginManager.loadAll(
+                    ClassLoaderUtils.parsePluginRootDirectories(Config.authentication_plugins_dir),
+                    getClass().getClassLoader());
         } catch (AuthenticationException e) {
             throw new AuthenticationException(
                     "Failed to load authentication plugins for type '" + pluginType + "': " + e.getMessage(),
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/ClassLoaderUtils.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/ClassLoaderUtils.java
index 97be2a5ad9e..91d5762c7fd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/ClassLoaderUtils.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/ClassLoaderUtils.java
@@ -21,6 +21,7 @@ import org.apache.doris.common.Config;
 import org.apache.doris.mysql.authenticate.AuthenticatorFactory;
 import org.apache.doris.mysql.privilege.AccessControllerFactory;
 
+import com.google.common.base.Splitter;
 import org.apache.commons.collections4.map.HashedMap;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -29,10 +30,15 @@ import java.io.File;
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * Utility class for loading service implementations from external JAR files in specific plugin directories.
@@ -61,11 +67,24 @@ import java.util.ServiceLoader;
 public class ClassLoaderUtils {
     private static final Logger LOG = LogManager.getLogger(ClassLoaderUtils.class);
     // A mapping of service class simple names to their respective plugin directories.
-    private static final Map<String, String> pluginDirMapping = new HashedMap();
+    private static final Map<String, Supplier<String>> pluginDirMapping = new HashedMap<>();
 
     static {
-        pluginDirMapping.put(AuthenticatorFactory.class.getSimpleName(), Config.authentication_plugins_dir);
-        pluginDirMapping.put(AccessControllerFactory.class.getSimpleName(), Config.authorization_plugins_dir);
+        pluginDirMapping.put(AuthenticatorFactory.class.getSimpleName(), () -> Config.authentication_plugins_dir);
+        pluginDirMapping.put(AccessControllerFactory.class.getSimpleName(), () -> Config.authorization_plugins_dir);
+    }
+
+    public static List<Path> parsePluginRootDirectories(String pluginRootsConfig) {
+        if (pluginRootsConfig == null) {
+            return Collections.emptyList();
+        }
+        return Splitter.on(',')
+                .trimResults()
+                .omitEmptyStrings()
+                .splitToList(pluginRootsConfig)
+                .stream()
+                .map(Paths::get)
+                .collect(Collectors.toList());
     }
 
     /**
@@ -87,38 +106,45 @@ public class ClassLoaderUtils {
      */
     public static <T> List<T> loadServicesFromDirectory(Class<T> serviceClass) throws IOException {
         String pluginDirKey = serviceClass.getSimpleName();
-        String pluginDir = pluginDirMapping.get(pluginDirKey);
-        if (pluginDir == null) {
+        Supplier<String> pluginDirSupplier = pluginDirMapping.get(pluginDirKey);
+        if (pluginDirSupplier == null) {
             throw new RuntimeException("No mapping found for plugin directory key: " + pluginDirKey);
         }
-        File jarDir = new File(pluginDir);
-        // If the directory does not exist, return an empty list.
-        if (!jarDir.exists()) {
-            return new ArrayList<>();
-        }
-        if (!jarDir.isDirectory()) {
-            throw new IOException("The specified path is not a directory: " + pluginDir);
-        }
-
-        File[] jarFiles = jarDir.listFiles((dir, name) -> name.endsWith(".jar"));
-        if (jarFiles == null || jarFiles.length == 0) {
-            LOG.info("No JAR files found in the plugin directory: {}", pluginDir);
+        List<Path> pluginRoots = parsePluginRootDirectories(pluginDirSupplier.get());
+        if (pluginRoots.isEmpty()) {
             return new ArrayList<>();
         }
 
         List<T> services = new ArrayList<>();
-        for (File jarFile : jarFiles) {
-            URL[] jarURLs;
-            jarURLs = new URL[]{jarFile.toURI().toURL()};
+        for (Path pluginRoot : pluginRoots) {
+            File jarDir = pluginRoot.toFile();
+            // If the directory does not exist, skip it.
+            if (!jarDir.exists()) {
+                continue;
+            }
+            if (!jarDir.isDirectory()) {
+                throw new IOException("The specified path is not a directory: " + pluginRoot);
+            }
+
+            File[] jarFiles = jarDir.listFiles((dir, name) -> name.endsWith(".jar"));
+            if (jarFiles == null || jarFiles.length == 0) {
+                LOG.info("No JAR files found in the plugin directory: {}", pluginRoot);
+                continue;
+            }
+
+            for (File jarFile : jarFiles) {
+                URL[] jarURLs;
+                jarURLs = new URL[]{jarFile.toURI().toURL()};
 
-            try (ChildFirstClassLoader urlClassLoader = new ChildFirstClassLoader(jarURLs,
-                    Thread.currentThread().getContextClassLoader())) {
-                ServiceLoader<T> serviceLoader = ServiceLoader.load(serviceClass, urlClassLoader);
-                for (T service : serviceLoader) {
-                    services.add(service);
+                try (ChildFirstClassLoader urlClassLoader = new ChildFirstClassLoader(jarURLs,
+                        Thread.currentThread().getContextClassLoader())) {
+                    ServiceLoader<T> serviceLoader = ServiceLoader.load(serviceClass, urlClassLoader);
+                    for (T service : serviceLoader) {
+                        services.add(service);
+                    }
+                } catch (URISyntaxException e) {
+                    throw new RuntimeException(e);
                 }
-            } catch (URISyntaxException e) {
-                throw new RuntimeException(e);
             }
         }
         return services;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
index ea6173ec527..cb5a63dc43e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
@@ -305,7 +305,9 @@ public class AuthenticatorManager {
     }
 
     private boolean isOidcAuthenticateRequest(AuthenticateRequest request) {
-        if (CredentialType.OIDC_ID_TOKEN.equals(request.getCredentialType())) {
+        if (CredentialType.OIDC_ID_TOKEN.equals(request.getCredentialType())
+                || CredentialType.OAUTH_TOKEN.equals(request.getCredentialType())
+                || CredentialType.JWT_TOKEN.equals(request.getCredentialType())) {
             return true;
         }
         if (!(request.getPassword() instanceof ClearPassword)) {
@@ -355,7 +357,7 @@ public class AuthenticatorManager {
                 .remoteHost(primaryRequest.getRemoteHost())
                 .remotePort(primaryRequest.getRemotePort())
                 .clientType(primaryRequest.getClientType())
-                .credentialType(CredentialType.OIDC_ID_TOKEN)
+                .credentialType(CredentialType.OAUTH_TOKEN)
                 .credential(clearPassword.getPassword().getBytes(StandardCharsets.UTF_8))
                 .properties(primaryRequest.getProperties())
                 .build();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthPacketCredentialExtractor.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthPacketCredentialExtractor.java
index c9f94024aa7..443515702ed 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthPacketCredentialExtractor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthPacketCredentialExtractor.java
@@ -54,7 +54,7 @@ public class MysqlAuthPacketCredentialExtractor {
                 .password(new ClearPassword(token))
                 .remoteHost(channel.getRemoteIp())
                 .clientType("mysql")
-                .credentialType(CredentialType.OIDC_ID_TOKEN)
+                .credentialType(CredentialType.OAUTH_TOKEN)
                 .credential(oidcToken)
                 .build());
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/integration/AuthenticationIntegrationAuthenticator.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/integration/AuthenticationIntegrationAuthenticator.java
index b53103eb240..4d3104f75a1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/integration/AuthenticationIntegrationAuthenticator.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/integration/AuthenticationIntegrationAuthenticator.java
@@ -225,10 +225,12 @@ public class AuthenticationIntegrationAuthenticator implements Authenticator {
             return "";
         }
         String detailMessage = Strings.nullToEmpty(exception.getMessage());
-        if (detailMessage.startsWith("OIDC token signature validation failed")) {
-            return "OIDC token signature validation failed";
+        if (detailMessage.startsWith("OIDC access token signature validation failed")) {
+            return "OIDC access token signature validation failed";
         }
-        if (detailMessage.startsWith("OIDC token ")
+        if (detailMessage.startsWith("OIDC access token ")
+                || detailMessage.startsWith("OIDC token ")
+                || "Authentication request username does not match OIDC access token username".equals(detailMessage)
                 || "Authentication request username does not match OIDC token username".equals(detailMessage)) {
             return detailMessage;
         }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/plugin/AuthenticationPluginAuthenticator.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/plugin/AuthenticationPluginAuthenticator.java
index f05f4dbbce7..ff61bde349c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/plugin/AuthenticationPluginAuthenticator.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/plugin/AuthenticationPluginAuthenticator.java
@@ -29,6 +29,7 @@ import org.apache.doris.authentication.handler.AuthenticationPluginManager;
 import org.apache.doris.authentication.spi.AuthenticationPlugin;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.common.Config;
+import org.apache.doris.common.util.ClassLoaderUtils;
 import org.apache.doris.mysql.authenticate.AuthenticateRequest;
 import org.apache.doris.mysql.authenticate.AuthenticateResponse;
 import org.apache.doris.mysql.authenticate.AuthenticationFailureSummary;
@@ -49,8 +50,6 @@ import org.apache.logging.log4j.Logger;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -202,8 +201,9 @@ public class AuthenticationPluginAuthenticator implements Authenticator {
             return;
         }
         try {
-            Path pluginRoot = Paths.get(Config.authentication_plugins_dir);
-            pluginManager.loadAll(Collections.singletonList(pluginRoot), getClass().getClassLoader());
+            pluginManager.loadAll(
+                    ClassLoaderUtils.parsePluginRootDirectories(Config.authentication_plugins_dir),
+                    getClass().getClassLoader());
         } catch (AuthenticationException e) {
             throw new AuthenticationException(
                     "Failed to load authentication plugin for type '" + pluginType + "': " + e.getMessage(),
@@ -228,10 +228,12 @@ public class AuthenticationPluginAuthenticator implements Authenticator {
             return "";
         }
         String detailMessage = Strings.nullToEmpty(exception.getMessage());
-        if (detailMessage.startsWith("OIDC token signature validation failed")) {
-            return "OIDC token signature validation failed";
+        if (detailMessage.startsWith("OIDC access token signature validation failed")) {
+            return "OIDC access token signature validation failed";
         }
-        if (detailMessage.startsWith("OIDC token ")
+        if (detailMessage.startsWith("OIDC access token ")
+                || detailMessage.startsWith("OIDC token ")
+                || "Authentication request username does not match OIDC access token username".equals(detailMessage)
                 || "Authentication request username does not match OIDC token username".equals(detailMessage)) {
             return detailMessage;
         }
diff --git a/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationRuntimeTest.java b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationRuntimeTest.java
index 1edeeb46958..72ba74428a0 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationRuntimeTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationRuntimeTest.java
@@ -27,10 +27,13 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -43,15 +46,18 @@ import java.util.Set;
 class AuthenticationIntegrationRuntimeTest {
     private static final String CREATE_USER = "creator";
     private MockedStatic<Env> envMockedStatic;
+    private String originalAuthenticationPluginsDir;
 
     @BeforeEach
     void setUp() {
         envMockedStatic = Mockito.mockStatic(Env.class);
         envMockedStatic.when(Env::getCurrentEnv).thenReturn(null);
+        originalAuthenticationPluginsDir = org.apache.doris.common.Config.authentication_plugins_dir;
     }
 
     @AfterEach
     void tearDown() {
+        org.apache.doris.common.Config.authentication_plugins_dir = originalAuthenticationPluginsDir;
         if (envMockedStatic != null) {
             envMockedStatic.close();
         }
@@ -272,6 +278,25 @@ class AuthenticationIntegrationRuntimeTest {
                 runtime.getRuntimeState("corp"));
     }
 
+    @Test
+    void testPrepareAuthenticationIntegrationLoadsPluginFactoriesFromMultipleRoots() throws Exception {
+        org.apache.doris.common.Config.authentication_plugins_dir = "/tmp/auth-root-a, /tmp/auth-root-b";
+        AuthenticationPluginManager pluginManager = Mockito.mock(AuthenticationPluginManager.class);
+        AuthenticationPlugin plugin = Mockito.mock(AuthenticationPlugin.class);
+        Mockito.when(pluginManager.hasFactory("multi-root")).thenReturn(false, true);
+        Mockito.when(pluginManager.createPlugin(Mockito.any())).thenReturn(plugin);
+
+        AuthenticationIntegrationRuntime runtime = new AuthenticationIntegrationRuntime(pluginManager);
+        runtime.prepareAuthenticationIntegration(meta("corp", "multi-root", Collections.emptyMap()));
+
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<List<Path>> pluginRootsCaptor = ArgumentCaptor.forClass((Class) List.class);
+        Mockito.verify(pluginManager).loadAll(pluginRootsCaptor.capture(), Mockito.any());
+        Assertions.assertEquals(
+                Arrays.asList(Paths.get("/tmp/auth-root-a"), Paths.get("/tmp/auth-root-b")),
+                pluginRootsCaptor.getValue());
+    }
+
     private static AuthenticationIntegrationMeta meta(String name, String type, Map<String, String> properties)
             throws Exception {
         Map<String, String> createProperties = new LinkedHashMap<>();
diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/AuthenticatorManagerTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/AuthenticatorManagerTest.java
index a989de3e8ab..a12923d14f1 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/AuthenticatorManagerTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/AuthenticatorManagerTest.java
@@ -536,7 +536,7 @@ class AuthenticatorManagerTest {
         Assertions.assertTrue(result);
         ArgumentCaptor<AuthenticateRequest> requestCaptor = ArgumentCaptor.forClass(AuthenticateRequest.class);
         Mockito.verify(chainAuthenticator).authenticate(requestCaptor.capture());
-        Assertions.assertEquals(CredentialType.OIDC_ID_TOKEN, requestCaptor.getValue().getCredentialType());
+        Assertions.assertEquals(CredentialType.OAUTH_TOKEN, requestCaptor.getValue().getCredentialType());
         Assertions.assertArrayEquals("oidc-token".getBytes(StandardCharsets.UTF_8),
                 requestCaptor.getValue().getCredential());
     }
@@ -554,7 +554,7 @@ class AuthenticatorManagerTest {
                         .password(new ClearPassword(OIDC_ID_TOKEN))
                         .remoteHost(REMOTE_IP)
                         .clientType("mysql")
-                        .credentialType(CredentialType.OIDC_ID_TOKEN)
+                        .credentialType(CredentialType.OAUTH_TOKEN)
                         .credential(OIDC_ID_TOKEN.getBytes(StandardCharsets.UTF_8))
                         .build()));
 
@@ -605,7 +605,7 @@ class AuthenticatorManagerTest {
                         .password(new ClearPassword(OIDC_ID_TOKEN))
                         .remoteHost(REMOTE_IP)
                         .clientType("mysql")
-                        .credentialType(CredentialType.OIDC_ID_TOKEN)
+                        .credentialType(CredentialType.OAUTH_TOKEN)
                         .credential(OIDC_ID_TOKEN.getBytes(StandardCharsets.UTF_8))
                         .build()));
 
@@ -731,7 +731,7 @@ class AuthenticatorManagerTest {
         ArgumentCaptor<AuthenticateRequest> requestCaptor = ArgumentCaptor.forClass(AuthenticateRequest.class);
         Mockito.verify(chainAuthenticator).authenticate(requestCaptor.capture());
         AuthenticateRequest chainRequest = requestCaptor.getValue();
-        Assertions.assertEquals(CredentialType.OIDC_ID_TOKEN, chainRequest.getCredentialType());
+        Assertions.assertEquals(CredentialType.OAUTH_TOKEN, chainRequest.getCredentialType());
         Assertions.assertArrayEquals(originalOidcToken, chainRequest.getCredential());
         Assertions.assertInstanceOf(ClearPassword.class, chainRequest.getPassword());
         Assertions.assertEquals("oidc-token", ((ClearPassword) chainRequest.getPassword()).getPassword());
diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/MysqlAuthPacketCredentialExtractorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/MysqlAuthPacketCredentialExtractorTest.java
index fc4bffe56e4..acb6c7790a1 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/MysqlAuthPacketCredentialExtractorTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/MysqlAuthPacketCredentialExtractorTest.java
@@ -56,7 +56,7 @@ class MysqlAuthPacketCredentialExtractorTest {
         AuthenticateRequest request = extractor.extractAuthenticateRequest(USER_NAME, channel, authPacket)
                 .orElseThrow(() -> new AssertionError("request is required"));
 
-        Assertions.assertEquals(CredentialType.OIDC_ID_TOKEN, request.getCredentialType());
+        Assertions.assertEquals(CredentialType.OAUTH_TOKEN, request.getCredentialType());
         Assertions.assertArrayEquals("token-from-client".getBytes(StandardCharsets.UTF_8), request.getCredential());
         Assertions.assertInstanceOf(ClearPassword.class, request.getPassword());
         Assertions.assertEquals("token-from-client", ((ClearPassword) request.getPassword()).getPassword());
@@ -75,7 +75,7 @@ class MysqlAuthPacketCredentialExtractorTest {
         AuthenticateRequest request = extractor.extractAuthenticateRequest(USER_NAME, channel, authPacket)
                 .orElseThrow(() -> new AssertionError("request is required"));
 
-        Assertions.assertEquals(CredentialType.OIDC_ID_TOKEN, request.getCredentialType());
+        Assertions.assertEquals(CredentialType.OAUTH_TOKEN, request.getCredentialType());
         Assertions.assertArrayEquals(token.getBytes(StandardCharsets.UTF_8), request.getCredential());
         Assertions.assertInstanceOf(ClearPassword.class, request.getPassword());
         Assertions.assertEquals(token, ((ClearPassword) request.getPassword()).getPassword());
diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/plugin/AuthenticationPluginAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/plugin/AuthenticationPluginAuthenticatorTest.java
index 86dc1642d3a..af012aeb2ac 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/plugin/AuthenticationPluginAuthenticatorTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/plugin/AuthenticationPluginAuthenticatorTest.java
@@ -41,8 +41,12 @@ import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 class AuthenticationPluginAuthenticatorTest {
@@ -51,6 +55,7 @@ class AuthenticationPluginAuthenticatorTest {
     private AuthenticationPluginManager pluginManager;
     private AuthenticationPlugin plugin;
     private MockedStatic<Env> envMockedStatic;
+    private String originalAuthenticationPluginsDir;
 
     private static final String USER_NAME = "alice";
     private static final String REMOTE_IP = "127.0.0.1";
@@ -62,6 +67,7 @@ class AuthenticationPluginAuthenticatorTest {
         auth = Mockito.mock(Auth.class);
         pluginManager = Mockito.mock(AuthenticationPluginManager.class);
         plugin = Mockito.mock(AuthenticationPlugin.class);
+        originalAuthenticationPluginsDir = org.apache.doris.common.Config.authentication_plugins_dir;
 
         envMockedStatic = Mockito.mockStatic(Env.class);
         envMockedStatic.when(Env::getCurrentEnv).thenReturn(env);
@@ -134,7 +140,7 @@ class AuthenticationPluginAuthenticatorTest {
                 authPacket,
                 handshakePacket).orElseThrow(() -> new AssertionError("request is required"));
 
-        Assertions.assertEquals(CredentialType.OIDC_ID_TOKEN, request.getCredentialType());
+        Assertions.assertEquals(CredentialType.OAUTH_TOKEN, request.getCredentialType());
         Assertions.assertArrayEquals("token-from-client".getBytes(StandardCharsets.UTF_8), request.getCredential());
 
         AuthenticateResponse response = authenticator.authenticate(request);
@@ -142,13 +148,29 @@ class AuthenticationPluginAuthenticatorTest {
 
         ArgumentCaptor<AuthenticationRequest> requestCaptor = ArgumentCaptor.forClass(AuthenticationRequest.class);
         Mockito.verify(plugin).authenticate(requestCaptor.capture(), Mockito.any());
-        Assertions.assertEquals(CredentialType.OIDC_ID_TOKEN, requestCaptor.getValue().getCredentialType());
+        Assertions.assertEquals(CredentialType.OAUTH_TOKEN, requestCaptor.getValue().getCredentialType());
         Assertions.assertArrayEquals("token-from-client".getBytes(StandardCharsets.UTF_8),
                 requestCaptor.getValue().getCredential());
     }
 
+    @Test
+    void testEnsurePluginFactoryLoadedSupportsMultiplePluginRoots() throws Exception {
+        org.apache.doris.common.Config.authentication_plugins_dir = "/tmp/auth-root-a, /tmp/auth-root-b";
+        Mockito.when(pluginManager.hasFactory("oidc")).thenReturn(false, true);
+
+        new AuthenticationPluginAuthenticator("oidc", new HashMap<>(), pluginManager);
+
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<List<Path>> pluginRootsCaptor = ArgumentCaptor.forClass((Class) List.class);
+        Mockito.verify(pluginManager).loadAll(pluginRootsCaptor.capture(), Mockito.any());
+        Assertions.assertEquals(
+                Arrays.asList(Paths.get("/tmp/auth-root-a"), Paths.get("/tmp/auth-root-b")),
+                pluginRootsCaptor.getValue());
+    }
+
     @AfterEach
     void tearDown() {
+        org.apache.doris.common.Config.authentication_plugins_dir = originalAuthenticationPluginsDir;
         if (envMockedStatic != null) {
             envMockedStatic.close();
         }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org