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

mchades pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new ac031e6e94 [Cherry-pick to branch-1.3] [#11680] feat(idp-basic): Add 
basic authenticator type for Web UI login contract (#11688) (#11723)
ac031e6e94 is described below

commit ac031e6e94176e01fa8a2eafab160c82fb3f2aa7
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Jun 18 09:36:18 2026 +0800

    [Cherry-pick to branch-1.3] [#11680] feat(idp-basic): Add basic 
authenticator type for Web UI login contract (#11688) (#11723)
    
    **Cherry-pick Information:**
    - Original commit: d845d5cddc5bc208968a0e6bf4b594691afdb853
    - Target branch: `branch-1.3`
    - Status: ✅ Clean cherry-pick (no conflicts)
    
    Co-authored-by: MaSai <[email protected]>
---
 .../apache/gravitino/auth/AuthenticatorType.java   |  3 +
 design-docs/gravitino-local-authentication.md      | 13 ++--
 docs/open-api/idp/idp.yaml                         |  5 +-
 docs/open-api/idp/openapi.yaml                     |  9 +--
 docs/security/how-to-authenticate.md               | 21 +++---
 docs/security/how-to-use-built-in-idp.md           | 38 ++++++-----
 docs/webui-v2.md                                   | 10 ++-
 docs/webui.md                                      |  8 +--
 .../gravitino/idp/auth/BasicAuthenticator.java     | 23 +++++++
 .../idp/web/rest/feature/IdpRESTFeature.java       | 33 ++++++++--
 .../apache/gravitino/idp/SystemExitTestHelper.java | 68 +++++++++++++++++++
 .../gravitino/idp/TestIdpUserGroupManager.java     | 17 +++--
 .../gravitino/idp/auth/TestBasicAuthenticator.java | 23 +++++++
 .../integration/test/BasicAuthOperationsIT.java    |  4 +-
 .../idp/integration/test/IdpRESTApiIT.java         |  6 +-
 .../idp/web/rest/feature/TestIdpRESTFeature.java   | 77 ++++++++--------------
 .../authentication/AuthenticatorFactory.java       |  6 ++
 17 files changed, 249 insertions(+), 115 deletions(-)

diff --git 
a/common/src/main/java/org/apache/gravitino/auth/AuthenticatorType.java 
b/common/src/main/java/org/apache/gravitino/auth/AuthenticatorType.java
index c44c2c9a90..a458d5952f 100644
--- a/common/src/main/java/org/apache/gravitino/auth/AuthenticatorType.java
+++ b/common/src/main/java/org/apache/gravitino/auth/AuthenticatorType.java
@@ -27,6 +27,9 @@ public enum AuthenticatorType {
   /** Simple authentication. */
   SIMPLE,
 
+  /** Built-in IdP HTTP Basic authentication. */
+  BASIC,
+
   /** Authentication that uses OAuth. */
   OAUTH,
 
diff --git a/design-docs/gravitino-local-authentication.md 
b/design-docs/gravitino-local-authentication.md
index bfb1d7ea3f..b8ba682d84 100644
--- a/design-docs/gravitino-local-authentication.md
+++ b/design-docs/gravitino-local-authentication.md
@@ -315,12 +315,13 @@ fresh Gravitino deployment.
 1. Deploy Gravitino with built-in IDP enabled:
 
    ```properties
+   gravitino.authenticators=basic
    
gravitino.server.rest.extensionPackages=org.apache.gravitino.idp.web.rest.feature
    gravitino.authorization.serviceAdmins=admin1,admin2
    ```
 
-   Built-in IdP is incompatible with the `simple` authenticator. When the 
`idp-basic` plugin is
-   enabled, `gravitino.authenticators` must not include `simple`.
+   Built-in IdP is incompatible with the `simple` authenticator (the default),
+   `gravitino.authenticators` must include `basic` and must not include 
`simple`.
 
 2. Export the initial service admin password before starting Gravitino:
 
@@ -399,11 +400,15 @@ credentials are otherwise exposed on the wire.
 
 | Key                                         | Value                          
                 | Required when using built-in IDP |
 
|---------------------------------------------|-------------------------------------------------|----------------------------------|
+| `gravitino.authenticators`                  | Must include `basic`           
                 | Yes                              |
 | `gravitino.server.rest.extensionPackages`   | 
`org.apache.gravitino.idp.web.rest.feature`     | Yes                           
   |
 | `gravitino.authorization.serviceAdmins`     | Comma-separated service admin  
                 | Yes                              |
 
-List `org.apache.gravitino.idp.web.rest.feature` in 
`gravitino.server.rest.extensionPackages` so
-Jersey registers `/api/idp/*` management APIs. Callers must use Basic 
authentication with a username
+List `basic` in `gravitino.authenticators` and 
`org.apache.gravitino.idp.web.rest.feature` in
+`gravitino.server.rest.extensionPackages` so Jersey registers `/api/idp/*` 
management APIs and the
+Web UI can use the built-in IdP login form. Built-in IdP is incompatible with 
the `simple`
+authenticator (the default), `gravitino.authenticators` must include `basic` 
and must not include
+`simple`. Callers must use Basic authentication with a username
 in `gravitino.authorization.serviceAdmins` and a password stored in 
`idp_user_meta`.
 
 ### 8.2 Password Algorithm
diff --git a/docs/open-api/idp/idp.yaml b/docs/open-api/idp/idp.yaml
index 6b233bbd51..51b44480db 100644
--- a/docs/open-api/idp/idp.yaml
+++ b/docs/open-api/idp/idp.yaml
@@ -26,8 +26,9 @@ paths:
       summary: Add built-in IDP user
       description: >
         Creates a built-in IDP user with the given username and password.
-        Requires the `idp-basic` plugin and built-in IdP Basic authentication.
-        `gravitino.authenticators` must not include `simple`.
+        Requires the `idp-basic` plugin and built-in IdP Basic authentication. 
Built-in IdP is
+        incompatible with the `simple` authenticator (the default), 
`gravitino.authenticators` must
+        include `basic` and must not include `simple`.
       operationId: addIdpUser
       requestBody:
         required: true
diff --git a/docs/open-api/idp/openapi.yaml b/docs/open-api/idp/openapi.yaml
index 23000ef4f9..d8271fab8d 100644
--- a/docs/open-api/idp/openapi.yaml
+++ b/docs/open-api/idp/openapi.yaml
@@ -25,10 +25,11 @@ info:
   version: 1.3.0-SNAPSHOT
   description: |
     OpenAPI specification for built-in IDP user and group management APIs 
exposed
-    by the `idp-basic` plugin. Clients authenticate with Basic credentials
-    validated against built-in IdP user metadata. Enable the plugin via
-    `gravitino.server.rest.extensionPackages`; `gravitino.authenticators` must 
not
-    include `simple` when IdP is enabled.
+    by the `idp-basic` plugin. Clients authenticate with Basic credentials 
validated against
+    built-in IdP user metadata. Enable built-in IdP by registering
+    `org.apache.gravitino.idp.web.rest.feature` in 
`gravitino.server.rest.extensionPackages`.
+    Built-in IdP is incompatible with the `simple` authenticator (the default),
+    `gravitino.authenticators` must include `basic` and must not include 
`simple`.
 
 servers:
   - url: "{scheme}://{host}:{port}/{basePath}"
diff --git a/docs/security/how-to-authenticate.md 
b/docs/security/how-to-authenticate.md
index 8cc2938a1e..87c20f953d 100644
--- a/docs/security/how-to-authenticate.md
+++ b/docs/security/how-to-authenticate.md
@@ -50,9 +50,13 @@ in the relational entity store.
 
 To enable Basic mode:
 
+- Set `gravitino.authenticators` to `basic`.
 - Set `gravitino.server.rest.extensionPackages` to 
`org.apache.gravitino.idp.web.rest.feature`.
 - Set `gravitino.authorization.serviceAdmins` to the service admin usernames 
that should exist in
   the built-in IDP.
+
+Built-in IdP is **incompatible** with the `simple` authenticator (the default),
+`gravitino.authenticators` must include `basic` and must not include `simple`.
 - On the first startup, if any configured service admin does not yet have a 
password, set the
   `GRAVITINO_INITIAL_ADMIN_PASSWORD` environment variable to the initial 
password (12 to 64
   characters) before starting Gravitino. The same password is applied to every 
configured service
@@ -86,13 +90,9 @@ curl -v -X GET \
   http://localhost:8090/api/version
 ```
 
-:::note
-The Web UI does not provide a username/password login form for built-in IDP 
Basic authentication.
-Use REST clients, the Java/Python client, or engine connectors instead. See
-[built-in IDP Web UI](how-to-use-built-in-idp.md#web-ui), the
-[Web UI initial page](../webui.md#initial-page), and the
-[Web V2 initial page](../webui-v2.md#initial-page).
-:::
+The Web UI uses the first entry in `gravitino.authenticators` from `/configs`. 
When it is `basic`,
+the login page shows a username and password form backed by built-in IdP user 
metadata. See
+[built-in IDP Web UI](how-to-use-built-in-idp.md#web-ui).
 
 ### OAuth Mode
 
@@ -325,7 +325,7 @@ Gravitino server and Gravitino Iceberg REST server share 
the same configuration
 | Configuration item                                  | Description            
                                                                                
                                                                                
                                                                                
 | Default value                                                       | 
Required                                                                        
                | Si [...]
 
|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|---
 [...]
 | `gravitino.authenticator`                           | It is deprecated since 
Gravitino 0.6.0. Please use `gravitino.authenticators` instead.                 
                                                                                
                                                                                
 | `simple`                                                            | No     
                                                                                
         | 0. [...]
-| `gravitino.authenticators`                          | The authenticators 
which Gravitino uses, setting as `simple`,`oauth` or `kerberos`. Multiple 
authenticators are separated by commas. If a request is supported by multiple 
authenticators simultaneously, the first authenticator will be used by default. 
             | `simple`                                                         
   | No                                                                         
                     | 0. [...]
+| `gravitino.authenticators`                          | The authenticators 
which Gravitino uses, setting as `simple`, `basic`, `oauth` or `kerberos`. 
Multiple authenticators are separated by commas. If a request is supported by 
multiple authenticators simultaneously, the first authenticator will be used by 
default.    | `simple`                                                          
  | No                                                                          
                    | 0. [...]
 | `gravitino.authenticator.oauth.serviceAudience`     | The audience name when 
Gravitino uses OAuth as the authenticator.                                      
                                                                                
                                                                                
 | `GravitinoServer`                                                   | No     
                                                                                
         | 0. [...]
 | `gravitino.authenticator.oauth.allowSkewSecs`       | The JWT allows skew 
seconds when Gravitino uses OAuth as the authenticator.                         
                                                                                
                                                                                
    | `0`                                                                 | No  
                                                                                
            | 0. [...]
 | `gravitino.authenticator.oauth.defaultSignKey`      | The signing key of JWT 
when Gravitino uses OAuth as the authenticator.                                 
                                                                                
                                                                                
 | (none)                                                              | Yes if 
use `oauth` as the authenticator                                                
         | 0. [...]
@@ -374,14 +374,15 @@ This example shows how to enable built-in Basic 
authentication.
 
 - Gravitino distribution package (includes the idp-basic plugin on the server 
classpath)
 
-Built-in IdP is **incompatible** with the `simple` authenticator (the 
default). When the
-`idp-basic` plugin is enabled, `gravitino.authenticators` must not include 
`simple`.
+Built-in IdP is **incompatible** with the `simple` authenticator (the default),
+`gravitino.authenticators` must include `basic` and must not include `simple`.
 
 **Configuration:**
 
 Append the following to `conf/gravitino.conf`:
 
 ```text
+gravitino.authenticators = basic
 gravitino.server.rest.extensionPackages = 
org.apache.gravitino.idp.web.rest.feature
 gravitino.authorization.serviceAdmins = admin
 ```
diff --git a/docs/security/how-to-use-built-in-idp.md 
b/docs/security/how-to-use-built-in-idp.md
index 09dd0e0ad5..9eb2e64b6a 100644
--- a/docs/security/how-to-use-built-in-idp.md
+++ b/docs/security/how-to-use-built-in-idp.md
@@ -21,12 +21,18 @@ request and response schemas, see the [Built-in IDP 
OpenAPI](../open-api/idp/ope
 
 ## Web UI
 
-Built-in IDP is **incompatible** with the `simple` authenticator (the 
default). The Web UI does not
-support signing in with built-in IDP Basic credentials. Login pages only 
handle `simple`
-(username-only when authorization is enabled) and `oauth` (OIDC/OAuth) flows. 
If you need the Web UI
-while built-in IDP is enabled, prefer [OAuth 
mode](how-to-authenticate.md#oauth-mode). For Basic
-authentication, use the REST API, [Java/Python 
clients](how-to-authenticate.md#basic-mode), or
-engine connectors instead.
+When built-in IdP Basic authentication is enabled, the Web UI exposes a 
username and password login
+form. Configure **both** of the following in `gravitino.conf`:
+
+```properties
+gravitino.authenticators = basic
+gravitino.server.rest.extensionPackages = 
org.apache.gravitino.idp.web.rest.feature
+```
+
+The Web UI reads `gravitino.authenticators` from `/configs` and uses the first 
entry as the active
+authentication type. List `basic` first when you want the built-in IdP login 
form. Built-in IdP is
+**incompatible** with the `simple` authenticator (the default). When IdP is 
enabled, do not include
+`simple` in `gravitino.authenticators`.
 
 ---
 
@@ -37,12 +43,12 @@ Before you call `/api/idp/*`, ensure the following:
 1. **IDP REST API registration** — In `gravitino.conf`, set:
 
    ```properties
+   gravitino.authenticators = basic
    gravitino.server.rest.extensionPackages = 
org.apache.gravitino.idp.web.rest.feature
    ```
 
 2. **Server authenticator** — Built-in IdP is **incompatible** with the 
`simple` authenticator
-   (the default). When the `idp-basic` plugin is enabled, 
`gravitino.authenticators` must not
-   include `simple`.
+   (the default). Do not list `simple` together with `basic` in 
`gravitino.authenticators`.
 
 3. **Service admin passwords** — Built-in IDP requires every username in
    `gravitino.authorization.serviceAdmins` to have a password stored in 
`idp_user_meta` before you
@@ -74,14 +80,16 @@ Before you call `/api/idp/*`, ensure the following:
 
 Set service admins in `gravitino.conf` (see also 
[Prerequisites](#prerequisites)):
 
-| Configuration item                        | Description                      
                                                   | Example |
-|-------------------------------------------|-------------------------------------------------------------------------------------|---------|
-| `gravitino.server.rest.extensionPackages` | Registers built-in IdP REST APIs 
                                                   | 
`org.apache.gravitino.idp.web.rest.feature` |
-| `gravitino.authorization.serviceAdmins`   | Comma-separated service admin 
that can call built-in IDP management APIs            | `admin` |
+| Configuration item                        | Description                      
                                        | Example                               
      |
+|-------------------------------------------|--------------------------------------------------------------------------|---------------------------------------------|
+| `gravitino.authenticators`                | Must include `basic` when the 
built-in IdP plugin is enabled             | `basic`                            
         |
+| `gravitino.server.rest.extensionPackages` | Registers built-in IdP REST APIs 
                                        | 
`org.apache.gravitino.idp.web.rest.feature` |
+| `gravitino.authorization.serviceAdmins`   | Comma-separated service admin 
that can call built-in IDP management APIs | `admin`                            
         |
 
 Example:
 
 ```properties
+gravitino.authenticators = basic
 gravitino.server.rest.extensionPackages = 
org.apache.gravitino.idp.web.rest.feature
 gravitino.authorization.serviceAdmins = admin
 ```
@@ -322,14 +330,14 @@ Replace these with values that match your deployment.
    [Configuration](#configuration)):
 
    ```properties
+   gravitino.authenticators = basic
    gravitino.server.rest.extensionPackages = 
org.apache.gravitino.idp.web.rest.feature
    gravitino.authorization.enable = true
    gravitino.authorization.serviceAdmins = admin
    ```
 
-   Built-in IdP is **incompatible** with the `simple` authenticator. Remove 
`simple` from
-   `gravitino.authenticators` (the default). For example, use `oauth` when the 
Web UI authenticates
-   through an external IdP; see [How to authenticate](how-to-authenticate.md).
+   Built-in IdP is **incompatible** with the `simple` authenticator (the 
default),
+   `gravitino.authenticators` must include `basic` and must not include 
`simple`.
 
 2. Before the first start, set the initial service admin password (see
    [password rules](#password-and-username-rules)):
diff --git a/docs/webui-v2.md b/docs/webui-v2.md
index 79c446301e..bc3fad8182 100644
--- a/docs/webui-v2.md
+++ b/docs/webui-v2.md
@@ -50,16 +50,14 @@ The Web V2 landing page depends on both the authentication 
mode and whether auth
 
   ![simple-with-login](./assets/webui-v2/simple-with-login.png)
 
+- When `gravitino.authenticators=basic` and the built-in IdP extension package 
is configured, the
+  Web UI shows a username and password login form backed by built-in IdP user 
metadata. See
+  [built-in IDP Web UI](security/how-to-use-built-in-idp.md#web-ui).
+
 - When `gravitino.authenticators=oauth`, OAuth configuration is required for 
login. OAuth mode requires `gravitino.authorization.enable=true`. See the 
details in [Security](security/security.md)
 
   ![oauth-login](./assets/webui-v2/oauth-login.png)
 
-:::note
-Built-in IDP Basic authentication is not supported in the Web UI. The UI does 
not collect
-username and password for this mode. Use the REST API, Java/Python clients, or 
engine connectors
-instead. See [built-in IDP Web UI](security/how-to-use-built-in-idp.md#web-ui).
-:::
-
 ### Metalakes
 
 Overview for Metalake in the Web V2.
diff --git a/docs/webui.md b/docs/webui.md
index d93ae14598..28da4d45d4 100644
--- a/docs/webui.md
+++ b/docs/webui.md
@@ -20,13 +20,7 @@ Integrate [OAuth settings](security/security.md) to view, 
add, modify, and delet
 
 The web UI homepage displayed in Gravitino depends on the configuration 
parameter for OAuth mode, see the details in [Security](security/security.md).
 
-Set parameter for `gravitino.authenticators`, [`simple`](#simple-mode) or 
[`oauth`](#oauth-mode). Simple mode is the default authentication option. If 
multiple authenticators are set, the first one is taken by default.
-
-:::note
-Built-in IDP Basic authentication is not supported in the Web UI. The UI does 
not collect
-username and password for this mode. Use the REST API, Java/Python clients, or 
engine connectors
-instead. See [built-in IDP Web UI](security/how-to-use-built-in-idp.md#web-ui).
-:::
+Set parameter for `gravitino.authenticators`: [`simple`](#simple-mode), 
[`basic`](../security/how-to-authenticate.md#basic-mode), or 
[`oauth`](#oauth-mode). Simple mode is the default authentication option. If 
multiple authenticators are set, the first one is taken by default.
 
 :::tip
 After changing the configuration, make sure to restart the Gravitino server.
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/auth/BasicAuthenticator.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/auth/BasicAuthenticator.java
index 3192f2fb67..d5a111d503 100644
--- 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/auth/BasicAuthenticator.java
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/auth/BasicAuthenticator.java
@@ -28,6 +28,7 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
 import org.apache.gravitino.GravitinoEnv;
 import org.apache.gravitino.UserGroup;
 import org.apache.gravitino.UserPrincipal;
@@ -35,6 +36,7 @@ import org.apache.gravitino.auth.AuthConstants;
 import org.apache.gravitino.exceptions.UnauthorizedException;
 import org.apache.gravitino.idp.IdpUserGroupManager;
 import org.apache.gravitino.idp.model.IdpUser;
+import org.apache.gravitino.idp.web.rest.feature.IdpRESTFeature;
 import org.apache.gravitino.server.authentication.Authenticator;
 
 /** Authenticates HTTP Basic credentials against built-in IdP user metadata. */
@@ -62,10 +64,31 @@ public class BasicAuthenticator implements Authenticator {
 
   @Override
   public void initialize(Config config) {
+    validateExtensionPackage(config);
     GravitinoEnv env = GravitinoEnv.getInstance();
     this.userGroupManager = IdpUserGroupManager.getInstance(config, 
env.idGenerator());
   }
 
+  /**
+   * Validates that the built-in IdP REST extension package is enabled when 
Basic authentication is
+   * used.
+   *
+   * @param config The server configuration.
+   */
+  static void validateExtensionPackage(Config config) {
+    boolean idpExtensionEnabled =
+        config.get(Configs.REST_API_EXTENSION_PACKAGES).stream()
+            .anyMatch(
+                pkg -> 
IdpRESTFeature.IDP_REST_EXTENSION_PACKAGE.equalsIgnoreCase(pkg.trim()));
+    if (!idpExtensionEnabled) {
+      throw new IllegalStateException(
+          String.format(
+              "'basic' in gravitino.authenticators requires 
gravitino.server.rest.extensionPackages "
+                  + "to include %s.",
+              IdpRESTFeature.IDP_REST_EXTENSION_PACKAGE));
+    }
+  }
+
   @Override
   public boolean supportsToken(byte[] tokenData) {
     return tokenData != null
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/rest/feature/IdpRESTFeature.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/rest/feature/IdpRESTFeature.java
index 668b5d84a3..bbe468046c 100644
--- 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/rest/feature/IdpRESTFeature.java
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/rest/feature/IdpRESTFeature.java
@@ -41,16 +41,17 @@ import org.slf4j.LoggerFactory;
 /**
  * Registers built-in IdP REST resources for the idp-basic plugin.
  *
- * <p>Configure {@link Configs#REST_API_EXTENSION_PACKAGES} to {@code
- * org.apache.gravitino.idp.web.rest.feature} so Jersey auto-discovers this 
feature. IdP REST
- * resource classes remain in {@code org.apache.gravitino.idp.web.rest} and 
are registered here.
- * Also initializes configured service admins in the built-in IdP when they do 
not yet exist.
+ * <p>Configure {@link Configs#REST_API_EXTENSION_PACKAGES} to {@link 
#IDP_REST_EXTENSION_PACKAGE}
+ * and list {@code basic} in {@link Configs#AUTHENTICATORS} so Jersey 
auto-discovers this feature.
+ * IdP REST resource classes remain in {@code 
org.apache.gravitino.idp.web.rest} and are registered
+ * here. Also initializes configured service admins in the built-in IdP when 
they do not yet exist.
  */
 @Provider
 public class IdpRESTFeature implements Feature {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(IdpRESTFeature.class);
 
+  /** Extension package name registered through {@code 
gravitino.server.rest.extensionPackages}. */
   public static final String IDP_REST_EXTENSION_PACKAGE = 
IdpRESTFeature.class.getPackageName();
 
   /** Environment variable for the initial password of configured service 
admins. */
@@ -81,16 +82,34 @@ public class IdpRESTFeature implements Feature {
   /**
    * Validates that the server configuration is compatible with the built-in 
IdP plugin.
    *
+   * <p>Called when the idp-basic extension package is enabled. Requires 
{@code basic} in {@link
+   * Configs#AUTHENTICATORS} and rejects {@code simple}.
+   *
    * @param config The server configuration.
    */
   static void validateConfiguration(Config config) {
+    List<String> authenticators = config.get(Configs.AUTHENTICATORS);
+    boolean usesBasic =
+        authenticators.stream()
+            .anyMatch(name -> 
AuthenticatorType.BASIC.name().equalsIgnoreCase(name.trim()));
     boolean usesSimple =
-        config.get(Configs.AUTHENTICATORS).stream()
+        authenticators.stream()
             .anyMatch(name -> 
AuthenticatorType.SIMPLE.name().equalsIgnoreCase(name.trim()));
+
+    if (!usesBasic) {
+      LOG.error(
+          "gravitino.server.rest.extensionPackages includes the built-in IdP 
plugin ({}) but "
+              + "'basic' is not listed in gravitino.authenticators. Add 
'basic' to "
+              + "gravitino.authenticators.",
+          IDP_REST_EXTENSION_PACKAGE);
+      System.exit(1);
+    }
+
     if (usesSimple) {
       LOG.error(
-          "Built-in IdP is incompatible with Simple authentication. "
-              + "Remove 'simple' from gravitino.authenticators (default is 
simple).");
+          "Built-in IdP basic authentication is incompatible with simple 
authentication because "
+              + "both handle Authorization: Basic headers. Remove 'simple' 
from "
+              + "gravitino.authenticators.");
       System.exit(1);
     }
   }
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/SystemExitTestHelper.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/SystemExitTestHelper.java
new file mode 100644
index 0000000000..17cc3423ef
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/SystemExitTestHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.idp;
+
+/** Test-only helpers for code paths that call {@link System#exit(int)}. */
+public final class SystemExitTestHelper {
+
+  private SystemExitTestHelper() {}
+
+  /**
+   * Runs an action while intercepting {@link System#exit(int)} and rethrowing 
it as {@link
+   * SystemExitException}.
+   *
+   * @param action The action that may call {@code System.exit}.
+   */
+  @SuppressWarnings("removal")
+  public static void runWithExitGuard(Runnable action) {
+    SecurityManager original = System.getSecurityManager();
+    System.setSecurityManager(
+        new SecurityManager() {
+          @Override
+          public void checkExit(int status) {
+            throw new SystemExitException(status);
+          }
+
+          @Override
+          public void checkPermission(java.security.Permission perm) {
+            // Allow test execution.
+          }
+        });
+    try {
+      action.run();
+    } finally {
+      System.setSecurityManager(original);
+    }
+  }
+
+  /** Thrown by {@link #runWithExitGuard(Runnable)} when the guarded action 
calls {@code exit}. */
+  public static final class SystemExitException extends SecurityException {
+    private final int status;
+
+    private SystemExitException(int status) {
+      super("System.exit(" + status + ")");
+      this.status = status;
+    }
+
+    /** Returns the exit status passed to {@link System#exit(int)}. */
+    public int status() {
+      return status;
+    }
+  }
+}
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
index b18972c597..419b688dcf 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
@@ -38,9 +38,9 @@ import java.nio.file.Path;
 import java.util.Comparator;
 import java.util.stream.Stream;
 import org.apache.gravitino.Config;
+import org.apache.gravitino.auth.AuthenticatorType;
 import org.apache.gravitino.exceptions.AlreadyExistsException;
 import org.apache.gravitino.exceptions.NotFoundException;
-import org.apache.gravitino.idp.auth.BasicAuthenticator;
 import org.apache.gravitino.idp.basic.IdpCredentialValidator;
 import org.apache.gravitino.idp.model.IdpGroup;
 import org.apache.gravitino.idp.model.IdpUser;
@@ -55,8 +55,7 @@ import org.junit.jupiter.params.provider.ValueSource;
 /** Integration tests for {@link IdpUserGroupManager} backed by an embedded H2 
store. */
 public class TestIdpUserGroupManager {
 
-  private static final String BASIC_AUTHENTICATOR_CLASS =
-      BasicAuthenticator.class.getCanonicalName();
+  private static final String BASIC_AUTHENTICATOR = 
AuthenticatorType.BASIC.name().toLowerCase();
 
   private static final String VALID_PASSWORD = "Passw0rd-1234";
   private static final String ANOTHER_VALID_PASSWORD = "AnotherPass1!";
@@ -208,7 +207,7 @@ public class TestIdpUserGroupManager {
 
   @Test
   public void 
testInitializeConfiguredServiceAdminsCreatesMissingServiceAdmin() throws 
IOException {
-    loadServiceAdminConfig(BASIC_AUTHENTICATOR_CLASS, 
"initAdminCreate1,initAdminCreate2");
+    loadServiceAdminConfig(BASIC_AUTHENTICATOR, 
"initAdminCreate1,initAdminCreate2");
     manager.addUser("initAdminCreate2", VALID_PASSWORD);
 
     manager.initializeConfiguredServiceAdmins(config, VALID_PASSWORD);
@@ -220,7 +219,7 @@ public class TestIdpUserGroupManager {
   @Test
   public void 
testInitializeConfiguredServiceAdminsSkipsWhenNoServiceAdminsConfigured()
       throws IOException {
-    loadServiceAdminConfig(BASIC_AUTHENTICATOR_CLASS, "");
+    loadServiceAdminConfig(BASIC_AUTHENTICATOR, "");
     manager.initializeConfiguredServiceAdmins(config, VALID_PASSWORD);
     Assertions.assertThrows(NotFoundException.class, () -> 
manager.getUser("initAdminSkipList1"));
   }
@@ -228,7 +227,7 @@ public class TestIdpUserGroupManager {
   @Test
   public void 
testInitializeConfiguredServiceAdminsSkipsWhenAllServiceAdminsAlreadyExist()
       throws IOException {
-    loadServiceAdminConfig(BASIC_AUTHENTICATOR_CLASS, 
"initAdminExist1,initAdminExist2");
+    loadServiceAdminConfig(BASIC_AUTHENTICATOR, 
"initAdminExist1,initAdminExist2");
     manager.addUser("initAdminExist1", VALID_PASSWORD);
     manager.addUser("initAdminExist2", VALID_PASSWORD);
 
@@ -240,7 +239,7 @@ public class TestIdpUserGroupManager {
 
   @Test
   public void 
testInitializeConfiguredServiceAdminsFailsWhenRequiredPasswordMissing() {
-    loadServiceAdminConfig(BASIC_AUTHENTICATOR_CLASS, "initAdminNoPwd1");
+    loadServiceAdminConfig(BASIC_AUTHENTICATOR, "initAdminNoPwd1");
 
     IllegalArgumentException exception =
         Assertions.assertThrows(
@@ -256,7 +255,7 @@ public class TestIdpUserGroupManager {
   @ParameterizedTest
   @ValueSource(strings = {"short"})
   public void 
testInitializeConfiguredServiceAdminsFailsOnInvalidPasswordPayload(String 
payload) {
-    loadServiceAdminConfig(BASIC_AUTHENTICATOR_CLASS, "initAdminBadPwd1");
+    loadServiceAdminConfig(BASIC_AUTHENTICATOR, "initAdminBadPwd1");
 
     IllegalArgumentException exception =
         Assertions.assertThrows(
@@ -271,7 +270,7 @@ public class TestIdpUserGroupManager {
   @Test
   public void 
testInitializeConfiguredServiceAdminsUsesSamePasswordForAllMissingServiceAdmins()
       throws IOException {
-    loadServiceAdminConfig(BASIC_AUTHENTICATOR_CLASS, 
"initAdminSame1,initAdminSame2");
+    loadServiceAdminConfig(BASIC_AUTHENTICATOR, 
"initAdminSame1,initAdminSame2");
 
     manager.initializeConfiguredServiceAdmins(config, VALID_PASSWORD);
 
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/auth/TestBasicAuthenticator.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/auth/TestBasicAuthenticator.java
index e31c9c8bb7..1431bdb50a 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/auth/TestBasicAuthenticator.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/auth/TestBasicAuthenticator.java
@@ -19,6 +19,7 @@
 
 package org.apache.gravitino.idp.auth;
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -26,19 +27,41 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import com.google.common.collect.Lists;
 import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Base64;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
 import org.apache.gravitino.UserPrincipal;
 import org.apache.gravitino.auth.AuthConstants;
 import org.apache.gravitino.exceptions.UnauthorizedException;
 import org.apache.gravitino.idp.IdpUserGroupManager;
 import org.apache.gravitino.idp.model.IdpUser;
+import org.apache.gravitino.idp.web.rest.feature.IdpRESTFeature;
 import org.junit.jupiter.api.Test;
 
 class TestBasicAuthenticator {
 
+  @Test
+  void testValidateExtensionPackageOk() {
+    Config config = new Config(false) {};
+    config.set(
+        Configs.REST_API_EXTENSION_PACKAGES,
+        Lists.newArrayList(IdpRESTFeature.IDP_REST_EXTENSION_PACKAGE));
+
+    assertDoesNotThrow(() -> 
BasicAuthenticator.validateExtensionPackage(config));
+  }
+
+  @Test
+  void testValidateExtensionPackageMissingFails() {
+    Config config = new Config(false) {};
+
+    assertThrows(
+        IllegalStateException.class, () -> 
BasicAuthenticator.validateExtensionPackage(config));
+  }
+
   @Test
   void testNotInitialized() {
     IllegalStateException exception =
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/BasicAuthOperationsIT.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/BasicAuthOperationsIT.java
index df16e56bf8..3ded05ef48 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/BasicAuthOperationsIT.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/BasicAuthOperationsIT.java
@@ -24,9 +24,9 @@ import com.google.common.collect.Maps;
 import java.io.IOException;
 import java.util.Map;
 import org.apache.gravitino.Configs;
+import org.apache.gravitino.auth.AuthenticatorType;
 import org.apache.gravitino.client.GravitinoAdminClient;
 import org.apache.gravitino.client.GravitinoVersion;
-import org.apache.gravitino.idp.auth.BasicAuthenticator;
 import org.apache.gravitino.idp.web.rest.feature.IdpRESTFeature;
 import org.apache.gravitino.integration.test.util.BaseIT;
 import org.apache.gravitino.integration.test.util.ITUtils;
@@ -53,7 +53,7 @@ public class BasicAuthOperationsIT extends BaseIT {
     configs.put(Configs.CACHE_ENABLED.getKey(), String.valueOf(false));
     configs.put(Configs.STORE_DELETE_AFTER_TIME.getKey(), String.valueOf(20 * 
60 * 1000L));
     configs.put(Configs.SERVICE_ADMINS.getKey(), ADMIN);
-    configs.put(Configs.AUTHENTICATORS.getKey(), 
BasicAuthenticator.class.getCanonicalName());
+    configs.put(Configs.AUTHENTICATORS.getKey(), 
AuthenticatorType.BASIC.name().toLowerCase());
     configs.put(
         Configs.REST_API_EXTENSION_PACKAGES.getKey(), 
IdpRESTFeature.IDP_REST_EXTENSION_PACKAGE);
     registerCustomConfigs(configs);
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
index 6bf9cbc75e..ca18bfcf6c 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
@@ -87,7 +87,11 @@ public class IdpRESTApiIT extends BaseIT {
     configs.put(Configs.CACHE_ENABLED.getKey(), String.valueOf(false));
     configs.put(Configs.STORE_DELETE_AFTER_TIME.getKey(), String.valueOf(20 * 
60 * 1000L));
     configs.put(Configs.SERVICE_ADMINS.getKey(), ADMIN);
-    configs.put(Configs.AUTHENTICATORS.getKey(), 
AuthenticatorType.OAUTH.name().toLowerCase());
+    configs.put(
+        Configs.AUTHENTICATORS.getKey(),
+        AuthenticatorType.BASIC.name().toLowerCase()
+            + ","
+            + AuthenticatorType.OAUTH.name().toLowerCase());
     configs.put(OAuthConfig.SERVICE_AUDIENCE.getKey(), "service1");
     configs.put(OAuthConfig.DEFAULT_SIGN_KEY.getKey(), oauthPublicSignKey());
     configs.put(OAuthConfig.DEFAULT_SERVER_URI.getKey(), "test");
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/rest/feature/TestIdpRESTFeature.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/rest/feature/TestIdpRESTFeature.java
index 7215361797..c596074900 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/rest/feature/TestIdpRESTFeature.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/rest/feature/TestIdpRESTFeature.java
@@ -27,72 +27,53 @@ import com.google.common.collect.Lists;
 import org.apache.gravitino.Config;
 import org.apache.gravitino.Configs;
 import org.apache.gravitino.auth.AuthenticatorType;
+import org.apache.gravitino.idp.SystemExitTestHelper;
+import org.apache.gravitino.idp.SystemExitTestHelper.SystemExitException;
 import org.junit.jupiter.api.Test;
 
 class TestIdpRESTFeature {
 
   @Test
-  void testSimpleFails() {
-    Config config = newConfig(AuthenticatorType.SIMPLE.name().toLowerCase());
+  void testSimpleWithBasicFails() {
+    Config config = new Config(false) {};
+    config.set(
+        Configs.AUTHENTICATORS,
+        Lists.newArrayList(
+            AuthenticatorType.SIMPLE.name().toLowerCase(),
+            AuthenticatorType.BASIC.name().toLowerCase()));
 
     SystemExitException exception =
-        assertThrows(SystemExitException.class, () -> 
validateWithExitGuard(config));
+        assertThrows(
+            SystemExitException.class,
+            () ->
+                SystemExitTestHelper.runWithExitGuard(
+                    () -> IdpRESTFeature.validateConfiguration(config)));
 
     assertEquals(1, exception.status());
   }
 
   @Test
-  void testDefaultSimpleFails() {
+  void testExtensionWithoutBasicFails() {
     Config config = new Config(false) {};
+    config.set(
+        Configs.AUTHENTICATORS, 
Lists.newArrayList(AuthenticatorType.OAUTH.name().toLowerCase()));
 
-    assertThrows(SystemExitException.class, () -> 
validateWithExitGuard(config));
-  }
-
-  @Test
-  void testOAuthOk() {
-    Config config = newConfig(AuthenticatorType.OAUTH.name().toLowerCase());
+    SystemExitException exception =
+        assertThrows(
+            SystemExitException.class,
+            () ->
+                SystemExitTestHelper.runWithExitGuard(
+                    () -> IdpRESTFeature.validateConfiguration(config)));
 
-    assertDoesNotThrow(() -> IdpRESTFeature.validateConfiguration(config));
+    assertEquals(1, exception.status());
   }
 
-  private static Config newConfig(String... authenticators) {
+  @Test
+  void testBasicOk() {
     Config config = new Config(false) {};
-    config.set(Configs.AUTHENTICATORS, Lists.newArrayList(authenticators));
-    return config;
-  }
-
-  @SuppressWarnings("removal")
-  private static void validateWithExitGuard(Config config) {
-    SecurityManager original = System.getSecurityManager();
-    System.setSecurityManager(
-        new SecurityManager() {
-          @Override
-          public void checkExit(int status) {
-            throw new SystemExitException(status);
-          }
-
-          @Override
-          public void checkPermission(java.security.Permission perm) {
-            // Allow test execution.
-          }
-        });
-    try {
-      IdpRESTFeature.validateConfiguration(config);
-    } finally {
-      System.setSecurityManager(original);
-    }
-  }
+    config.set(
+        Configs.AUTHENTICATORS, 
Lists.newArrayList(AuthenticatorType.BASIC.name().toLowerCase()));
 
-  private static final class SystemExitException extends SecurityException {
-    private final int status;
-
-    private SystemExitException(int status) {
-      super("System.exit(" + status + ")");
-      this.status = status;
-    }
-
-    private int status() {
-      return status;
-    }
+    assertDoesNotThrow(() -> IdpRESTFeature.validateConfiguration(config));
   }
 }
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticatorFactory.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticatorFactory.java
index 31d67cfac8..beaf52cf23 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticatorFactory.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticatorFactory.java
@@ -33,10 +33,16 @@ public class AuthenticatorFactory {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(AuthenticatorFactory.class);
 
+  /** Fully qualified class name of {@code BasicAuthenticator} in the 
idp-basic plugin. */
+  private static final String BASIC_AUTHENTICATOR_CLASS_NAME =
+      "org.apache.gravitino.idp.auth.BasicAuthenticator";
+
   public static final ImmutableMap<String, String> AUTHENTICATORS =
       ImmutableMap.of(
           AuthenticatorType.SIMPLE.name().toLowerCase(),
           SimpleAuthenticator.class.getCanonicalName(),
+          AuthenticatorType.BASIC.name().toLowerCase(),
+          BASIC_AUTHENTICATOR_CLASS_NAME,
           AuthenticatorType.OAUTH.name().toLowerCase(),
           OAuth2TokenAuthenticator.class.getCanonicalName(),
           AuthenticatorType.KERBEROS.name().toLowerCase(),


Reply via email to