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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new f0cc411202d branch-4.1: [feat](auth) support querying authentication 
integrations from system table (#61246 #61172 #60902) (#61517)
f0cc411202d is described below

commit f0cc411202d8a46bae69056db2fe21c7480ef63c
Author: Calvin Kirs <[email protected]>
AuthorDate: Thu Mar 19 21:46:51 2026 +0800

    branch-4.1: [feat](auth) support querying authentication integrations from 
system table (#61246 #61172 #60902) (#61517)
    
    [feat](auth) support querying authentication integrations from system
    table (#61246 #61172 #60902)
---
 be/src/exec/schema_scanner.cpp                     |   3 +
 .../schema_authentication_integrations_scanner.cpp | 150 +++++++++++++
 .../schema_authentication_integrations_scanner.h   |  55 +++++
 .../antlr4/org/apache/doris/nereids/DorisLexer.g4  |   2 +
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |  15 ++
 .../org/apache/doris/analysis/SchemaTableType.java |   4 +-
 .../AuthenticationIntegrationMeta.java             | 178 +++++++++++++++
 .../AuthenticationIntegrationMgr.java              | 206 +++++++++++++++++
 .../main/java/org/apache/doris/catalog/Env.java    |  27 ++-
 .../java/org/apache/doris/catalog/SchemaTable.java |  11 +
 .../org/apache/doris/common/UserAuditMetadata.java |  77 +++++++
 .../org/apache/doris/journal/JournalEntity.java    |  13 ++
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  77 +++++++
 .../parser/LogicalPlanBuilderForEncryption.java    |  25 +++
 .../apache/doris/nereids/trees/plans/PlanType.java |   3 +
 .../AlterAuthenticationIntegrationCommand.java     | 136 +++++++++++
 .../CreateAuthenticationIntegrationCommand.java    |  95 ++++++++
 .../DropAuthenticationIntegrationCommand.java      |  65 ++++++
 .../trees/plans/visitor/CommandVisitor.java        |  18 ++
 .../DropAuthenticationIntegrationOperationLog.java |  53 +++++
 .../java/org/apache/doris/persist/EditLog.java     |  29 +++
 .../org/apache/doris/persist/OperationType.java    |   3 +
 .../doris/persist/meta/MetaPersistMethod.java      |   9 +
 .../doris/persist/meta/PersistMetaModules.java     |   8 +-
 .../doris/tablefunction/MetadataGenerator.java     |  96 ++++++++
 .../AuthenticationIntegrationMetaTest.java         | 190 ++++++++++++++++
 .../AuthenticationIntegrationMgrTest.java          | 216 ++++++++++++++++++
 .../org/apache/doris/catalog/SchemaTableTest.java  |  10 +
 .../AuthenticationIntegrationParserTest.java       | 111 +++++++++
 .../AuthenticationIntegrationCommandTest.java      | 249 +++++++++++++++++++++
 ...pAuthenticationIntegrationOperationLogTest.java |  47 ++++
 .../doris/service/FrontendServiceImplTest.java     |  56 +++++
 gensrc/thrift/Descriptors.thrift                   |   1 +
 gensrc/thrift/FrontendService.thrift               |   1 +
 .../test_authentication_integration_auth.groovy    | 109 +++++++++
 35 files changed, 2344 insertions(+), 4 deletions(-)

diff --git a/be/src/exec/schema_scanner.cpp b/be/src/exec/schema_scanner.cpp
index ff7ee7e02ae..6f54ba591e1 100644
--- a/be/src/exec/schema_scanner.cpp
+++ b/be/src/exec/schema_scanner.cpp
@@ -27,6 +27,7 @@
 #include <utility>
 
 #include "exec/schema_scanner/schema_active_queries_scanner.h"
+#include "exec/schema_scanner/schema_authentication_integrations_scanner.h"
 #include "exec/schema_scanner/schema_backend_active_tasks.h"
 #include "exec/schema_scanner/schema_backend_configuration_scanner.h"
 #include "exec/schema_scanner/schema_backend_kerberos_ticket_cache.h"
@@ -259,6 +260,8 @@ std::unique_ptr<SchemaScanner> 
SchemaScanner::create(TSchemaTableType::type type
         return SchemaClusterSnapshotPropertiesScanner::create_unique();
     case TSchemaTableType::SCH_COLUMN_DATA_SIZES:
         return SchemaColumnDataSizesScanner::create_unique();
+    case TSchemaTableType::SCH_AUTHENTICATION_INTEGRATIONS:
+        return SchemaAuthenticationIntegrationsScanner::create_unique();
     default:
         return SchemaDummyScanner::create_unique();
         break;
diff --git 
a/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.cpp 
b/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.cpp
new file mode 100644
index 00000000000..7367b3aa99a
--- /dev/null
+++ b/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.cpp
@@ -0,0 +1,150 @@
+// 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.
+
+#include "exec/schema_scanner/schema_authentication_integrations_scanner.h"
+
+#include <utility>
+
+#include "exec/schema_scanner/schema_helper.h"
+#include "runtime/client_cache.h"
+#include "runtime/exec_env.h"
+#include "runtime/runtime_state.h"
+#include "util/thrift_rpc_helper.h"
+#include "vec/common/string_ref.h"
+#include "vec/core/block.h"
+#include "vec/core/column_with_type_and_name.h"
+#include "vec/data_types/data_type_factory.hpp"
+
+namespace doris {
+#include "common/compile_check_begin.h"
+
+std::vector<SchemaScanner::ColumnDesc> 
SchemaAuthenticationIntegrationsScanner::_s_tbls_columns = {
+        {"NAME", TYPE_VARCHAR, sizeof(StringRef), true},
+        {"TYPE", TYPE_VARCHAR, sizeof(StringRef), true},
+        {"PROPERTIES", TYPE_STRING, sizeof(StringRef), true},
+        {"COMMENT", TYPE_STRING, sizeof(StringRef), true},
+        {"CREATE_USER", TYPE_STRING, sizeof(StringRef), true},
+        {"CREATE_TIME", TYPE_STRING, sizeof(StringRef), true},
+        {"ALTER_USER", TYPE_STRING, sizeof(StringRef), true},
+        {"MODIFY_TIME", TYPE_STRING, sizeof(StringRef), true},
+};
+
+SchemaAuthenticationIntegrationsScanner::SchemaAuthenticationIntegrationsScanner()
+        : SchemaScanner(_s_tbls_columns, 
TSchemaTableType::SCH_AUTHENTICATION_INTEGRATIONS) {}
+
+Status SchemaAuthenticationIntegrationsScanner::start(RuntimeState* state) {
+    if (!_is_init) {
+        return Status::InternalError("used before initialized.");
+    }
+    _block_rows_limit = state->batch_size();
+    _rpc_timeout_ms = state->execution_timeout() * 1000;
+    return Status::OK();
+}
+
+Status 
SchemaAuthenticationIntegrationsScanner::_get_authentication_integrations_block_from_fe()
 {
+    TNetworkAddress master_addr = 
ExecEnv::GetInstance()->cluster_info()->master_fe_addr;
+
+    TSchemaTableRequestParams schema_table_request_params;
+    for (int i = 0; i < _s_tbls_columns.size(); i++) {
+        schema_table_request_params.__isset.columns_name = true;
+        
schema_table_request_params.columns_name.emplace_back(_s_tbls_columns[i].name);
+    }
+    
schema_table_request_params.__set_current_user_ident(*_param->common_param->current_user_ident);
+    if (_param->common_param->frontend_conjuncts) {
+        schema_table_request_params.__set_frontend_conjuncts(
+                *_param->common_param->frontend_conjuncts);
+    }
+
+    TFetchSchemaTableDataRequest request;
+    
request.__set_schema_table_name(TSchemaTableName::AUTHENTICATION_INTEGRATIONS);
+    request.__set_schema_table_params(schema_table_request_params);
+
+    TFetchSchemaTableDataResult result;
+    RETURN_IF_ERROR(ThriftRpcHelper::rpc<FrontendServiceClient>(
+            master_addr.hostname, master_addr.port,
+            [&request, &result](FrontendServiceConnection& client) {
+                client->fetchSchemaTableData(result, request);
+            },
+            _rpc_timeout_ms));
+
+    Status status(Status::create(result.status));
+    if (!status.ok()) {
+        LOG(WARNING) << "fetch authentication integrations from FE failed, 
errmsg=" << status;
+        return status;
+    }
+
+    _authentication_integrations_block = vectorized::Block::create_unique();
+    for (int i = 0; i < _s_tbls_columns.size(); ++i) {
+        auto data_type = 
vectorized::DataTypeFactory::instance().create_data_type(
+                _s_tbls_columns[i].type, true);
+        
_authentication_integrations_block->insert(vectorized::ColumnWithTypeAndName(
+                data_type->create_column(), data_type, 
_s_tbls_columns[i].name));
+    }
+    _authentication_integrations_block->reserve(_block_rows_limit);
+
+    std::vector<TRow> result_data = std::move(result.data_batch);
+    if (!result_data.empty()) {
+        auto col_size = result_data[0].column_value.size();
+        if (col_size != _s_tbls_columns.size()) {
+            return Status::InternalError<false>(
+                    "authentication integrations schema is not match for FE 
and BE");
+        }
+    }
+
+    for (int i = 0; i < result_data.size(); i++) {
+        const TRow& row = result_data[i];
+        for (int j = 0; j < _s_tbls_columns.size(); j++) {
+            RETURN_IF_ERROR(insert_block_column(row.column_value[j], j,
+                                                
_authentication_integrations_block.get(),
+                                                _s_tbls_columns[j].type));
+        }
+    }
+    return Status::OK();
+}
+
+Status 
SchemaAuthenticationIntegrationsScanner::get_next_block_internal(vectorized::Block*
 block,
+                                                                        bool* 
eos) {
+    if (!_is_init) {
+        return Status::InternalError("Used before initialized.");
+    }
+
+    if (nullptr == block || nullptr == eos) {
+        return Status::InternalError("input pointer is nullptr.");
+    }
+
+    if (_authentication_integrations_block == nullptr) {
+        RETURN_IF_ERROR(_get_authentication_integrations_block_from_fe());
+        _total_rows = 
static_cast<int>(_authentication_integrations_block->rows());
+    }
+
+    if (_row_idx == _total_rows) {
+        *eos = true;
+        return Status::OK();
+    }
+
+    int current_batch_rows = std::min(_block_rows_limit, _total_rows - 
_row_idx);
+    vectorized::MutableBlock mblock = 
vectorized::MutableBlock::build_mutable_block(block);
+    RETURN_IF_ERROR(mblock.add_rows(_authentication_integrations_block.get(), 
_row_idx,
+                                    current_batch_rows));
+    _row_idx += current_batch_rows;
+
+    *eos = _row_idx == _total_rows;
+    return Status::OK();
+}
+
+#include "common/compile_check_end.h"
+} // namespace doris
diff --git 
a/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.h 
b/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.h
new file mode 100644
index 00000000000..5563ac21f3e
--- /dev/null
+++ b/be/src/exec/schema_scanner/schema_authentication_integrations_scanner.h
@@ -0,0 +1,55 @@
+// 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.
+
+#pragma once
+
+#include <gen_cpp/FrontendService_types.h>
+
+#include <vector>
+
+#include "common/status.h"
+#include "exec/schema_scanner.h"
+
+namespace doris {
+class RuntimeState;
+namespace vectorized {
+class Block;
+} // namespace vectorized
+
+class SchemaAuthenticationIntegrationsScanner : public SchemaScanner {
+    ENABLE_FACTORY_CREATOR(SchemaAuthenticationIntegrationsScanner);
+
+public:
+    SchemaAuthenticationIntegrationsScanner();
+    ~SchemaAuthenticationIntegrationsScanner() override = default;
+
+    Status start(RuntimeState* state) override;
+    Status get_next_block_internal(vectorized::Block* block, bool* eos) 
override;
+
+    static std::vector<SchemaScanner::ColumnDesc> _s_tbls_columns;
+
+private:
+    Status _get_authentication_integrations_block_from_fe();
+
+    int _block_rows_limit = 4096;
+    int _row_idx = 0;
+    int _total_rows = 0;
+    int _rpc_timeout_ms = 3000;
+    std::unique_ptr<vectorized::Block> _authentication_integrations_block = 
nullptr;
+};
+
+} // namespace doris
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
index 81d36337d94..47ee4eea108 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
@@ -86,6 +86,7 @@ ASC: 'ASC';
 ASOF: 'ASOF';
 AT: 'AT';
 AUTHORS: 'AUTHORS';
+AUTHENTICATION: 'AUTHENTICATION';
 AUTO: 'AUTO';
 AUTO_INCREMENT: 'AUTO_INCREMENT';
 ALWAYS: 'ALWAYS';
@@ -296,6 +297,7 @@ IGNORE: 'IGNORE';
 IMMEDIATE: 'IMMEDIATE';
 IN: 'IN';
 INCREMENTAL: 'INCREMENTAL';
+INTEGRATION: 'INTEGRATION';
 INDEX: 'INDEX';
 INDEXES: 'INDEXES';
 INFILE: 'INFILE';
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index 23ab160196c..32cdb65a71e 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -212,6 +212,8 @@ supportedCreateStatement
         LIKE existedTable=multipartIdentifier
         (WITH ROLLUP (rollupNames=identifierList)?)?                      
#createTableLike
     | CREATE ROLE (IF NOT EXISTS)? name=identifierOrText (COMMENT 
STRING_LITERAL)?    #createRole
+    | CREATE AUTHENTICATION INTEGRATION (IF NOT EXISTS)? 
integrationName=identifier
+        properties=propertyClause commentSpec?                                 
 #createAuthenticationIntegration
     | CREATE WORKLOAD GROUP (IF NOT EXISTS)?
         name=identifierOrText (FOR computeGroup=identifierOrText)? 
properties=propertyClause? #createWorkloadGroup
     | CREATE CATALOG (IF NOT EXISTS)? catalogName=identifier
@@ -295,6 +297,12 @@ supportedAlterStatement
         properties=propertyClause?                                             
             #alterComputeGroup
     | ALTER CATALOG name=identifier SET PROPERTIES
         LEFT_PAREN propertyItemList RIGHT_PAREN                                
             #alterCatalogProperties        
+    | ALTER AUTHENTICATION INTEGRATION integrationName=identifier
+        SET properties=propertyClause                                          
             #alterAuthenticationIntegrationProperties
+    | ALTER AUTHENTICATION INTEGRATION integrationName=identifier
+        UNSET properties=propertyKeyClause                                     
             #alterAuthenticationIntegrationUnsetProperties
+    | ALTER AUTHENTICATION INTEGRATION integrationName=identifier
+        SET COMMENT comment=STRING_LITERAL                                     
              #alterAuthenticationIntegrationComment
     | ALTER WORKLOAD POLICY name=identifierOrText
         properties=propertyClause?                                             
             #alterWorkloadPolicy
     | ALTER SQL_BLOCK_RULE name=identifier properties=propertyClause?          
             #alterSqlBlockRule
@@ -338,6 +346,7 @@ supportedDropStatement
     | DROP STORAGE POLICY (IF EXISTS)? name=identifier                         
 #dropStoragePolicy
     | DROP WORKLOAD GROUP (IF EXISTS)? name=identifierOrText (FOR 
computeGroup=identifierOrText)?                    #dropWorkloadGroup
     | DROP CATALOG (IF EXISTS)? name=identifier                                
 #dropCatalog
+    | DROP AUTHENTICATION INTEGRATION (IF EXISTS)? name=identifier             
 #dropAuthenticationIntegration
     | DROP FILE name=STRING_LITERAL
         ((FROM | IN) database=identifier)? properties=propertyClause           
 #dropFile
     | DROP WORKLOAD POLICY (IF EXISTS)? name=identifierOrText                  
 #dropWorkloadPolicy
@@ -1477,6 +1486,10 @@ propertyClause
     : PROPERTIES LEFT_PAREN fileProperties=propertyItemList RIGHT_PAREN
     ;
 
+propertyKeyClause
+    : PROPERTIES LEFT_PAREN keys+=propertyKey (COMMA keys+=propertyKey)* 
RIGHT_PAREN
+    ;
+
 propertyItemList
     : properties+=propertyItem (COMMA properties+=propertyItem)*
     ;
@@ -1973,6 +1986,7 @@ nonReserved
     | ARRAY
     | AT
     | AUTHORS
+    | AUTHENTICATION
     | AUTO_INCREMENT
     | BACKENDS
     | BACKUP
@@ -2126,6 +2140,7 @@ nonReserved
     | IGNORE
     | IMMEDIATE
     | INCREMENTAL
+    | INTEGRATION
     | INDEXES
     | INSERT
     | INVERTED
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
index 5576c6d294b..c5beb18953c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
@@ -110,7 +110,9 @@ public enum SchemaTableType {
     SCH_BLACKHOLE("BLACKHOLE", "BLACKHOLE",
             TSchemaTableType.SCH_BLACKHOLE),
     SCH_COLUMN_DATA_SIZES("COLUMN_DATA_SIZES", "COLUMN_DATA_SIZES",
-            TSchemaTableType.SCH_COLUMN_DATA_SIZES);
+            TSchemaTableType.SCH_COLUMN_DATA_SIZES),
+    SCH_AUTHENTICATION_INTEGRATIONS("AUTHENTICATION_INTEGRATIONS", 
"AUTHENTICATION_INTEGRATIONS",
+            TSchemaTableType.SCH_AUTHENTICATION_INTEGRATIONS);
 
     private static final String dbName = "INFORMATION_SCHEMA";
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java
 
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java
new file mode 100644
index 00000000000..a57e3d3ced4
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMeta.java
@@ -0,0 +1,178 @@
+// 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.doris.authentication;
+
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.UserAuditMetadata;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Persistent metadata for AUTHENTICATION INTEGRATION.
+ */
+public class AuthenticationIntegrationMeta extends UserAuditMetadata 
implements Writable {
+    public static final String TYPE_PROPERTY = "type";
+
+    @SerializedName(value = "n")
+    private String name;
+    @SerializedName(value = "t")
+    private String type;
+    @SerializedName(value = "p")
+    private Map<String, String> properties;
+    @SerializedName(value = "c")
+    private String comment;
+
+    private AuthenticationIntegrationMeta() {
+        super();
+        this.name = "";
+        this.type = "";
+        this.properties = Collections.emptyMap();
+        this.comment = null;
+    }
+
+    public AuthenticationIntegrationMeta(String name, String type, Map<String, 
String> properties, String comment,
+            String createUser, long createTime, String alterUser, long 
modifyTime) {
+        super(createUser, createTime, alterUser, modifyTime);
+        this.name = Objects.requireNonNull(name, "name can not be null");
+        this.type = Objects.requireNonNull(type, "type can not be null");
+        this.properties = Collections.unmodifiableMap(
+                new LinkedHashMap<>(Objects.requireNonNull(properties, 
"properties can not be null")));
+        this.comment = comment;
+    }
+
+    /**
+     * Build metadata from CREATE SQL arguments.
+     */
+    public static AuthenticationIntegrationMeta fromCreateSql(
+            String integrationName, Map<String, String> properties, String 
comment, String createUser)
+            throws DdlException {
+        if (properties == null || properties.isEmpty()) {
+            throw new DdlException("Property 'type' is required in CREATE 
AUTHENTICATION INTEGRATION");
+        }
+        String type = null;
+        Map<String, String> copiedProperties = new LinkedHashMap<>();
+        for (Map.Entry<String, String> entry : properties.entrySet()) {
+            String key = Objects.requireNonNull(entry.getKey(), "property key 
can not be null");
+            if (TYPE_PROPERTY.equalsIgnoreCase(key)) {
+                if (type != null) {
+                    throw new DdlException("Property 'type' is duplicated in 
CREATE AUTHENTICATION INTEGRATION");
+                }
+                type = entry.getValue();
+                continue;
+            }
+            copiedProperties.put(key, entry.getValue());
+        }
+        if (type == null || type.isEmpty()) {
+            throw new DdlException("Property 'type' is required in CREATE 
AUTHENTICATION INTEGRATION");
+        }
+        long currentTime = System.currentTimeMillis();
+        return new AuthenticationIntegrationMeta(integrationName, type, 
copiedProperties, comment,
+                Objects.requireNonNull(createUser, "createUser can not be 
null"),
+                currentTime, createUser, currentTime);
+    }
+
+    /**
+     * Build a new metadata object after ALTER ... SET PROPERTIES.
+     */
+    public AuthenticationIntegrationMeta withAlterProperties(Map<String, 
String> propertiesDelta, String alterUser)
+            throws DdlException {
+        if (propertiesDelta == null || propertiesDelta.isEmpty()) {
+            throw new DdlException("ALTER AUTHENTICATION INTEGRATION should 
contain at least one property");
+        }
+        for (String key : propertiesDelta.keySet()) {
+            if (TYPE_PROPERTY.equalsIgnoreCase(key)) {
+                throw new DdlException("ALTER AUTHENTICATION INTEGRATION does 
not allow modifying property 'type'");
+            }
+        }
+        Map<String, String> mergedProperties = new LinkedHashMap<>(properties);
+        mergedProperties.putAll(propertiesDelta);
+        return new AuthenticationIntegrationMeta(name, type, mergedProperties, 
comment,
+                getCreateUser(), getCreateTime(),
+                Objects.requireNonNull(alterUser, "alterUser can not be 
null"), System.currentTimeMillis());
+    }
+
+    /**
+     * Build a new metadata object after ALTER ... UNSET PROPERTIES.
+     */
+    public AuthenticationIntegrationMeta withUnsetProperties(Set<String> 
propertiesToUnset, String alterUser)
+            throws DdlException {
+        if (propertiesToUnset == null || propertiesToUnset.isEmpty()) {
+            throw new DdlException("ALTER AUTHENTICATION INTEGRATION should 
contain at least one property");
+        }
+        Map<String, String> reducedProperties = new 
LinkedHashMap<>(properties);
+        for (String key : propertiesToUnset) {
+            if (TYPE_PROPERTY.equalsIgnoreCase(key)) {
+                throw new DdlException("ALTER AUTHENTICATION INTEGRATION does 
not allow modifying property 'type'");
+            }
+            reducedProperties.remove(key);
+        }
+        return new AuthenticationIntegrationMeta(name, type, 
reducedProperties, comment,
+                getCreateUser(), getCreateTime(),
+                Objects.requireNonNull(alterUser, "alterUser can not be 
null"), System.currentTimeMillis());
+    }
+
+    public AuthenticationIntegrationMeta withComment(String newComment, String 
alterUser) {
+        return new AuthenticationIntegrationMeta(name, type, properties, 
newComment,
+                getCreateUser(), getCreateTime(),
+                Objects.requireNonNull(alterUser, "alterUser can not be 
null"), System.currentTimeMillis());
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public Map<String, String> toSqlPropertiesView() {
+        Map<String, String> allProperties = new LinkedHashMap<>();
+        allProperties.put(TYPE_PROPERTY, type);
+        allProperties.putAll(properties);
+        return allProperties;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        Text.writeString(out, GsonUtils.GSON.toJson(this));
+    }
+
+    public static AuthenticationIntegrationMeta read(DataInput in) throws 
IOException {
+        return GsonUtils.GSON.fromJson(Text.readString(in), 
AuthenticationIntegrationMeta.class);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.java
 
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.java
new file mode 100644
index 00000000000..487637056b8
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/authentication/AuthenticationIntegrationMgr.java
@@ -0,0 +1,206 @@
+// 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.doris.authentication;
+
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.DropAuthenticationIntegrationOperationLog;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Manager for AUTHENTICATION INTEGRATION metadata.
+ */
+public class AuthenticationIntegrationMgr implements Writable {
+    private final ReentrantReadWriteLock lock = new 
ReentrantReadWriteLock(true);
+
+    @SerializedName(value = "nTi")
+    private Map<String, AuthenticationIntegrationMeta> nameToIntegration = new 
LinkedHashMap<>();
+
+    private void readLock() {
+        lock.readLock().lock();
+    }
+
+    private void readUnlock() {
+        lock.readLock().unlock();
+    }
+
+    private void writeLock() {
+        lock.writeLock().lock();
+    }
+
+    private void writeUnlock() {
+        lock.writeLock().unlock();
+    }
+
+    public void createAuthenticationIntegration(
+            String integrationName, boolean ifNotExists, Map<String, String> 
properties, String comment,
+            String createUser)
+            throws DdlException {
+        AuthenticationIntegrationMeta meta =
+                AuthenticationIntegrationMeta.fromCreateSql(integrationName, 
properties, comment, createUser);
+        writeLock();
+        try {
+            if (nameToIntegration.containsKey(integrationName)) {
+                if (ifNotExists) {
+                    return;
+                }
+                throw new DdlException("Authentication integration " + 
integrationName + " already exists");
+            }
+            nameToIntegration.put(integrationName, meta);
+            // TODO(authentication-integration): Re-enable edit log persistence
+            // when authentication integration is fully integrated.
+            // 
Env.getCurrentEnv().getEditLog().logCreateAuthenticationIntegration(meta);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public void alterAuthenticationIntegrationProperties(
+            String integrationName, Map<String, String> properties, String 
alterUser) throws DdlException {
+        writeLock();
+        try {
+            AuthenticationIntegrationMeta current = 
getOrThrow(integrationName);
+            AuthenticationIntegrationMeta updated = 
current.withAlterProperties(properties, alterUser);
+            nameToIntegration.put(integrationName, updated);
+            // TODO(authentication-integration): Re-enable edit log persistence
+            // when authentication integration is fully integrated.
+            // 
Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public void alterAuthenticationIntegrationUnsetProperties(
+            String integrationName, Set<String> propertiesToUnset, String 
alterUser) throws DdlException {
+        writeLock();
+        try {
+            AuthenticationIntegrationMeta current = 
getOrThrow(integrationName);
+            AuthenticationIntegrationMeta updated = 
current.withUnsetProperties(propertiesToUnset, alterUser);
+            nameToIntegration.put(integrationName, updated);
+            // TODO(authentication-integration): Re-enable edit log persistence
+            // when authentication integration is fully integrated.
+            // 
Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public void alterAuthenticationIntegrationComment(String integrationName, 
String comment, String alterUser)
+            throws DdlException {
+        writeLock();
+        try {
+            AuthenticationIntegrationMeta current = 
getOrThrow(integrationName);
+            AuthenticationIntegrationMeta updated = 
current.withComment(comment, alterUser);
+            nameToIntegration.put(integrationName, updated);
+            // TODO(authentication-integration): Re-enable edit log persistence
+            // when authentication integration is fully integrated.
+            // 
Env.getCurrentEnv().getEditLog().logAlterAuthenticationIntegration(updated);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public void dropAuthenticationIntegration(String integrationName, boolean 
ifExists) throws DdlException {
+        writeLock();
+        try {
+            if (!nameToIntegration.containsKey(integrationName)) {
+                if (ifExists) {
+                    return;
+                }
+                throw new DdlException("Authentication integration " + 
integrationName + " does not exist");
+            }
+            nameToIntegration.remove(integrationName);
+            // TODO(authentication-integration): Re-enable edit log persistence
+            // when authentication integration is fully integrated.
+            // 
Env.getCurrentEnv().getEditLog().logDropAuthenticationIntegration(
+            //         new 
DropAuthenticationIntegrationOperationLog(integrationName));
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public void 
replayCreateAuthenticationIntegration(AuthenticationIntegrationMeta meta) {
+        writeLock();
+        try {
+            nameToIntegration.put(meta.getName(), meta);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public void 
replayAlterAuthenticationIntegration(AuthenticationIntegrationMeta meta) {
+        writeLock();
+        try {
+            nameToIntegration.put(meta.getName(), meta);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public void 
replayDropAuthenticationIntegration(DropAuthenticationIntegrationOperationLog 
log) {
+        writeLock();
+        try {
+            nameToIntegration.remove(log.getIntegrationName());
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    public Map<String, AuthenticationIntegrationMeta> 
getAuthenticationIntegrations() {
+        readLock();
+        try {
+            return Collections.unmodifiableMap(new 
LinkedHashMap<>(nameToIntegration));
+        } finally {
+            readUnlock();
+        }
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        Text.writeString(out, GsonUtils.GSON.toJson(this));
+    }
+
+    public static AuthenticationIntegrationMgr read(DataInput in) throws 
IOException {
+        String json = Text.readString(in);
+        AuthenticationIntegrationMgr mgr = GsonUtils.GSON.fromJson(json, 
AuthenticationIntegrationMgr.class);
+        if (mgr.nameToIntegration == null) {
+            mgr.nameToIntegration = new LinkedHashMap<>();
+        }
+        return mgr;
+    }
+
+    private AuthenticationIntegrationMeta getOrThrow(String integrationName) 
throws DdlException {
+        AuthenticationIntegrationMeta meta = 
nameToIntegration.get(integrationName);
+        if (meta == null) {
+            throw new DdlException("Authentication integration " + 
integrationName + " does not exist");
+        }
+        return meta;
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 377bedf1eed..961da675754 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -37,6 +37,7 @@ import org.apache.doris.analysis.ReplacePartitionClause;
 import org.apache.doris.analysis.RollupRenameClause;
 import org.apache.doris.analysis.SlotRef;
 import org.apache.doris.analysis.TableRenameClause;
+import org.apache.doris.authentication.AuthenticationIntegrationMgr;
 import org.apache.doris.backup.BackupHandler;
 import org.apache.doris.backup.RestoreJob;
 import org.apache.doris.binlog.BinlogGcer;
@@ -374,6 +375,7 @@ public class Env {
     private RoutineLoadManager routineLoadManager;
     private GroupCommitManager groupCommitManager;
     private SqlBlockRuleMgr sqlBlockRuleMgr;
+    private AuthenticationIntegrationMgr authenticationIntegrationMgr;
     private ExportMgr exportMgr;
     private Alter alter;
     private ConsistencyChecker consistencyChecker;
@@ -705,6 +707,7 @@ public class Env {
         this.routineLoadManager = 
EnvFactory.getInstance().createRoutineLoadManager();
         this.groupCommitManager = new GroupCommitManager();
         this.sqlBlockRuleMgr = new SqlBlockRuleMgr();
+        this.authenticationIntegrationMgr = new AuthenticationIntegrationMgr();
         this.exportMgr = new ExportMgr();
         this.alter = new Alter();
         this.consistencyChecker = new ConsistencyChecker();
@@ -2471,6 +2474,17 @@ public class Env {
         return checksum;
     }
 
+    public long loadAuthenticationIntegrations(DataInputStream in, long 
checksum) throws IOException {
+        // TODO(authentication-integration): Re-enable image persistence
+        // when authentication integration is fully integrated.
+        // Consume persisted bytes to keep image stream alignment,
+        // but do not restore into in-memory state for now.
+        AuthenticationIntegrationMgr.read(in);
+        authenticationIntegrationMgr = new AuthenticationIntegrationMgr();
+        LOG.info("skip replay authentication integrations from image 
temporarily");
+        return checksum;
+    }
+
     /**
      * Load policy through file.
      **/
@@ -2784,6 +2798,14 @@ public class Env {
         return checksum;
     }
 
+    public long saveAuthenticationIntegrations(CountingDataOutputStream out, 
long checksum) throws IOException {
+        // TODO(authentication-integration): Re-enable image persistence
+        // when authentication integration is fully integrated.
+        // Persist an empty manager temporarily.
+        new AuthenticationIntegrationMgr().write(out);
+        return checksum;
+    }
+
     public long savePolicy(CountingDataOutputStream out, long checksum) throws 
IOException {
         Env.getCurrentEnv().getPolicyMgr().write(out);
         return checksum;
@@ -5133,6 +5155,10 @@ public class Env {
         return sqlBlockRuleMgr;
     }
 
+    public AuthenticationIntegrationMgr getAuthenticationIntegrationMgr() {
+        return authenticationIntegrationMgr;
+    }
+
     public RoutineLoadTaskScheduler getRoutineLoadTaskScheduler() {
         return routineLoadTaskScheduler;
     }
@@ -7438,4 +7464,3 @@ public class Env {
 
     protected void cloneClusterSnapshot() throws Exception {}
 }
-
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
index edebeff57d1..17003808040 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
@@ -792,6 +792,17 @@ public class SchemaTable extends Table {
                             .column("MAX_RESERVED_SNAPSHOTS", 
ScalarType.createType(PrimitiveType.BIGINT))
                             .column("SNAPSHOT_INTERVAL_SECONDS", 
ScalarType.createType(PrimitiveType.BIGINT))
                             .build()))
+            .put("authentication_integrations",
+                    new SchemaTable(SystemIdGenerator.getNextId(), 
"authentication_integrations", TableType.SCHEMA,
+                        builder().column("NAME", ScalarType.createVarchar(256))
+                            .column("TYPE", ScalarType.createVarchar(64))
+                            .column("PROPERTIES", 
ScalarType.createStringType())
+                            .column("COMMENT", ScalarType.createStringType())
+                            .column("CREATE_USER", 
ScalarType.createStringType())
+                            .column("CREATE_TIME", 
ScalarType.createStringType())
+                            .column("ALTER_USER", 
ScalarType.createStringType())
+                            .column("MODIFY_TIME", 
ScalarType.createStringType())
+                            .build()))
             .build();
 
     private boolean fetchAllFe = false;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/UserAuditMetadata.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/UserAuditMetadata.java
new file mode 100644
index 00000000000..d473068eeb8
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/UserAuditMetadata.java
@@ -0,0 +1,77 @@
+// 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.doris.common;
+
+import org.apache.doris.common.util.TimeUtils;
+
+import com.google.common.base.Preconditions;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Objects;
+
+/**
+ * Common persisted audit metadata for user-driven DDL objects.
+ * Time is stored as epoch millis and formatted lazily with Doris/session 
timezone when needed.
+ */
+public abstract class UserAuditMetadata {
+    @SerializedName(value = "cu")
+    private String createUser;
+    @SerializedName(value = "ct")
+    private long createTime;
+    @SerializedName(value = "au")
+    private String alterUser;
+    @SerializedName(value = "mt")
+    private long modifyTime;
+
+    protected UserAuditMetadata() {
+    }
+
+    protected UserAuditMetadata(String createUser, long createTime, String 
alterUser, long modifyTime) {
+        this.createUser = Objects.requireNonNull(createUser, "createUser can 
not be null");
+        this.alterUser = Objects.requireNonNull(alterUser, "alterUser can not 
be null");
+        Preconditions.checkArgument(createTime > 0, "createTime must be 
positive");
+        Preconditions.checkArgument(modifyTime > 0, "modifyTime must be 
positive");
+        Preconditions.checkArgument(modifyTime >= createTime, "modifyTime can 
not be earlier than createTime");
+        this.createTime = createTime;
+        this.modifyTime = modifyTime;
+    }
+
+    public String getCreateUser() {
+        return createUser;
+    }
+
+    public long getCreateTime() {
+        return createTime;
+    }
+
+    public String getAlterUser() {
+        return alterUser;
+    }
+
+    public long getModifyTime() {
+        return modifyTime;
+    }
+
+    public String getCreateTimeString() {
+        return TimeUtils.longToTimeString(createTime);
+    }
+
+    public String getModifyTimeString() {
+        return TimeUtils.longToTimeString(modifyTime);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java 
b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
index cb6dee000de..5b36fcb98ee 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
@@ -21,6 +21,7 @@ import org.apache.doris.alter.AlterJobV2;
 import org.apache.doris.alter.BatchAlterJobPersistInfo;
 import org.apache.doris.alter.IndexChangeJob;
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.authentication.AuthenticationIntegrationMeta;
 import org.apache.doris.backup.BackupJob;
 import org.apache.doris.backup.Repository;
 import org.apache.doris.backup.RestoreJob;
@@ -89,6 +90,7 @@ import org.apache.doris.persist.CreateTableInfo;
 import org.apache.doris.persist.DatabaseInfo;
 import org.apache.doris.persist.DictionaryDecreaseVersionInfo;
 import org.apache.doris.persist.DictionaryIncreaseVersionInfo;
+import org.apache.doris.persist.DropAuthenticationIntegrationOperationLog;
 import org.apache.doris.persist.DropDbInfo;
 import org.apache.doris.persist.DropDictionaryPersistInfo;
 import org.apache.doris.persist.DropInfo;
@@ -710,6 +712,17 @@ public class JournalEntity implements Writable {
                 isRead = true;
                 break;
             }
+            case OperationType.OP_CREATE_AUTHENTICATION_INTEGRATION:
+            case OperationType.OP_ALTER_AUTHENTICATION_INTEGRATION: {
+                data = AuthenticationIntegrationMeta.read(in);
+                isRead = true;
+                break;
+            }
+            case OperationType.OP_DROP_AUTHENTICATION_INTEGRATION: {
+                data = DropAuthenticationIntegrationOperationLog.read(in);
+                isRead = true;
+                break;
+            }
             case OperationType.OP_MODIFY_TABLE_ENGINE: {
                 data = ModifyTableEngineOperationLog.read(in);
                 isRead = true;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 7d4d972860e..dde35800b85 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -643,6 +643,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.AdminSetPartitionVersionCom
 import 
org.apache.doris.nereids.trees.plans.commands.AdminSetReplicaStatusCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AdminSetReplicaVersionCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AdminSetTableStatusCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.AlterAuthenticationIntegrationCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AlterCatalogCommentCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AlterCatalogPropertiesCommand;
 import org.apache.doris.nereids.trees.plans.commands.AlterCatalogRenameCommand;
@@ -684,6 +685,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.CleanQueryStatsCommand;
 import org.apache.doris.nereids.trees.plans.commands.Command;
 import org.apache.doris.nereids.trees.plans.commands.Constraint;
 import org.apache.doris.nereids.trees.plans.commands.CopyIntoCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.CreateAuthenticationIntegrationCommand;
 import org.apache.doris.nereids.trees.plans.commands.CreateCatalogCommand;
 import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand;
 import org.apache.doris.nereids.trees.plans.commands.CreateDictionaryCommand;
@@ -716,6 +718,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.DeleteFromCommand;
 import org.apache.doris.nereids.trees.plans.commands.DeleteFromUsingCommand;
 import org.apache.doris.nereids.trees.plans.commands.DescribeCommand;
 import org.apache.doris.nereids.trees.plans.commands.DropAnalyzeJobCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.DropAuthenticationIntegrationCommand;
 import org.apache.doris.nereids.trees.plans.commands.DropCachedStatsCommand;
 import org.apache.doris.nereids.trees.plans.commands.DropCatalogCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.DropCatalogRecycleBinCommand;
@@ -2129,6 +2132,18 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return propertiesMap.build();
     }
 
+    @Override
+    public Set<String> 
visitPropertyKeyClause(DorisParser.PropertyKeyClauseContext ctx) {
+        if (ctx == null || ctx.keys == null) {
+            return ImmutableSet.of();
+        }
+        ImmutableSet.Builder<String> propertyKeys = ImmutableSet.builder();
+        for (PropertyKeyContext propertyKey : ctx.keys) {
+            propertyKeys.add(parsePropertyKey(propertyKey));
+        }
+        return propertyKeys.build();
+    }
+
     @Override
     public BrokerDesc 
visitWithRemoteStorageSystem(WithRemoteStorageSystemContext ctx) {
         BrokerDesc brokerDesc = null;
@@ -5081,6 +5096,15 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return item.getText();
     }
 
+    private boolean containsPropertyKeyIgnoreCase(Iterable<String> 
propertyKeys, String expectedKey) {
+        for (String key : propertyKeys) {
+            if (key.equalsIgnoreCase(expectedKey)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private ExplainLevel parseExplainPlanType(PlanTypeContext planTypeContext) 
{
         if (planTypeContext == null || planTypeContext.ALL() != null) {
             return ExplainLevel.ALL_PLAN;
@@ -7133,6 +7157,19 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new CreateCatalogCommand(catalogName, ifNotExists, 
resourceName, comment, properties);
     }
 
+    @Override
+    public LogicalPlan visitCreateAuthenticationIntegration(
+            DorisParser.CreateAuthenticationIntegrationContext ctx) {
+        boolean ifNotExists = ctx.IF() != null;
+        String integrationName = stripQuotes(ctx.integrationName.getText());
+        Map<String, String> properties = 
Maps.newHashMap(visitPropertyClause(ctx.properties));
+        if (!containsPropertyKeyIgnoreCase(properties.keySet(), "type")) {
+            throw new ParseException("Property 'type' is required in CREATE 
AUTHENTICATION INTEGRATION", ctx);
+        }
+        String comment = ctx.commentSpec() == null ? null : 
stripQuotes(ctx.commentSpec().STRING_LITERAL().getText());
+        return new CreateAuthenticationIntegrationCommand(integrationName, 
ifNotExists, properties, comment);
+    }
+
     @Override
     public LogicalPlan visitShowStages(ShowStagesContext ctx) {
         return new ShowStagesCommand();
@@ -7169,6 +7206,38 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new AlterCatalogPropertiesCommand(catalogName, properties);
     }
 
+    @Override
+    public LogicalPlan visitAlterAuthenticationIntegrationProperties(
+            DorisParser.AlterAuthenticationIntegrationPropertiesContext ctx) {
+        String integrationName = stripQuotes(ctx.integrationName.getText());
+        Map<String, String> properties = 
Maps.newHashMap(visitPropertyClause(ctx.properties));
+        if (containsPropertyKeyIgnoreCase(properties.keySet(), "type")) {
+            throw new ParseException(
+                    "ALTER AUTHENTICATION INTEGRATION does not allow modifying 
property 'type'", ctx);
+        }
+        return 
AlterAuthenticationIntegrationCommand.forSetProperties(integrationName, 
properties);
+    }
+
+    @Override
+    public LogicalPlan visitAlterAuthenticationIntegrationUnsetProperties(
+            DorisParser.AlterAuthenticationIntegrationUnsetPropertiesContext 
ctx) {
+        String integrationName = stripQuotes(ctx.integrationName.getText());
+        Set<String> unsetProperties = visitPropertyKeyClause(ctx.properties);
+        if (containsPropertyKeyIgnoreCase(unsetProperties, "type")) {
+            throw new ParseException(
+                    "ALTER AUTHENTICATION INTEGRATION does not allow modifying 
property 'type'", ctx);
+        }
+        return 
AlterAuthenticationIntegrationCommand.forUnsetProperties(integrationName, 
unsetProperties);
+    }
+
+    @Override
+    public LogicalPlan visitAlterAuthenticationIntegrationComment(
+            DorisParser.AlterAuthenticationIntegrationCommentContext ctx) {
+        String integrationName = stripQuotes(ctx.integrationName.getText());
+        String comment = stripQuotes(ctx.comment.getText());
+        return 
AlterAuthenticationIntegrationCommand.forSetComment(integrationName, comment);
+    }
+
     @Override
     public RecoverTableCommand visitRecoverTable(RecoverTableContext ctx) {
         List<String> dbTblNameParts = visitMultipartIdentifier(ctx.name);
@@ -7287,6 +7356,14 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new DropCatalogCommand(catalogName, ifExists);
     }
 
+    @Override
+    public LogicalPlan visitDropAuthenticationIntegration(
+            DorisParser.DropAuthenticationIntegrationContext ctx) {
+        String integrationName = stripQuotes(ctx.name.getText());
+        boolean ifExists = ctx.EXISTS() != null;
+        return new DropAuthenticationIntegrationCommand(ifExists, 
integrationName);
+    }
+
     @Override
     public LogicalPlan visitCreateEncryptkey(CreateEncryptkeyContext ctx) {
         List<String> nameParts = 
visitMultipartIdentifier(ctx.multipartIdentifier());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
index 3c9eea1d386..0555ea108dc 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
@@ -132,6 +132,18 @@ public class LogicalPlanBuilderForEncryption extends 
LogicalPlanBuilder {
         return super.visitCreateStorageVault(ctx);
     }
 
+    // create authentication integration clause
+    @Override
+    public LogicalPlan 
visitCreateAuthenticationIntegration(DorisParser.CreateAuthenticationIntegrationContext
 ctx) {
+        if (ctx.properties != null && ctx.properties.fileProperties != null) {
+            DorisParser.PropertyClauseContext propertyClauseContext = 
ctx.properties;
+            encryptProperty(visitPropertyClause(propertyClauseContext),
+                    propertyClauseContext.fileProperties.start.getStartIndex(),
+                    propertyClauseContext.fileProperties.stop.getStopIndex());
+        }
+        return super.visitCreateAuthenticationIntegration(ctx);
+    }
+
     // alter storage vault clause
     @Override
     public LogicalPlan 
visitAlterStorageVault(DorisParser.AlterStorageVaultContext ctx) {
@@ -144,6 +156,19 @@ public class LogicalPlanBuilderForEncryption extends 
LogicalPlanBuilder {
         return super.visitAlterStorageVault(ctx);
     }
 
+    // alter authentication integration properties clause
+    @Override
+    public LogicalPlan visitAlterAuthenticationIntegrationProperties(
+            DorisParser.AlterAuthenticationIntegrationPropertiesContext ctx) {
+        if (ctx.properties != null && ctx.properties.fileProperties != null) {
+            DorisParser.PropertyClauseContext propertyClauseContext = 
ctx.properties;
+            encryptProperty(visitPropertyClause(propertyClauseContext),
+                    propertyClauseContext.fileProperties.start.getStartIndex(),
+                    propertyClauseContext.fileProperties.stop.getStopIndex());
+        }
+        return super.visitAlterAuthenticationIntegrationProperties(ctx);
+    }
+
     // select from tvf
     @Override
     public LogicalPlan 
visitTableValuedFunction(DorisParser.TableValuedFunctionContext ctx) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
index 9271e7354d5..3c0ee3371a0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
@@ -152,6 +152,7 @@ public enum PlanType {
     COPY_INTO_COMMAND,
     CREATE_POLICY_COMMAND,
     CREATE_TABLE_COMMAND,
+    CREATE_AUTHENTICATION_INTEGRATION_COMMAND,
     CREATE_DICTIONARY_COMMAND,
     DROP_DICTIONARY_COMMAND,
     CREATE_SQL_BLOCK_RULE_COMMAND,
@@ -180,10 +181,12 @@ public enum PlanType {
     CANCEL_JOB_COMMAND,
     DROP_CATALOG_COMMAND,
     DROP_DATABASE_COMMAND,
+    DROP_AUTHENTICATION_INTEGRATION_COMMAND,
     DROP_JOB_COMMAND,
     RESUME_JOB_COMMAND,
     ALTER_MTMV_COMMAND,
     ALTER_CATALOG_PROPERTIES_COMMAND,
+    ALTER_AUTHENTICATION_INTEGRATION_COMMAND,
     ADD_CONSTRAINT_COMMAND,
     ADMIN_COMPACT_TABLE_COMMAND,
     DROP_CONSTRAINT_COMMAND,
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java
new file mode 100644
index 00000000000..0a8ef9c1868
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterAuthenticationIntegrationCommand.java
@@ -0,0 +1,136 @@
+// 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.doris.nereids.trees.plans.commands;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ALTER AUTHENTICATION INTEGRATION command entry.
+ */
+public class AlterAuthenticationIntegrationCommand extends AlterCommand 
implements NeedAuditEncryption {
+    /** alter action. */
+    public enum AlterType {
+        SET_PROPERTIES,
+        UNSET_PROPERTIES,
+        SET_COMMENT
+    }
+
+    private final String integrationName;
+    private final AlterType alterType;
+    private final Map<String, String> properties;
+    private final Set<String> unsetProperties;
+    private final String comment;
+
+    private AlterAuthenticationIntegrationCommand(String integrationName, 
AlterType alterType,
+            Map<String, String> properties, Set<String> unsetProperties, 
String comment) {
+        super(PlanType.ALTER_AUTHENTICATION_INTEGRATION_COMMAND);
+        this.integrationName = Objects.requireNonNull(integrationName, 
"integrationName can not be null");
+        this.alterType = Objects.requireNonNull(alterType, "alterType can not 
be null");
+        this.properties = Collections.unmodifiableMap(
+                new LinkedHashMap<>(Objects.requireNonNull(properties, 
"properties can not be null")));
+        this.unsetProperties = Collections.unmodifiableSet(
+                new LinkedHashSet<>(Objects.requireNonNull(unsetProperties, 
"unsetProperties can not be null")));
+        this.comment = comment;
+    }
+
+    public static AlterAuthenticationIntegrationCommand 
forSetProperties(String integrationName,
+            Map<String, String> properties) {
+        return new AlterAuthenticationIntegrationCommand(
+                integrationName, AlterType.SET_PROPERTIES, properties, 
Collections.emptySet(), null);
+    }
+
+    public static AlterAuthenticationIntegrationCommand 
forUnsetProperties(String integrationName,
+            Set<String> unsetProperties) {
+        return new AlterAuthenticationIntegrationCommand(
+                integrationName, AlterType.UNSET_PROPERTIES, 
Collections.emptyMap(), unsetProperties, null);
+    }
+
+    public static AlterAuthenticationIntegrationCommand forSetComment(String 
integrationName, String comment) {
+        return new AlterAuthenticationIntegrationCommand(
+                integrationName, AlterType.SET_COMMENT, 
Collections.emptyMap(), Collections.emptySet(), comment);
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitAlterAuthenticationIntegrationCommand(this, 
context);
+    }
+
+    @Override
+    public void doRun(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ctx, 
PrivPredicate.ADMIN)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, 
"ADMIN");
+        }
+        String alterUser = Objects.requireNonNull(ctx.getQualifiedUser(), 
"qualifiedUser can not be null");
+        switch (alterType) {
+            case SET_PROPERTIES:
+                Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+                        
.alterAuthenticationIntegrationProperties(integrationName, properties, 
alterUser);
+                return;
+            case UNSET_PROPERTIES:
+                Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+                        
.alterAuthenticationIntegrationUnsetProperties(integrationName, 
unsetProperties, alterUser);
+                return;
+            case SET_COMMENT:
+                Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+                        
.alterAuthenticationIntegrationComment(integrationName, comment, alterUser);
+                return;
+            default:
+                throw new AnalysisException("Unsupported alter type for 
AUTHENTICATION INTEGRATION: " + alterType);
+        }
+    }
+
+    @Override
+    public boolean needAuditEncryption() {
+        return true;
+    }
+
+    public String getIntegrationName() {
+        return integrationName;
+    }
+
+    public AlterType getAlterType() {
+        return alterType;
+    }
+
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    public Set<String> getUnsetProperties() {
+        return unsetProperties;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java
new file mode 100644
index 00000000000..690a024086c
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateAuthenticationIntegrationCommand.java
@@ -0,0 +1,95 @@
+// 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.doris.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * CREATE AUTHENTICATION INTEGRATION command entry.
+ */
+public class CreateAuthenticationIntegrationCommand extends Command implements 
ForwardWithSync, NeedAuditEncryption {
+    private final String integrationName;
+    private final boolean ifNotExists;
+    private final Map<String, String> properties;
+    private final String comment;
+
+    /** Constructor. */
+    public CreateAuthenticationIntegrationCommand(String integrationName, 
boolean ifNotExists,
+            Map<String, String> properties, String comment) {
+        super(PlanType.CREATE_AUTHENTICATION_INTEGRATION_COMMAND);
+        this.integrationName = Objects.requireNonNull(integrationName, 
"integrationName can not be null");
+        this.ifNotExists = ifNotExists;
+        this.properties = Collections.unmodifiableMap(
+                new LinkedHashMap<>(Objects.requireNonNull(properties, 
"properties can not be null")));
+        this.comment = comment;
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitCreateAuthenticationIntegrationCommand(this, 
context);
+    }
+
+    @Override
+    public void run(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ctx, 
PrivPredicate.ADMIN)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, 
"ADMIN");
+        }
+        Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+                .createAuthenticationIntegration(integrationName, ifNotExists, 
properties, comment,
+                        Objects.requireNonNull(ctx.getQualifiedUser(), 
"qualifiedUser can not be null"));
+    }
+
+    @Override
+    public StmtType stmtType() {
+        return StmtType.CREATE;
+    }
+
+    @Override
+    public boolean needAuditEncryption() {
+        return true;
+    }
+
+    public String getIntegrationName() {
+        return integrationName;
+    }
+
+    public boolean isSetIfNotExists() {
+        return ifNotExists;
+    }
+
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropAuthenticationIntegrationCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropAuthenticationIntegrationCommand.java
new file mode 100644
index 00000000000..aa041d44678
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropAuthenticationIntegrationCommand.java
@@ -0,0 +1,65 @@
+// 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.doris.nereids.trees.plans.commands;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import java.util.Objects;
+
+/**
+ * DROP AUTHENTICATION INTEGRATION command entry.
+ */
+public class DropAuthenticationIntegrationCommand extends DropCommand {
+    private final boolean ifExists;
+    private final String integrationName;
+
+    public DropAuthenticationIntegrationCommand(boolean ifExists, String 
integrationName) {
+        super(PlanType.DROP_AUTHENTICATION_INTEGRATION_COMMAND);
+        this.ifExists = ifExists;
+        this.integrationName = Objects.requireNonNull(integrationName, 
"integrationName can not be null");
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitDropAuthenticationIntegrationCommand(this, 
context);
+    }
+
+    @Override
+    public void doRun(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        if 
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.ADMIN)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, 
"ADMIN");
+        }
+        Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+                .dropAuthenticationIntegration(integrationName, ifExists);
+    }
+
+    public boolean isIfExists() {
+        return ifExists;
+    }
+
+    public String getIntegrationName() {
+        return integrationName;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
index 7ab5334529b..d2f645be59b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
@@ -36,6 +36,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.AdminSetPartitionVersionCom
 import 
org.apache.doris.nereids.trees.plans.commands.AdminSetReplicaStatusCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AdminSetReplicaVersionCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AdminSetTableStatusCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.AlterAuthenticationIntegrationCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AlterCatalogCommentCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AlterCatalogPropertiesCommand;
 import org.apache.doris.nereids.trees.plans.commands.AlterCatalogRenameCommand;
@@ -72,6 +73,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.CleanAllProfileCommand;
 import org.apache.doris.nereids.trees.plans.commands.CleanQueryStatsCommand;
 import org.apache.doris.nereids.trees.plans.commands.Command;
 import org.apache.doris.nereids.trees.plans.commands.CopyIntoCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.CreateAuthenticationIntegrationCommand;
 import org.apache.doris.nereids.trees.plans.commands.CreateCatalogCommand;
 import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand;
 import org.apache.doris.nereids.trees.plans.commands.CreateDictionaryCommand;
@@ -104,6 +106,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.DeleteFromCommand;
 import org.apache.doris.nereids.trees.plans.commands.DeleteFromUsingCommand;
 import org.apache.doris.nereids.trees.plans.commands.DescribeCommand;
 import org.apache.doris.nereids.trees.plans.commands.DropAnalyzeJobCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.DropAuthenticationIntegrationCommand;
 import org.apache.doris.nereids.trees.plans.commands.DropCachedStatsCommand;
 import org.apache.doris.nereids.trees.plans.commands.DropCatalogCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.DropCatalogRecycleBinCommand;
@@ -531,6 +534,11 @@ public interface CommandVisitor<R, C> {
         return visitCommand(createCatalogCommand, context);
     }
 
+    default R visitCreateAuthenticationIntegrationCommand(
+            CreateAuthenticationIntegrationCommand 
createAuthenticationIntegrationCommand, C context) {
+        return visitCommand(createAuthenticationIntegrationCommand, context);
+    }
+
     default R visitShowWarningErrorsCommand(ShowWarningErrorsCommand 
showWarningErrorsCommand, C context) {
         return visitCommand(showWarningErrorsCommand, context);
     }
@@ -563,6 +571,11 @@ public interface CommandVisitor<R, C> {
         return visitCommand(dropCatalogCommand, context);
     }
 
+    default R visitDropAuthenticationIntegrationCommand(
+            DropAuthenticationIntegrationCommand 
dropAuthenticationIntegrationCommand, C context) {
+        return visitCommand(dropAuthenticationIntegrationCommand, context);
+    }
+
     default R visitAlterCatalogCommentCommand(AlterCatalogCommentCommand 
alterCatalogCommentCommand, C context) {
         return visitCommand(alterCatalogCommentCommand, context);
     }
@@ -878,6 +891,11 @@ public interface CommandVisitor<R, C> {
         return visitCommand(alterCatalogPropsCmd, context);
     }
 
+    default R visitAlterAuthenticationIntegrationCommand(
+            AlterAuthenticationIntegrationCommand 
alterAuthenticationIntegrationCommand, C context) {
+        return visitCommand(alterAuthenticationIntegrationCommand, context);
+    }
+
     default R 
visitAlterDatabasePropertiesCommand(AlterDatabasePropertiesCommand 
alterDatabasePropsCmd, C context) {
         return visitCommand(alterDatabasePropsCmd, context);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLog.java
 
b/fe/fe-core/src/main/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLog.java
new file mode 100644
index 00000000000..a2f2015cdc2
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLog.java
@@ -0,0 +1,53 @@
+// 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.doris.persist;
+
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Drop log for AUTHENTICATION INTEGRATION.
+ */
+public class DropAuthenticationIntegrationOperationLog implements Writable {
+    @SerializedName(value = "in")
+    private String integrationName;
+
+    public DropAuthenticationIntegrationOperationLog(String integrationName) {
+        this.integrationName = integrationName;
+    }
+
+    public String getIntegrationName() {
+        return integrationName;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        Text.writeString(out, GsonUtils.GSON.toJson(this));
+    }
+
+    public static DropAuthenticationIntegrationOperationLog read(DataInput in) 
throws IOException {
+        return GsonUtils.GSON.fromJson(Text.readString(in), 
DropAuthenticationIntegrationOperationLog.class);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java 
b/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
index 95885f92286..4a2754d0b12 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
@@ -22,6 +22,7 @@ import org.apache.doris.alter.AlterJobV2.JobState;
 import org.apache.doris.alter.BatchAlterJobPersistInfo;
 import org.apache.doris.alter.IndexChangeJob;
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.authentication.AuthenticationIntegrationMeta;
 import org.apache.doris.backup.BackupJob;
 import org.apache.doris.backup.Repository;
 import org.apache.doris.backup.RestoreJob;
@@ -1072,6 +1073,22 @@ public class EditLog {
                     env.getSqlBlockRuleMgr().replayDrop(log.getRuleNames());
                     break;
                 }
+                case OperationType.OP_CREATE_AUTHENTICATION_INTEGRATION: {
+                    AuthenticationIntegrationMeta log = 
(AuthenticationIntegrationMeta) journal.getData();
+                    
env.getAuthenticationIntegrationMgr().replayCreateAuthenticationIntegration(log);
+                    break;
+                }
+                case OperationType.OP_ALTER_AUTHENTICATION_INTEGRATION: {
+                    AuthenticationIntegrationMeta log = 
(AuthenticationIntegrationMeta) journal.getData();
+                    
env.getAuthenticationIntegrationMgr().replayAlterAuthenticationIntegration(log);
+                    break;
+                }
+                case OperationType.OP_DROP_AUTHENTICATION_INTEGRATION: {
+                    DropAuthenticationIntegrationOperationLog log =
+                            (DropAuthenticationIntegrationOperationLog) 
journal.getData();
+                    
env.getAuthenticationIntegrationMgr().replayDropAuthenticationIntegration(log);
+                    break;
+                }
                 case OperationType.OP_MODIFY_TABLE_ENGINE: {
                     ModifyTableEngineOperationLog log = 
(ModifyTableEngineOperationLog) journal.getData();
                     env.getAlterInstance().replayProcessModifyEngine(log);
@@ -2279,6 +2296,18 @@ public class EditLog {
         logEdit(OperationType.OP_DROP_SQL_BLOCK_RULE, new 
DropSqlBlockRuleOperationLog(ruleNames));
     }
 
+    public void 
logCreateAuthenticationIntegration(AuthenticationIntegrationMeta meta) {
+        logEdit(OperationType.OP_CREATE_AUTHENTICATION_INTEGRATION, meta);
+    }
+
+    public void 
logAlterAuthenticationIntegration(AuthenticationIntegrationMeta meta) {
+        logEdit(OperationType.OP_ALTER_AUTHENTICATION_INTEGRATION, meta);
+    }
+
+    public void 
logDropAuthenticationIntegration(DropAuthenticationIntegrationOperationLog log) 
{
+        logEdit(OperationType.OP_DROP_AUTHENTICATION_INTEGRATION, log);
+    }
+
     public void logModifyTableEngine(ModifyTableEngineOperationLog log) {
         logEdit(OperationType.OP_MODIFY_TABLE_ENGINE, log);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java 
b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
index fc42ce65163..d1964d9d769 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
@@ -416,6 +416,9 @@ public class OperationType {
     public static final short OP_DROP_INDEX_POLICY = 491;
 
     public static final short OP_OPERATE_KEY = 492;
+    public static final short OP_CREATE_AUTHENTICATION_INTEGRATION = 493;
+    public static final short OP_ALTER_AUTHENTICATION_INTEGRATION = 494;
+    public static final short OP_DROP_AUTHENTICATION_INTEGRATION = 495;
 
     // For cloud.
     public static final short OP_UPDATE_CLOUD_REPLICA = 1000;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java 
b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java
index 0114bde9eee..ea3585d493b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/MetaPersistMethod.java
@@ -186,6 +186,15 @@ public class MetaPersistMethod {
                 metaPersistMethod.writeMethod =
                         Env.class.getDeclaredMethod("saveSqlBlockRule", 
CountingDataOutputStream.class, long.class);
                 break;
+            // TODO: Re-enable this module once AuthenticationIntegrations 
should be persisted again.
+            // case "authenticationIntegrations":
+            //     metaPersistMethod.readMethod =
+            //             
Env.class.getDeclaredMethod("loadAuthenticationIntegrations", 
DataInputStream.class,
+            //                     long.class);
+            //     metaPersistMethod.writeMethod =
+            //             
Env.class.getDeclaredMethod("saveAuthenticationIntegrations",
+            //                     CountingDataOutputStream.class, long.class);
+            //     break;
             case "policy":
                 metaPersistMethod.readMethod =
                         Env.class.getDeclaredMethod("loadPolicy", 
DataInputStream.class, long.class);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/PersistMetaModules.java
 
b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/PersistMetaModules.java
index 665f3cb09ed..44809035406 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/persist/meta/PersistMetaModules.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/persist/meta/PersistMetaModules.java
@@ -40,9 +40,13 @@ public class PersistMetaModules {
             "masterInfo", "frontends", "backends", "datasource", "db", 
"alterJob", "recycleBin",
             "globalVariable", "cluster", "broker", "resources", "exportJob", 
"backupHandler",
             "paloAuth", "transactionState", "colocateTableIndex", 
"routineLoadJobs", "loadJobV2", "smallFiles",
-            "plugins", "deleteHandler", "sqlBlockRule", "policy", 
"globalFunction", "workloadGroups",
+            "plugins", "deleteHandler", "sqlBlockRule", "policy",
+            "globalFunction", "workloadGroups",
             "binlogs", "resourceGroups", "AnalysisMgrV2", "AsyncJobManager", 
"workloadSchedPolicy",
-            "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy", 
"KeyManagerStore");
+            "insertOverwrite", "plsql", "dictionaryManager", "indexPolicy", 
"KeyManagerStore"
+            // TODO: Re-enable "authenticationIntegrations" after persistence 
requirements are confirmed.
+            // , "authenticationIntegrations"
+    );
 
     // The modules in `CloudEnv`.
     public static final ImmutableList<String> CLOUD_MODULE_NAMES = 
ImmutableList.of("cloudWarmUpJob");
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
index 62e9768d614..878e7452b24 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
@@ -18,6 +18,7 @@
 package org.apache.doris.tablefunction;
 
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.authentication.AuthenticationIntegrationMeta;
 import org.apache.doris.blockrule.SqlBlockRule;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.DataProperty;
@@ -49,6 +50,7 @@ import org.apache.doris.common.proc.PartitionsProcDir;
 import org.apache.doris.common.profile.RuntimeProfile;
 import org.apache.doris.common.util.DebugUtil;
 import org.apache.doris.common.util.NetUtils;
+import org.apache.doris.common.util.PrintableMap;
 import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.common.util.Util;
 import org.apache.doris.datasource.CatalogIf;
@@ -131,7 +133,9 @@ import org.jetbrains.annotations.NotNull;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Stream;
@@ -161,6 +165,8 @@ public class MetadataGenerator {
 
     private static final ImmutableMap<String, Integer> 
SQL_BLOCK_RULE_STATUS_COLUMN_TO_INDEX;
 
+    private static final ImmutableMap<String, Integer> 
AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX;
+
     static {
         ImmutableMap.Builder<String, Integer> activeQueriesbuilder = new 
ImmutableMap.Builder();
         List<Column> activeQueriesColList = 
SchemaTable.TABLE_MAP.get("active_queries").getFullSchema();
@@ -237,6 +243,15 @@ public class MetadataGenerator {
             
sqlBlockRuleStatusBuilder.put(sqlBlockRuleStatusBuilderColList.get(i).getName().toLowerCase(),
 i);
         }
         SQL_BLOCK_RULE_STATUS_COLUMN_TO_INDEX = 
sqlBlockRuleStatusBuilder.build();
+
+        ImmutableMap.Builder<String, Integer> 
authenticationIntegrationsBuilder = new ImmutableMap.Builder();
+        List<Column> authenticationIntegrationsColList = 
SchemaTable.TABLE_MAP.get("authentication_integrations")
+                .getFullSchema();
+        for (int i = 0; i < authenticationIntegrationsColList.size(); i++) {
+            authenticationIntegrationsBuilder.put(
+                    
authenticationIntegrationsColList.get(i).getName().toLowerCase(), i);
+        }
+        AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX = 
authenticationIntegrationsBuilder.build();
     }
 
     public static TFetchSchemaTableDataResult 
getMetadataTable(TFetchSchemaTableDataRequest request) throws TException {
@@ -351,6 +366,10 @@ public class MetadataGenerator {
                 result = sqlBlockRuleStatusMetadataResult(schemaTableParams);
                 columnIndex = SQL_BLOCK_RULE_STATUS_COLUMN_TO_INDEX;
                 break;
+            case AUTHENTICATION_INTEGRATIONS:
+                result = 
authenticationIntegrationsMetadataResult(schemaTableParams);
+                columnIndex = AUTHENTICATION_INTEGRATIONS_COLUMN_TO_INDEX;
+                break;
             default:
                 return errorResult("invalid schema table name.");
         }
@@ -691,6 +710,83 @@ public class MetadataGenerator {
         return result;
     }
 
+    private static TFetchSchemaTableDataResult 
authenticationIntegrationsMetadataResult(
+            TSchemaTableRequestParams params) {
+        if (!params.isSetCurrentUserIdent()) {
+            return errorResult("current user ident is not set.");
+        }
+        UserIdentity currentUserIdentity = 
UserIdentity.fromThrift(params.getCurrentUserIdent());
+        TFetchSchemaTableDataResult result = new TFetchSchemaTableDataResult();
+        List<TRow> dataBatch = Lists.newArrayList();
+        result.setDataBatch(dataBatch);
+        result.setStatus(new TStatus(TStatusCode.OK));
+        if 
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(currentUserIdentity, 
PrivPredicate.ADMIN)) {
+            return result;
+        }
+
+        List<Expression> conjuncts = Collections.EMPTY_LIST;
+        if (params.isSetFrontendConjuncts()) {
+            conjuncts = 
FrontendConjunctsUtils.convertToExpression(params.getFrontendConjuncts());
+        }
+        List<Expression> nameConjuncts = 
FrontendConjunctsUtils.filterBySlotName(conjuncts, "NAME");
+        List<Expression> typeConjuncts = 
FrontendConjunctsUtils.filterBySlotName(conjuncts, "TYPE");
+
+        for (AuthenticationIntegrationMeta meta : 
Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+                .getAuthenticationIntegrations().values()) {
+            if (FrontendConjunctsUtils.isFiltered(nameConjuncts, "NAME", 
meta.getName())
+                    || FrontendConjunctsUtils.isFiltered(typeConjuncts, 
"TYPE", meta.getType())) {
+                continue;
+            }
+            TRow row = new TRow();
+            row.addToColumnValue(new TCell().setStringVal(meta.getName()));
+            row.addToColumnValue(new TCell().setStringVal(meta.getType()));
+            row.addToColumnValue(new 
TCell().setStringVal(maskAuthenticationProperties(meta.getProperties())));
+            if (meta.getComment() == null) {
+                row.addToColumnValue(new TCell());
+            } else {
+                row.addToColumnValue(new 
TCell().setStringVal(meta.getComment()));
+            }
+            row.addToColumnValue(new 
TCell().setStringVal(meta.getCreateUser()));
+            row.addToColumnValue(new 
TCell().setStringVal(meta.getCreateTimeString()));
+            row.addToColumnValue(new 
TCell().setStringVal(meta.getAlterUser()));
+            row.addToColumnValue(new 
TCell().setStringVal(meta.getModifyTimeString()));
+            dataBatch.add(row);
+        }
+        return result;
+    }
+
+    private static String maskAuthenticationProperties(Map<String, String> 
properties) {
+        Map<String, String> maskedProperties = new LinkedHashMap<>();
+        for (Map.Entry<String, String> entry : properties.entrySet()) {
+            if (shouldMaskAuthenticationProperty(entry.getKey())) {
+                maskedProperties.put(entry.getKey(), 
PrintableMap.PASSWORD_MASK);
+            } else {
+                maskedProperties.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return new PrintableMap<>(maskedProperties, "=", true, 
false).toString();
+    }
+
+    private static boolean shouldMaskAuthenticationProperty(String key) {
+        String lowerCaseKey = key.toLowerCase(Locale.ROOT);
+        return PrintableMap.SENSITIVE_KEY.contains(key)
+                || lowerCaseKey.startsWith("secret.")
+                || lowerCaseKey.endsWith(".password")
+                || lowerCaseKey.endsWith("_password")
+                || lowerCaseKey.equals("secret")
+                || lowerCaseKey.endsWith(".secret")
+                || lowerCaseKey.endsWith("_secret")
+                || lowerCaseKey.endsWith(".secret_key")
+                || lowerCaseKey.endsWith("_secret_key")
+                || lowerCaseKey.endsWith(".token")
+                || lowerCaseKey.endsWith("_token")
+                || lowerCaseKey.endsWith(".credential")
+                || lowerCaseKey.endsWith("_credential")
+                || lowerCaseKey.endsWith(".keytab")
+                || lowerCaseKey.endsWith("_keytab")
+                || lowerCaseKey.endsWith("keytab_content");
+    }
+
     private static TFetchSchemaTableDataResult 
viewDependencyMetadataResult(TSchemaTableRequestParams params) {
         if (!params.isSetCurrentUserIdent()) {
             return errorResult("current user ident is not set.");
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java
new file mode 100644
index 00000000000..8bdd9a491dd
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMetaTest.java
@@ -0,0 +1,190 @@
+// 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.doris.authentication;
+
+import org.apache.doris.common.DdlException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AuthenticationIntegrationMetaTest {
+    private static final String CREATE_USER = "creator";
+    private static final String ALTER_USER = "modifier";
+
+    private static Map<String, String> map(String... kvs) {
+        Map<String, String> result = new LinkedHashMap<>();
+        for (int i = 0; i < kvs.length; i += 2) {
+            result.put(kvs[i], kvs[i + 1]);
+        }
+        return result;
+    }
+
+    private static Set<String> set(String... keys) {
+        Set<String> result = new LinkedHashSet<>();
+        Collections.addAll(result, keys);
+        return result;
+    }
+
+    @Test
+    public void testFromCreateSqlSuccessAndTypeFiltered() throws Exception {
+        Map<String, String> properties = new LinkedHashMap<>();
+        properties.put("TYPE", "ldap");
+        properties.put("ldap.server", "ldap://127.0.0.1:389";);
+        properties.put("ldap.admin_dn", "cn=admin,dc=example,dc=com");
+
+        AuthenticationIntegrationMeta meta =
+                AuthenticationIntegrationMeta.fromCreateSql("corp_ldap", 
properties, "ldap integration", CREATE_USER);
+
+        Assertions.assertEquals("corp_ldap", meta.getName());
+        Assertions.assertEquals("ldap", meta.getType());
+        Assertions.assertEquals("ldap integration", meta.getComment());
+        Assertions.assertEquals(CREATE_USER, meta.getCreateUser());
+        Assertions.assertEquals(CREATE_USER, meta.getAlterUser());
+        Assertions.assertTrue(meta.getCreateTime() > 0);
+        Assertions.assertEquals(meta.getCreateTime(), meta.getModifyTime());
+        Assertions.assertEquals(meta.getCreateTimeString(), 
meta.getModifyTimeString());
+        Assertions.assertEquals(2, meta.getProperties().size());
+        Assertions.assertEquals("ldap://127.0.0.1:389";, 
meta.getProperties().get("ldap.server"));
+        Assertions.assertFalse(meta.getProperties().containsKey("type"));
+        Assertions.assertFalse(meta.getProperties().containsKey("TYPE"));
+
+        Assertions.assertThrows(UnsupportedOperationException.class,
+                () -> meta.getProperties().put("x", "y"));
+
+        Map<String, String> sqlProperties = meta.toSqlPropertiesView();
+        Assertions.assertEquals("ldap", sqlProperties.get("type"));
+        Assertions.assertEquals("cn=admin,dc=example,dc=com", 
sqlProperties.get("ldap.admin_dn"));
+    }
+
+    @Test
+    public void testFromCreateSqlRequireType() {
+        Assertions.assertThrows(DdlException.class,
+                () -> AuthenticationIntegrationMeta.fromCreateSql("i1", null, 
null, CREATE_USER));
+        Assertions.assertThrows(DdlException.class,
+                () -> AuthenticationIntegrationMeta.fromCreateSql("i1", 
Collections.emptyMap(), null, CREATE_USER));
+        Assertions.assertThrows(DdlException.class,
+                () -> AuthenticationIntegrationMeta.fromCreateSql("i1", 
map("k", "v"), null, CREATE_USER));
+        Assertions.assertThrows(DdlException.class,
+                () -> AuthenticationIntegrationMeta.fromCreateSql("i1", 
map("type", ""), null, CREATE_USER));
+    }
+
+    @Test
+    public void testFromCreateSqlRejectDuplicatedTypeIgnoreCase() {
+        Map<String, String> properties = new LinkedHashMap<>();
+        properties.put("type", "ldap");
+        properties.put("TYPE", "oidc");
+
+        Assertions.assertThrows(DdlException.class,
+                () -> AuthenticationIntegrationMeta.fromCreateSql("i1", 
properties, null, CREATE_USER));
+    }
+
+    @Test
+    public void testWithAlterProperties() throws Exception {
+        AuthenticationIntegrationMeta meta = 
AuthenticationIntegrationMeta.fromCreateSql(
+                "corp_ldap",
+                map("type", "ldap",
+                        "ldap.server", "ldap://old";,
+                        "ldap.base_dn", "dc=example,dc=com"),
+                "old comment",
+                CREATE_USER);
+
+        AuthenticationIntegrationMeta altered = meta.withAlterProperties(map(
+                "ldap.server", "ldap://new";,
+                "ldap.user_filter", "(uid={login})"), ALTER_USER);
+
+        Assertions.assertEquals("ldap", altered.getType());
+        Assertions.assertEquals("old comment", altered.getComment());
+        Assertions.assertEquals(CREATE_USER, altered.getCreateUser());
+        Assertions.assertEquals(ALTER_USER, altered.getAlterUser());
+        Assertions.assertEquals(meta.getCreateTime(), altered.getCreateTime());
+        Assertions.assertTrue(altered.getModifyTime() >= meta.getModifyTime());
+        Assertions.assertEquals("ldap://new";, 
altered.getProperties().get("ldap.server"));
+        Assertions.assertEquals("(uid={login})", 
altered.getProperties().get("ldap.user_filter"));
+
+        Assertions.assertThrows(DdlException.class,
+                () -> meta.withAlterProperties(Collections.emptyMap(), 
ALTER_USER));
+        Assertions.assertThrows(DdlException.class,
+                () -> meta.withAlterProperties(map("TYPE", "oidc"), 
ALTER_USER));
+    }
+
+    @Test
+    public void testWithUnsetProperties() throws Exception {
+        AuthenticationIntegrationMeta meta = 
AuthenticationIntegrationMeta.fromCreateSql(
+                "corp_ldap",
+                map("type", "ldap",
+                        "ldap.server", "ldap://old";,
+                        "ldap.base_dn", "dc=example,dc=com"),
+                "old comment",
+                CREATE_USER);
+
+        AuthenticationIntegrationMeta altered = 
meta.withUnsetProperties(set("ldap.base_dn"), ALTER_USER);
+        Assertions.assertEquals("ldap", altered.getType());
+        Assertions.assertEquals(CREATE_USER, altered.getCreateUser());
+        Assertions.assertEquals(ALTER_USER, altered.getAlterUser());
+        Assertions.assertEquals(meta.getCreateTime(), altered.getCreateTime());
+        Assertions.assertTrue(altered.getModifyTime() >= meta.getModifyTime());
+        
Assertions.assertFalse(altered.getProperties().containsKey("ldap.base_dn"));
+        Assertions.assertEquals("ldap://old";, 
altered.getProperties().get("ldap.server"));
+
+        Assertions.assertThrows(DdlException.class,
+                () -> meta.withUnsetProperties(Collections.emptySet(), 
ALTER_USER));
+        Assertions.assertThrows(DdlException.class,
+                () -> meta.withUnsetProperties(set("TYPE"), ALTER_USER));
+    }
+
+    @Test
+    public void testWriteReadRoundTrip() throws IOException, DdlException {
+        AuthenticationIntegrationMeta meta = 
AuthenticationIntegrationMeta.fromCreateSql(
+                "corp_ldap",
+                map("type", "ldap",
+                        "ldap.server", "ldap://127.0.0.1:389";,
+                        "ldap.admin_password", "123456"),
+                "comment",
+                CREATE_USER);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (DataOutputStream dos = new DataOutputStream(bos)) {
+            meta.write(dos);
+        }
+
+        AuthenticationIntegrationMeta read;
+        try (DataInputStream dis = new DataInputStream(new 
ByteArrayInputStream(bos.toByteArray()))) {
+            read = AuthenticationIntegrationMeta.read(dis);
+        }
+
+        Assertions.assertEquals(meta.getName(), read.getName());
+        Assertions.assertEquals(meta.getType(), read.getType());
+        Assertions.assertEquals(meta.getComment(), read.getComment());
+        Assertions.assertEquals(meta.getProperties(), read.getProperties());
+        Assertions.assertEquals(meta.getCreateUser(), read.getCreateUser());
+        Assertions.assertEquals(meta.getCreateTime(), read.getCreateTime());
+        Assertions.assertEquals(meta.getAlterUser(), read.getAlterUser());
+        Assertions.assertEquals(meta.getModifyTime(), read.getModifyTime());
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java
new file mode 100644
index 00000000000..c965cea9f01
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/authentication/AuthenticationIntegrationMgrTest.java
@@ -0,0 +1,216 @@
+// 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.doris.authentication;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.persist.DropAuthenticationIntegrationOperationLog;
+import org.apache.doris.persist.EditLog;
+
+import mockit.Expectations;
+import mockit.Mocked;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AuthenticationIntegrationMgrTest {
+    private static final String CREATE_USER = "creator";
+    private static final String ALTER_USER = "modifier";
+
+    @Mocked
+    private Env env;
+
+    @Mocked
+    private EditLog editLog;
+
+    private static Map<String, String> map(String... kvs) {
+        Map<String, String> result = new LinkedHashMap<>();
+        for (int i = 0; i < kvs.length; i += 2) {
+            result.put(kvs[i], kvs[i + 1]);
+        }
+        return result;
+    }
+
+    private static Set<String> set(String... keys) {
+        Set<String> result = new LinkedHashSet<>();
+        Collections.addAll(result, keys);
+        return result;
+    }
+
+    @Test
+    public void testCreateAlterDropFlow() throws Exception {
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getEditLog();
+                minTimes = 0;
+                result = editLog;
+
+                
editLog.logCreateAuthenticationIntegration((AuthenticationIntegrationMeta) any);
+                minTimes = 0;
+
+                
editLog.logAlterAuthenticationIntegration((AuthenticationIntegrationMeta) any);
+                minTimes = 0;
+
+                
editLog.logDropAuthenticationIntegration((DropAuthenticationIntegrationOperationLog)
 any);
+                minTimes = 0;
+            }
+        };
+
+        AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+        Map<String, String> createProperties = new LinkedHashMap<>();
+        createProperties.put("type", "ldap");
+        createProperties.put("ldap.server", "ldap://127.0.0.1:389";);
+        createProperties.put("ldap.admin_password", "123456");
+
+        mgr.createAuthenticationIntegration("corp_ldap", false, 
createProperties, "comment", CREATE_USER);
+        AuthenticationIntegrationMeta created = 
mgr.getAuthenticationIntegrations().get("corp_ldap");
+        Assertions.assertNotNull(created);
+        Assertions.assertEquals("ldap", created.getType());
+        Assertions.assertEquals(CREATE_USER, created.getCreateUser());
+        Assertions.assertEquals(CREATE_USER, created.getAlterUser());
+        Assertions.assertEquals("ldap://127.0.0.1:389";, 
created.getProperties().get("ldap.server"));
+
+        mgr.alterAuthenticationIntegrationProperties(
+                "corp_ldap", map("ldap.server", "ldap://127.0.0.1:1389";), 
ALTER_USER);
+        Assertions.assertEquals(ALTER_USER,
+                
mgr.getAuthenticationIntegrations().get("corp_ldap").getAlterUser());
+        Assertions.assertEquals("ldap://127.0.0.1:1389";,
+                
mgr.getAuthenticationIntegrations().get("corp_ldap").getProperties().get("ldap.server"));
+
+        mgr.alterAuthenticationIntegrationUnsetProperties("corp_ldap", 
set("ldap.admin_password"), ALTER_USER);
+        Assertions.assertFalse(mgr.getAuthenticationIntegrations()
+                
.get("corp_ldap").getProperties().containsKey("ldap.admin_password"));
+
+        mgr.alterAuthenticationIntegrationComment("corp_ldap", "new comment", 
ALTER_USER);
+        Assertions.assertEquals("new comment", 
mgr.getAuthenticationIntegrations().get("corp_ldap").getComment());
+
+        mgr.dropAuthenticationIntegration("corp_ldap", false);
+        Assertions.assertTrue(mgr.getAuthenticationIntegrations().isEmpty());
+    }
+
+    @Test
+    public void testCreateDuplicateAndDropIfExists() throws Exception {
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getEditLog();
+                minTimes = 0;
+                result = editLog;
+
+                
editLog.logCreateAuthenticationIntegration((AuthenticationIntegrationMeta) any);
+                minTimes = 0;
+
+                
editLog.logDropAuthenticationIntegration((DropAuthenticationIntegrationOperationLog)
 any);
+                minTimes = 0;
+            }
+        };
+
+        AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+        mgr.createAuthenticationIntegration("corp_ldap", false, map(
+                "type", "ldap",
+                "ldap.server", "ldap://127.0.0.1:389";), null, CREATE_USER);
+
+        Assertions.assertThrows(DdlException.class,
+                () -> mgr.createAuthenticationIntegration("corp_ldap", false, 
map("type", "ldap"), null, CREATE_USER));
+        Assertions.assertDoesNotThrow(
+                () -> mgr.createAuthenticationIntegration("corp_ldap", true, 
map("type", "ldap"), null, CREATE_USER));
+
+        Assertions.assertDoesNotThrow(() -> 
mgr.dropAuthenticationIntegration("not_exist", true));
+        Assertions.assertThrows(DdlException.class,
+                () -> mgr.dropAuthenticationIntegration("not_exist", false));
+    }
+
+    @Test
+    public void testAlterNotExistThrows() {
+        AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+        Assertions.assertThrows(DdlException.class,
+                () -> 
mgr.alterAuthenticationIntegrationProperties("not_exist", map("k", "v"), 
ALTER_USER));
+        Assertions.assertThrows(DdlException.class,
+                () -> 
mgr.alterAuthenticationIntegrationUnsetProperties("not_exist", set("k"), 
ALTER_USER));
+        Assertions.assertThrows(DdlException.class,
+                () -> mgr.alterAuthenticationIntegrationComment("not_exist", 
"comment", ALTER_USER));
+    }
+
+    @Test
+    public void testReplayAndGetUnmodifiableView() throws Exception {
+        AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+
+        AuthenticationIntegrationMeta meta1 = 
AuthenticationIntegrationMeta.fromCreateSql(
+                "corp_ldap", map("type", "ldap", "ldap.server", "ldap://old";), 
null, CREATE_USER);
+        AuthenticationIntegrationMeta meta2 = 
meta1.withAlterProperties(map("ldap.server", "ldap://new";), ALTER_USER);
+
+        mgr.replayCreateAuthenticationIntegration(meta1);
+        mgr.replayAlterAuthenticationIntegration(meta2);
+
+        Map<String, AuthenticationIntegrationMeta> copy = 
mgr.getAuthenticationIntegrations();
+        Assertions.assertEquals(1, copy.size());
+        Assertions.assertThrows(UnsupportedOperationException.class,
+                () -> copy.put("x", meta1));
+
+        mgr.replayDropAuthenticationIntegration(new 
DropAuthenticationIntegrationOperationLog("corp_ldap"));
+        Assertions.assertTrue(mgr.getAuthenticationIntegrations().isEmpty());
+    }
+
+    @Test
+    public void testWriteReadRoundTrip() throws IOException, DdlException {
+        AuthenticationIntegrationMgr mgr = new AuthenticationIntegrationMgr();
+        AuthenticationIntegrationMeta meta = 
AuthenticationIntegrationMeta.fromCreateSql(
+                "corp_ldap", map(
+                        "type", "ldap",
+                        "ldap.server", "ldap://127.0.0.1:389";),
+                "comment",
+                CREATE_USER);
+        mgr.replayCreateAuthenticationIntegration(meta);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (DataOutputStream dos = new DataOutputStream(bos)) {
+            mgr.write(dos);
+        }
+
+        AuthenticationIntegrationMgr read;
+        try (DataInputStream dis = new DataInputStream(new 
ByteArrayInputStream(bos.toByteArray()))) {
+            read = AuthenticationIntegrationMgr.read(dis);
+        }
+
+        Assertions.assertEquals(1, 
read.getAuthenticationIntegrations().size());
+        AuthenticationIntegrationMeta readMeta = 
read.getAuthenticationIntegrations().get("corp_ldap");
+        Assertions.assertNotNull(readMeta);
+        Assertions.assertEquals("ldap", readMeta.getType());
+        Assertions.assertEquals("ldap://127.0.0.1:389";, 
readMeta.getProperties().get("ldap.server"));
+        Assertions.assertEquals("comment", readMeta.getComment());
+        Assertions.assertEquals(CREATE_USER, readMeta.getCreateUser());
+        Assertions.assertEquals(CREATE_USER, readMeta.getAlterUser());
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
index 03c6d092eab..9970d50a37a 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/SchemaTableTest.java
@@ -92,5 +92,15 @@ public class SchemaTableTest {
         SchemaTable viewDependency = (SchemaTable) 
SchemaTable.TABLE_MAP.get("view_dependency");
         Assertions.assertFalse(viewDependency.shouldFetchAllFe());
         Assertions.assertFalse(viewDependency.shouldAddAgg());
+
+        SchemaTable authenticationIntegrations =
+                (SchemaTable) 
SchemaTable.TABLE_MAP.get("authentication_integrations");
+        Assertions.assertFalse(authenticationIntegrations.shouldFetchAllFe());
+        Assertions.assertFalse(authenticationIntegrations.shouldAddAgg());
+        Assertions.assertEquals(8, 
authenticationIntegrations.getFullSchema().size());
+        Assertions.assertEquals("CREATE_USER", 
authenticationIntegrations.getFullSchema().get(4).getName());
+        Assertions.assertEquals("CREATE_TIME", 
authenticationIntegrations.getFullSchema().get(5).getName());
+        Assertions.assertEquals("ALTER_USER", 
authenticationIntegrations.getFullSchema().get(6).getName());
+        Assertions.assertEquals("MODIFY_TIME", 
authenticationIntegrations.getFullSchema().get(7).getName());
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java
new file mode 100644
index 00000000000..2a01a237209
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AuthenticationIntegrationParserTest.java
@@ -0,0 +1,111 @@
+// 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.doris.nereids.parser;
+
+import org.apache.doris.nereids.exceptions.ParseException;
+import 
org.apache.doris.nereids.trees.plans.commands.AlterAuthenticationIntegrationCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.CreateAuthenticationIntegrationCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.DropAuthenticationIntegrationCommand;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class AuthenticationIntegrationParserTest {
+
+    private final NereidsParser parser = new NereidsParser();
+
+    @Test
+    public void testCreateAuthenticationIntegrationParse() {
+        LogicalPlan plan = parser.parseSingle("CREATE AUTHENTICATION 
INTEGRATION IF NOT EXISTS corp_ldap "
+                + "PROPERTIES ('type'='ldap', 
'ldap.server'='ldap://127.0.0.1:389') "
+                + "COMMENT 'ldap integration'");
+
+        
Assertions.assertInstanceOf(CreateAuthenticationIntegrationCommand.class, plan);
+        CreateAuthenticationIntegrationCommand command = 
(CreateAuthenticationIntegrationCommand) plan;
+        Assertions.assertEquals("corp_ldap", command.getIntegrationName());
+        Assertions.assertTrue(command.isSetIfNotExists());
+        Assertions.assertEquals("ldap", command.getProperties().get("type"));
+        Assertions.assertEquals("ldap://127.0.0.1:389";, 
command.getProperties().get("ldap.server"));
+        Assertions.assertEquals("ldap integration", command.getComment());
+    }
+
+    @Test
+    public void testCreateAuthenticationIntegrationRequireType() {
+        Assertions.assertThrows(ParseException.class, () -> parser.parseSingle(
+                "CREATE AUTHENTICATION INTEGRATION corp_ldap "
+                        + "PROPERTIES 
('ldap.server'='ldap://127.0.0.1:389')"));
+    }
+
+    @Test
+    public void testAlterAuthenticationIntegrationParse() {
+        LogicalPlan alterProperties = parser.parseSingle("ALTER AUTHENTICATION 
INTEGRATION corp_ldap "
+                + "SET PROPERTIES ('ldap.server'='ldap://127.0.0.1:1389')");
+        
Assertions.assertInstanceOf(AlterAuthenticationIntegrationCommand.class, 
alterProperties);
+
+        AlterAuthenticationIntegrationCommand alterPropertiesCommand =
+                (AlterAuthenticationIntegrationCommand) alterProperties;
+        Assertions.assertEquals("corp_ldap", 
alterPropertiesCommand.getIntegrationName());
+        
Assertions.assertEquals(AlterAuthenticationIntegrationCommand.AlterType.SET_PROPERTIES,
+                alterPropertiesCommand.getAlterType());
+        Assertions.assertEquals("ldap://127.0.0.1:1389";,
+                alterPropertiesCommand.getProperties().get("ldap.server"));
+
+        LogicalPlan unsetProperties = parser.parseSingle("ALTER AUTHENTICATION 
INTEGRATION corp_ldap "
+                + "UNSET PROPERTIES ('ldap.server')");
+        
Assertions.assertInstanceOf(AlterAuthenticationIntegrationCommand.class, 
unsetProperties);
+
+        AlterAuthenticationIntegrationCommand unsetPropertiesCommand =
+                (AlterAuthenticationIntegrationCommand) unsetProperties;
+        
Assertions.assertEquals(AlterAuthenticationIntegrationCommand.AlterType.UNSET_PROPERTIES,
+                unsetPropertiesCommand.getAlterType());
+        
Assertions.assertTrue(unsetPropertiesCommand.getUnsetProperties().contains("ldap.server"));
+
+        LogicalPlan alterComment = parser.parseSingle(
+                "ALTER AUTHENTICATION INTEGRATION corp_ldap SET COMMENT 'new 
comment'");
+        
Assertions.assertInstanceOf(AlterAuthenticationIntegrationCommand.class, 
alterComment);
+
+        AlterAuthenticationIntegrationCommand alterCommentCommand =
+                (AlterAuthenticationIntegrationCommand) alterComment;
+        
Assertions.assertEquals(AlterAuthenticationIntegrationCommand.AlterType.SET_COMMENT,
+                alterCommentCommand.getAlterType());
+        Assertions.assertEquals("new comment", 
alterCommentCommand.getComment());
+    }
+
+    @Test
+    public void testAlterAuthenticationIntegrationRejectType() {
+        Assertions.assertThrows(ParseException.class, () -> parser.parseSingle(
+                "ALTER AUTHENTICATION INTEGRATION corp_ldap SET PROPERTIES 
('TYPE'='oidc')"));
+        Assertions.assertThrows(ParseException.class, () -> parser.parseSingle(
+                "ALTER AUTHENTICATION INTEGRATION corp_ldap UNSET PROPERTIES 
('TYPE')"));
+    }
+
+    @Test
+    public void testDropAuthenticationIntegrationParse() {
+        LogicalPlan plan1 = parser.parseSingle("DROP AUTHENTICATION 
INTEGRATION corp_ldap");
+        
Assertions.assertInstanceOf(DropAuthenticationIntegrationCommand.class, plan1);
+        DropAuthenticationIntegrationCommand drop1 = 
(DropAuthenticationIntegrationCommand) plan1;
+        Assertions.assertEquals("corp_ldap", drop1.getIntegrationName());
+        Assertions.assertFalse(drop1.isIfExists());
+
+        LogicalPlan plan2 = parser.parseSingle("DROP AUTHENTICATION 
INTEGRATION IF EXISTS corp_ldap");
+        
Assertions.assertInstanceOf(DropAuthenticationIntegrationCommand.class, plan2);
+        DropAuthenticationIntegrationCommand drop2 = 
(DropAuthenticationIntegrationCommand) plan2;
+        Assertions.assertTrue(drop2.isIfExists());
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java
new file mode 100644
index 00000000000..8d387c5c877
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AuthenticationIntegrationCommandTest.java
@@ -0,0 +1,249 @@
+// 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.doris.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.authentication.AuthenticationIntegrationMgr;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.mysql.privilege.AccessControllerManager;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
+
+import mockit.Expectations;
+import mockit.Mocked;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AuthenticationIntegrationCommandTest {
+
+    @Mocked
+    private Env env;
+
+    @Mocked
+    private AccessControllerManager accessManager;
+
+    @Mocked
+    private AuthenticationIntegrationMgr authenticationIntegrationMgr;
+
+    @Mocked
+    private ConnectContext connectContext;
+
+    private static Map<String, String> map(String... kvs) {
+        Map<String, String> result = new LinkedHashMap<>();
+        for (int i = 0; i < kvs.length; i += 2) {
+            result.put(kvs[i], kvs[i + 1]);
+        }
+        return result;
+    }
+
+    private static Set<String> set(String... keys) {
+        Set<String> result = new LinkedHashSet<>();
+        Collections.addAll(result, keys);
+        return result;
+    }
+
+    @Test
+    public void testCreateCommandRunAndDenied() throws Exception {
+        UserIdentity currentUser = 
UserIdentity.createAnalyzedUserIdentWithIp("admin", "%");
+        CreateAuthenticationIntegrationCommand createCommand =
+                new CreateAuthenticationIntegrationCommand("corp_ldap", false,
+                        map("type", "ldap", "ldap.server", 
"ldap://127.0.0.1:389";), "comment");
+
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessManager;
+
+                accessManager.checkGlobalPriv((ConnectContext) any, 
PrivPredicate.ADMIN);
+                result = true;
+
+                connectContext.getQualifiedUser();
+                minTimes = 0;
+                result = currentUser.getQualifiedUser();
+
+                env.getAuthenticationIntegrationMgr();
+                minTimes = 0;
+                result = authenticationIntegrationMgr;
+
+                authenticationIntegrationMgr.createAuthenticationIntegration(
+                        anyString, anyBoolean, (Map<String, String>) any, 
anyString, anyString);
+                times = 1;
+            }
+        };
+
+        Assertions.assertDoesNotThrow(() -> createCommand.run(connectContext, 
null));
+        Assertions.assertEquals(StmtType.CREATE, createCommand.stmtType());
+        Assertions.assertTrue(createCommand.needAuditEncryption());
+
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessManager;
+
+                accessManager.checkGlobalPriv((ConnectContext) any, 
PrivPredicate.ADMIN);
+                result = false;
+            }
+        };
+
+        Assertions.assertThrows(AnalysisException.class, () -> 
createCommand.run(connectContext, null));
+    }
+
+    @Test
+    public void testAlterCommandRun() throws Exception {
+        UserIdentity currentUser = 
UserIdentity.createAnalyzedUserIdentWithIp("admin", "%");
+        AlterAuthenticationIntegrationCommand setPropertiesCommand =
+                AlterAuthenticationIntegrationCommand.forSetProperties(
+                        "corp_ldap", map("ldap.server", 
"ldap://127.0.0.1:1389";));
+        AlterAuthenticationIntegrationCommand unsetPropertiesCommand =
+                AlterAuthenticationIntegrationCommand.forUnsetProperties(
+                        "corp_ldap", set("ldap.server"));
+        AlterAuthenticationIntegrationCommand setCommentCommand =
+                
AlterAuthenticationIntegrationCommand.forSetComment("corp_ldap", "new comment");
+
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessManager;
+
+                accessManager.checkGlobalPriv((ConnectContext) any, 
PrivPredicate.ADMIN);
+                minTimes = 0;
+                result = true;
+
+                connectContext.getQualifiedUser();
+                minTimes = 0;
+                result = currentUser.getQualifiedUser();
+
+                env.getAuthenticationIntegrationMgr();
+                minTimes = 0;
+                result = authenticationIntegrationMgr;
+
+                
authenticationIntegrationMgr.alterAuthenticationIntegrationProperties(
+                        anyString, (Map<String, String>) any, anyString);
+                times = 1;
+
+                
authenticationIntegrationMgr.alterAuthenticationIntegrationUnsetProperties(
+                        anyString, (Set<String>) any, anyString);
+                times = 1;
+
+                
authenticationIntegrationMgr.alterAuthenticationIntegrationComment(anyString, 
anyString, anyString);
+                times = 1;
+            }
+        };
+
+        Assertions.assertDoesNotThrow(() -> 
setPropertiesCommand.doRun(connectContext, null));
+        Assertions.assertDoesNotThrow(() -> 
unsetPropertiesCommand.doRun(connectContext, null));
+        Assertions.assertDoesNotThrow(() -> 
setCommentCommand.doRun(connectContext, null));
+        Assertions.assertTrue(setPropertiesCommand.needAuditEncryption());
+        Assertions.assertTrue(unsetPropertiesCommand.needAuditEncryption());
+        Assertions.assertTrue(setCommentCommand.needAuditEncryption());
+    }
+
+    @Test
+    public void testAlterCommandDenied() {
+        AlterAuthenticationIntegrationCommand setPropertiesCommand =
+                AlterAuthenticationIntegrationCommand.forSetProperties(
+                        "corp_ldap", map("ldap.server", 
"ldap://127.0.0.1:1389";));
+
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessManager;
+
+                accessManager.checkGlobalPriv((ConnectContext) any, 
PrivPredicate.ADMIN);
+                result = false;
+            }
+        };
+
+        Assertions.assertThrows(AnalysisException.class, () -> 
setPropertiesCommand.doRun(connectContext, null));
+    }
+
+    @Test
+    public void testDropCommandRunAndDenied() throws Exception {
+        DropAuthenticationIntegrationCommand dropCommand =
+                new DropAuthenticationIntegrationCommand(true, "corp_ldap");
+
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessManager;
+
+                accessManager.checkGlobalPriv((ConnectContext) any, 
PrivPredicate.ADMIN);
+                result = true;
+
+                env.getAuthenticationIntegrationMgr();
+                minTimes = 0;
+                result = authenticationIntegrationMgr;
+
+                
authenticationIntegrationMgr.dropAuthenticationIntegration(anyString, 
anyBoolean);
+                times = 1;
+            }
+        };
+
+        Assertions.assertDoesNotThrow(() -> dropCommand.doRun(connectContext, 
null));
+
+        new Expectations() {
+            {
+                Env.getCurrentEnv();
+                minTimes = 0;
+                result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessManager;
+
+                accessManager.checkGlobalPriv((ConnectContext) any, 
PrivPredicate.ADMIN);
+                result = false;
+            }
+        };
+
+        Assertions.assertThrows(AnalysisException.class, () -> 
dropCommand.doRun(connectContext, null));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLogTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLogTest.java
new file mode 100644
index 00000000000..042a7fc40d2
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/persist/DropAuthenticationIntegrationOperationLogTest.java
@@ -0,0 +1,47 @@
+// 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.doris.persist;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public class DropAuthenticationIntegrationOperationLogTest {
+
+    @Test
+    public void testWriteReadRoundTrip() throws Exception {
+        DropAuthenticationIntegrationOperationLog log =
+                new DropAuthenticationIntegrationOperationLog("corp_ldap");
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (DataOutputStream dos = new DataOutputStream(bos)) {
+            log.write(dos);
+        }
+
+        DropAuthenticationIntegrationOperationLog read;
+        try (DataInputStream dis = new DataInputStream(new 
ByteArrayInputStream(bos.toByteArray()))) {
+            read = DropAuthenticationIntegrationOperationLog.read(dis);
+        }
+
+        Assertions.assertEquals("corp_ldap", read.getIntegrationName());
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
index f78d1bdf6e7..56d5710f92d 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
@@ -23,6 +23,7 @@ import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.Partition;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.FeConstants;
+import org.apache.doris.common.util.PrintableMap;
 import org.apache.doris.nereids.parser.NereidsParser;
 import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand;
 import org.apache.doris.nereids.trees.plans.commands.CreateTableCommand;
@@ -41,6 +42,7 @@ import org.apache.doris.thrift.TMetadataTableRequestParams;
 import org.apache.doris.thrift.TMetadataType;
 import org.apache.doris.thrift.TNullableStringLiteral;
 import org.apache.doris.thrift.TSchemaTableName;
+import org.apache.doris.thrift.TSchemaTableRequestParams;
 import org.apache.doris.thrift.TShowUserRequest;
 import org.apache.doris.thrift.TShowUserResult;
 import org.apache.doris.thrift.TStatusCode;
@@ -57,6 +59,7 @@ import org.junit.rules.ExpectedException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.UUID;
 import java.util.stream.Collectors;
@@ -247,4 +250,57 @@ public class FrontendServiceImplTest {
         TShowUserResult result = impl.showUser(request);
         System.out.println(result);
     }
+
+    @Test
+    public void testFetchAuthenticationIntegrationsSchemaTableData() throws 
Exception {
+        String integrationName = "test_authentication_integration";
+        
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
 true);
+
+        LinkedHashMap<String, String> properties = new LinkedHashMap<>();
+        properties.put("type", "ldap");
+        properties.put("server", "ldap://127.0.0.1:389";);
+        properties.put("bind_password", "plain_secret");
+        properties.put("secret.endpoint", "masked_by_prefix");
+        Env.getCurrentEnv().getAuthenticationIntegrationMgr()
+                .createAuthenticationIntegration(
+                        integrationName, false, properties, "ldap comment", 
connectContext.getQualifiedUser());
+
+        try {
+            FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+            TFetchSchemaTableDataRequest request = new 
TFetchSchemaTableDataRequest();
+            
request.setSchemaTableName(TSchemaTableName.AUTHENTICATION_INTEGRATIONS);
+            TSchemaTableRequestParams params = new TSchemaTableRequestParams();
+            
params.setCurrentUserIdent(connectContext.getCurrentUserIdentity().toThrift());
+            request.setSchemaTableParams(params);
+
+            TFetchSchemaTableDataResult result = 
impl.fetchSchemaTableData(request);
+            Assert.assertEquals(TStatusCode.OK, 
result.getStatus().getStatusCode());
+
+            List<String> rowValues = result.getDataBatch().stream()
+                    .filter(row -> 
integrationName.equals(row.getColumnValue().get(0).getStringVal()))
+                    .map(row -> row.getColumnValue().stream()
+                            .map(cell -> cell.isSetStringVal() ? 
cell.getStringVal() : null)
+                            .collect(Collectors.toList()))
+                    .findFirst()
+                    .orElseThrow(() -> new 
java.lang.AssertionError("authentication integration row not found"));
+
+            Assert.assertEquals(integrationName, rowValues.get(0));
+            Assert.assertEquals("ldap", rowValues.get(1));
+            Assert.assertTrue(rowValues.get(2).contains("\"server\" = 
\"ldap://127.0.0.1:389\"";));
+            Assert.assertTrue(rowValues.get(2).contains(
+                    "\"bind_password\" = \"" + PrintableMap.PASSWORD_MASK + 
"\""));
+            Assert.assertTrue(rowValues.get(2).contains(
+                    "\"secret.endpoint\" = \"" + PrintableMap.PASSWORD_MASK + 
"\""));
+            Assert.assertFalse(rowValues.get(2).contains("plain_secret"));
+            Assert.assertFalse(rowValues.get(2).contains("masked_by_prefix"));
+            Assert.assertEquals("ldap comment", rowValues.get(3));
+            Assert.assertEquals(connectContext.getQualifiedUser(), 
rowValues.get(4));
+            Assert.assertNotNull(rowValues.get(5));
+            Assert.assertFalse(rowValues.get(5).isEmpty());
+            Assert.assertEquals(connectContext.getQualifiedUser(), 
rowValues.get(6));
+            Assert.assertEquals(rowValues.get(5), rowValues.get(7));
+        } finally {
+            
Env.getCurrentEnv().getAuthenticationIntegrationMgr().dropAuthenticationIntegration(integrationName,
 true);
+        }
+    }
 }
diff --git a/gensrc/thrift/Descriptors.thrift b/gensrc/thrift/Descriptors.thrift
index fb5fe5109f3..ea915a5c7a8 100644
--- a/gensrc/thrift/Descriptors.thrift
+++ b/gensrc/thrift/Descriptors.thrift
@@ -212,6 +212,7 @@ enum TSchemaTableType {
     SCH_BLACKHOLE = 62;
     SCH_COLUMN_DATA_SIZES = 63;
     SCH_LOAD_JOBS = 64;
+    SCH_AUTHENTICATION_INTEGRATIONS = 65;
 }
 
 enum THdfsCompression {
diff --git a/gensrc/thrift/FrontendService.thrift 
b/gensrc/thrift/FrontendService.thrift
index f4b155a209a..641624f4d12 100644
--- a/gensrc/thrift/FrontendService.thrift
+++ b/gensrc/thrift/FrontendService.thrift
@@ -868,6 +868,7 @@ enum TSchemaTableName {
   PARTITIONS = 10,
   VIEW_DEPENDENCY = 11,
   SQL_BLOCK_RULE_STATUS = 12,
+  AUTHENTICATION_INTEGRATIONS = 13,
 }
 
 struct TMetadataTableRequestParams {
diff --git 
a/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy 
b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy
new file mode 100644
index 00000000000..caec26467d2
--- /dev/null
+++ b/regression-test/suites/auth_p0/test_authentication_integration_auth.groovy
@@ -0,0 +1,109 @@
+// 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.
+
+suite("test_authentication_integration_auth", "p0,auth") {
+    String suiteName = "test_authentication_integration_auth"
+    String integrationName = "${suiteName}_ldap"
+
+    try_sql("DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}")
+
+    try {
+        test {
+            sql """
+                CREATE AUTHENTICATION INTEGRATION ${integrationName}
+                 PROPERTIES ('ldap.server'='ldap://127.0.0.1:389')
+            """
+            exception "Property 'type' is required"
+        }
+
+        sql """
+            CREATE AUTHENTICATION INTEGRATION ${integrationName}
+             PROPERTIES (
+                'type'='ldap',
+                'ldap.server'='ldap://127.0.0.1:389',
+                'ldap.admin_password'='123456',
+                'secret.endpoint'='secret_create_value'
+            )
+            COMMENT 'for regression test'
+        """
+
+        test {
+            sql """
+                CREATE AUTHENTICATION INTEGRATION ${integrationName}
+                 PROPERTIES ('type'='ldap', 
'ldap.server'='ldap://127.0.0.1:1389')
+            """
+            exception "already exists"
+        }
+
+        test {
+            sql """
+                ALTER AUTHENTICATION INTEGRATION ${integrationName}
+                SET PROPERTIES ('type'='oidc')
+            """
+            exception "does not allow modifying property 'type'"
+        }
+
+        sql """
+            ALTER AUTHENTICATION INTEGRATION ${integrationName}
+            SET PROPERTIES (
+                'ldap.server'='ldap://127.0.0.1:1389',
+                'ldap.admin_password'='abcdef',
+                'secret.endpoint'='secret_alter_value'
+            )
+        """
+
+        sql """ALTER AUTHENTICATION INTEGRATION ${integrationName} SET COMMENT 
'updated comment'"""
+
+        def result = sql """
+            SELECT
+                NAME,
+                TYPE,
+                PROPERTIES,
+                COMMENT,
+                CREATE_USER,
+                CREATE_TIME,
+                ALTER_USER,
+                MODIFY_TIME
+            FROM information_schema.authentication_integrations
+            WHERE NAME = '${integrationName}'
+            ORDER BY NAME
+        """
+        assertEquals(1, result.size())
+        assertEquals(8, result[0].size())
+        assertEquals(integrationName, result[0][0])
+        assertEquals("ldap", result[0][1])
+        assertTrue(result[0][2].contains("\"ldap.server\" = 
\"ldap://127.0.0.1:1389\"";))
+        assertTrue(result[0][2].contains("\"ldap.admin_password\" = \"*XXX\""))
+        assertTrue(result[0][2].contains("\"secret.endpoint\" = \"*XXX\""))
+        assertTrue(!result[0][2].contains("abcdef"))
+        assertTrue(!result[0][2].contains("secret_alter_value"))
+        assertEquals("updated comment", result[0][3])
+        assertTrue(result[0][4] != null && result[0][4].length() > 0)
+        assertTrue(result[0][5] != null && result[0][5].length() > 0)
+        assertTrue(result[0][6] != null && result[0][6].length() > 0)
+        assertTrue(result[0][7] != null && result[0][7].length() > 0)
+
+        test {
+            sql """DROP AUTHENTICATION INTEGRATION 
${integrationName}_not_exist"""
+            exception "does not exist"
+        }
+
+        sql """DROP AUTHENTICATION INTEGRATION IF EXISTS 
${integrationName}_not_exist"""
+    } finally {
+        try_sql("DROP AUTHENTICATION INTEGRATION IF EXISTS ${integrationName}")
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to