You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by ya...@apache.org on 2021/07/30 01:25:00 UTC

[incubator-doris] branch master updated: [Feature][LDAP] Add LDAP authentication login and LDAP group authorization support. (#6333)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 9ca369a  [Feature][LDAP] Add LDAP authentication login and LDAP group authorization support. (#6333)
9ca369a is described below

commit 9ca369aa58ef6215e2c79b14fc1b4edfc2e2d720
Author: luozenglin <37...@users.noreply.github.com>
AuthorDate: Fri Jul 30 09:24:50 2021 +0800

    [Feature][LDAP] Add LDAP authentication login and LDAP group authorization support. (#6333)
    
    * [Feature][LDAP] Add LDAP authentication login and LDAP group authorization support.
    
    * Update docs/.vuepress/sidebar/en.js
    
    Co-authored-by: Mingyu Chen <mo...@gmail.com>
    
    Co-authored-by: Mingyu Chen <mo...@gmail.com>
---
 docs/.vuepress/sidebar/en.js                       |   2 +
 docs/.vuepress/sidebar/zh-CN.js                    |   2 +
 docs/en/administrator-guide/ldap.md                | 175 ++++++++++++++
 .../Administration/SET LDAP_ADMIN_PASSWORD.md      |  45 ++++
 docs/zh-CN/administrator-guide/ldap.md             | 176 ++++++++++++++
 .../Administration/SET LDAP_ADMIN_PASSWORD.md      |  45 ++++
 fe/conf/ldap.conf                                  |  54 +++++
 fe/fe-core/src/main/cup/sql_parser.cup             |   8 +-
 .../src/main/java/org/apache/doris/PaloFe.java     |  12 +-
 .../org/apache/doris/analysis/SetLdapPassVar.java  |  63 +++++
 .../java/org/apache/doris/common/ConfigBase.java   |  36 ++-
 .../java/org/apache/doris/common/ErrorCode.java    |   6 +-
 .../java/org/apache/doris/common/LdapConfig.java   | 145 ++++++++++++
 .../doris/common/util/SymmetricEncryption.java     |  76 ++++++
 .../org/apache/doris/journal/JournalEntity.java    |   6 +
 .../org/apache/doris/ldap/LdapAuthenticate.java    | 145 ++++++++++++
 .../java/org/apache/doris/ldap/LdapClient.java     | 182 ++++++++++++++
 .../org/apache/doris/ldap/LdapPrivsChecker.java    | 263 +++++++++++++++++++++
 .../apache/doris/mysql/MysqlAuthSwitchPacket.java  |  31 +++
 .../apache/doris/mysql/MysqlClearTextPacket.java   |  40 ++++
 .../java/org/apache/doris/mysql/MysqlProto.java    | 172 ++++++++++----
 .../org/apache/doris/mysql/privilege/PaloAuth.java | 231 ++++++++++++++----
 .../org/apache/doris/mysql/privilege/PaloRole.java |   9 +-
 .../doris/mysql/privilege/UserPrivTable.java       |  11 +
 .../java/org/apache/doris/persist/EditLog.java     |   9 +
 .../java/org/apache/doris/persist/LdapInfo.java    |  51 ++++
 .../org/apache/doris/persist/OperationType.java    |   2 +
 .../java/org/apache/doris/qe/ConnectContext.java   |  15 ++
 .../java/org/apache/doris/qe/ConnectScheduler.java |   7 +-
 .../main/java/org/apache/doris/qe/SetExecutor.java |   4 +
 fe/fe-core/src/main/jflex/sql_scanner.flex         |   1 +
 .../apache/doris/ldap/LdapAuthenticateTest.java    | 212 +++++++++++++++++
 .../java/org/apache/doris/ldap/LdapClientTest.java | 102 ++++++++
 .../apache/doris/ldap/LdapPrivsCheckerTest.java    | 189 +++++++++++++++
 .../org/apache/doris/mysql/MysqlProtoTest.java     |  88 ++++++-
 .../org/apache/doris/persist/LdapInfoTest.java     |  35 +++
 36 files changed, 2532 insertions(+), 118 deletions(-)

diff --git a/docs/.vuepress/sidebar/en.js b/docs/.vuepress/sidebar/en.js
index 8f26f16..8d171bb 100644
--- a/docs/.vuepress/sidebar/en.js
+++ b/docs/.vuepress/sidebar/en.js
@@ -180,6 +180,7 @@ module.exports = [
       "export-manual",
       "outfile",
       "privilege",
+      "ldap",
       "resource-management",
       "running-profile",
       "runtime-filter",
@@ -443,6 +444,7 @@ module.exports = [
               "INSTALL PLUGIN",
               "LINK DATABASE",
               "MIGRATE DATABASE",
+              "SET LDAP_ADMIN_PASSWORD",
               "SHOW BACKENDS",
               "SHOW BROKER",
               "SHOW FILE",
diff --git a/docs/.vuepress/sidebar/zh-CN.js b/docs/.vuepress/sidebar/zh-CN.js
index 3bfdf15..0f16e39 100644
--- a/docs/.vuepress/sidebar/zh-CN.js
+++ b/docs/.vuepress/sidebar/zh-CN.js
@@ -181,6 +181,7 @@ module.exports = [
       "outfile",
       "partition_cache",
       "privilege",
+      "ldap",
       "resource-management",
       "running-profile",
       "runtime-filter",
@@ -448,6 +449,7 @@ module.exports = [
               "INSTALL PLUGIN",
               "LINK DATABASE",
               "MIGRATE DATABASE",
+              "SET LDAP_ADMIN_PASSWORD",
               "SHOW BACKENDS",
               "SHOW BROKER",
               "SHOW FILE",
diff --git a/docs/en/administrator-guide/ldap.md b/docs/en/administrator-guide/ldap.md
new file mode 100644
index 0000000..cb3cb25
--- /dev/null
+++ b/docs/en/administrator-guide/ldap.md
@@ -0,0 +1,175 @@
+---
+{
+    "title": "LDAP",
+    "language": "en"
+}
+---
+
+<!-- 
+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.
+-->
+
+# LDAP
+
+Access to third-party LDAP services to provide authentication login and group authorization services for Doris.
+
+LDAP authentication login complements Doris authentication login by accessing the LDAP service for password authentication; Doris uses LDAP to authenticate the user's password first; if the user does not exist in the LDAP service, it continues to use Doris to authenticate the password; if the LDAP password is correct but there is no corresponding account in Doris, a temporary user is created to log in to Doris.
+
+LDAP group authorization, is to map the group in LDAP to the Role in Doris, if the user belongs to multiple user groups in LDAP, after logging into Doris the user will get the permission of all groups corresponding to the Role, requiring the group name to be the same as the Role name.
+
+## Noun Interpretation
+
+* LDAP: Lightweight directory access protocol that enables centralized management of account passwords.
+* Privilege: Permissions act on nodes, databases or tables. Different permissions represent different permission to operate.
+* Role: Doris can create custom named roles. A role can be thought of as a collection of permissions.
+
+## Enable LDAP Authentication
+### Server-side Configuration
+
+You need to configure the LDAP basic information in the fe/conf/ldap.conf file, and the LDAP administrator password needs to be set using sql statements.
+
+#### Configure the fe/conf/ldap.conf file:
+* ldap_authentication_enabled = false  
+  Set the value to "true" to enable LDAP authentication; when the value is "false", LDAP authentication is not enabled and all other configuration items of this profile are invalid.Set the value to "true" to enable LDAP authentication; when the value is "false", LDAP authentication is not enabled and all other configuration items of this profile are invalid.
+
+* ldap_host = 127.0.0.1  
+  LDAP service ip.
+  
+* ldap_port = 389  
+  LDAP service port, the default plaintext transfer port is 389, currently Doris' LDAP function only supports plaintext password transfer.
+  
+* ldap_admin_name = cn=admin,dc=domain,dc=com  
+  LDAP administrator account "Distinguished Name". When a user logs into Doris using LDAP authentication, Doris will bind the administrator account to search for user information in LDAP.
+  
+* ldap_user_basedn = ou=people,dc=domain,dc=com
+  Doris base dn when searching for user information in LDAP.
+  
+* ldap_user_filter = (&(uid={login}))
+
+  For Doris' filtering criteria when searching for user information in LDAP, the placeholder "{login}" will be replaced with the login username. You must ensure that the user searched by this filter is unique, otherwise Doris will not be able to verify the password through LDAP and the error message "ERROR 5081 (42000): user is not unique in LDAP server." will appear when logging in.
+  
+  For example, if you use the LDAP user node uid attribute as the username to log into Doris, you can configure it as:    
+  ldap_user_filter = (&(uid={login}));  
+  This item can be configured using the LDAP user mailbox prefix as the user name:   
+  ldap_user_filter = (&(mail={login}@baidu.com))。
+
+* ldap_group_basedn = ou=group,dc=domain,dc=com
+  base dn when Doris searches for group information in LDAP. if this item is not configured, LDAP group authorization will not be enabled.  
+
+#### Set the LDAP administrator password:
+After configuring the ldap.conf file, start fe, log in to Doris with the root or admin account, and execute sql:  
+```
+set ldap_admin_password = 'ldap_admin_password';
+```
+
+### Client-side configuration  
+Client-side LDAP authentication requires the mysql client-side explicit authentication plugin to be enabled. Logging into Doris using the command line enables the mysql explicit authentication plugin in one of two ways.
+
+* Set the environment variable LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN to value 1.
+  For example, in a linux or max environment you can use the command:
+  ```
+  echo "export LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN=1" >> ~/.bash_profile && source ~/.bash_profile
+  ```
+
+* Add the parameter "--enable-cleartext-plugin" each time you log in to Doris.
+  ```
+  mysql -hDORIS_HOST -PDORIS_PORT -u user -p --enable-cleartext-plugin
+
+  Enter ldap password
+  ```
+
+## LDAP authentication detailed explanation
+LDAP password authentication and group authorization are complementary to Doris password authentication and authorization. Enabling LDAP functionality does not completely replace Doris password authentication and authorization, but coexists with Doris password authentication and authorization.
+
+### LDAP authentication login details
+When LDAP is enabled, users have the following in Doris and DLAP:  
+
+|LDAP User|Doris User|Password|Login Status|Login to Doris users|
+|--|--|--|--|--|
+|Existent|Existent|LDAP Password|Login successful|Doris User|
+|Existent|Existent|Doris Password|Login failure|None|
+|Non-Existent|Existent|Doris Password|Login successful|Doris User|
+|Existent|Non-Existent|LDAP Password|Login successful|Ldap Temporary user|
+
+After LDAP is enabled, when a user logs in using mysql client, Doris will first verify the user's password through the LDAP service, and if the LDAP user exists and the password is correct, Doris will use the user to log in; at this time, if the corresponding account exists, Doris will directly log in to the account, and if the corresponding account does not exist, it will create a temporary account for the user and log in to the account. The temporary account has the appropriate pair of [...]
+If no login user exists in the LDAP service, Doris is used for password authentication.
+
+The following assumes that LDAP authentication is enabled, ldap_user_filter = (&(uid={login})) is configured, and all other configuration items are correct, and the client sets the environment variable LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN=1  
+
+For example:
+
+#### 1:Accounts exist in both Doris and LDAP.
+
+Doris account exists: jack@'172.10.1.10', password: 123456  
+LDAP user node presence attribute: uid: jack user password: abcdef  
+The jack@'172.10.1.10' account can be logged into by logging into Doris using the following command:
+```
+mysql -hDoris_HOST -PDoris_PORT -ujack -p abcdef
+```
+
+Login will fail with the following command:  
+```
+mysql -hDoris_HOST -PDoris_PORT -ujack -p 123456
+```
+
+#### 2:The user exists in LDAP and the corresponding account does not exist in Doris.
+
+LDAP user node presence attribute: uid: jack User password: abcdef  
+Use the following command to create a temporary user and log in to jack@'%', the temporary user has basic privileges DatabasePrivs: Select_priv, Doris will delete the temporary user after the user logs out and logs in:  
+```
+mysql -hDoris_HOST -PDoris_PORT -ujack -p abcdef
+```
+
+#### 3:LDAP does not exist for the user.
+
+Doris account exists: jack@'172.10.1.10', password: 123456  
+Login to the account using the Doris password, successfully:  
+```
+mysql -hDoris_HOST -PDoris_PORT -ujack -p 123456
+```
+
+### LDAP group authorization details
+
+If a DLAP user dn is the "member" attribute of an LDAP group node, Doris assumes that the user belongs to the group. Doris will revoke the corresponding role privileges after the user logs out. Before using LDAP group authorization, you should create the corresponding role pairs in Doris and authorize the roles.
+
+Login user Privileges are related to Doris user and group Privileges, as shown in the following table:  
+|LDAP Users|Doris Users|Login User Privileges|
+|--|--|--|
+|exist|exist|LDAP group Privileges + Doris user Privileges|
+|Does not exist|Exists|Doris user Privileges|
+|exist|non-exist|LDAP group Privileges|
+
+If the logged-in user is a temporary user and no group permission exists, the user has the select_priv permission of the information_schema by default
+
+Example:  
+LDAP user dn is the "member" attribute of the LDAP group node then the user is considered to belong to the group, Doris will intercept the first Rdn of group dn as the group name.  
+For example, if user dn is "uid=jack,ou=aidp,dc=domain,dc=com", the group information is as follows:  
+```
+dn: cn=doris_rd,ou=group,dc=domain,dc=com  
+objectClass: groupOfNames  
+member: uid=jack,ou=aidp,dc=domain,dc=com  
+```
+Then the group name is doris_rd.
+
+If jack also belongs to the LDAP groups doris_qa, doris_pm; Doris exists roles: doris_rd, doris_qa, doris_pm, after logging in using LDAP authentication, the user will not only have the original permissions of the account, but will also get the roles doris_rd, doris_qa and doris _pm privileges.
+
+## Limitations of LDAP authentication
+
+* The current LDAP feature of Doris only supports plaintext password authentication, that is, when a user logs in, the password is transmitted in plaintext between client and fe and between fe and LDAP service.
+* The current LDAP authentication only supports password authentication under mysql protocol. If you use the Http interface, you cannot use LDAP users for authentication.
+* Temporary users do not have user properties.
\ No newline at end of file
diff --git a/docs/en/sql-reference/sql-statements/Administration/SET LDAP_ADMIN_PASSWORD.md b/docs/en/sql-reference/sql-statements/Administration/SET LDAP_ADMIN_PASSWORD.md
new file mode 100644
index 0000000..30ba218
--- /dev/null
+++ b/docs/en/sql-reference/sql-statements/Administration/SET LDAP_ADMIN_PASSWORD.md	
@@ -0,0 +1,45 @@
+---
+{
+    "title": "SET LDAP_ADMIN_PASSWORD",
+    "language": "en"
+}
+---
+
+<!-- 
+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.
+-->
+
+# SET LDAP_ADMIN_PASSWORD
+## description
+
+Syntax:
+
+    SET LDAP_ADMIN_PASSWORD = 'plain password'
+
+    The SET command is used to set the LDAP administrator password. When using LDAP authentication, doris needs to use the administrator account and password to query the LDAP service for information about the logging user.
+
+## example
+
+1. Set LDAP admin password:
+```
+SET LDAP_ADMIN_PASSWORD = '123456'
+```
+
+## keyword
+    SET, LDAP, LDAP_ADMIN_PASSWORD
+
diff --git a/docs/zh-CN/administrator-guide/ldap.md b/docs/zh-CN/administrator-guide/ldap.md
new file mode 100644
index 0000000..aa35ef0
--- /dev/null
+++ b/docs/zh-CN/administrator-guide/ldap.md
@@ -0,0 +1,176 @@
+---
+{
+    "title": "LDAP",
+    "language": "zh-CN"
+}
+---
+
+<!-- 
+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.
+-->
+
+# LDAP
+
+接入第三方LDAP服务为Doris提供验证登录和组授权服务。
+
+LDAP验证登录指的是接入LDAP服务的密码验证来补充Doris的验证登录。Doris优先使用LDAP验证用户密码,如果LDAP服务中不存在该用户则继续使用Doris验证密码,如果LDAP密码正确但是Doris中没有对应账户则创建临时用户登录Doris。
+
+LDAP组授权是将LDAP中的group映射到Doris中的Role,如果用户在LDAP中属于多个用户组,登录Doris后用户将获得所有组对应Role的权限,要求组名与Role名字相同。
+
+## 名词解释
+
+* LDAP: 轻量级目录访问协议,能够实现账号密码的集中管理。
+* 权限 Privilege:权限作用的对象是节点、数据库或表。不同的权限代表不同的操作许可。
+* 角色 Role:Doris可以创建自定义命名的角色。角色可以被看做是一组权限的集合。
+
+## 启用LDAP认证
+### server端配置
+
+需要在fe/conf/ldap.conf文件中配置LDAP基本信息,另有LDAP管理员密码需要使用sql语句进行设置。
+
+#### 配置fe/conf/ldap.conf文件:
+* ldap_authentication_enabled = false  
+  设置值为“true”启用LDAP验证;当值为“false”时,不启用LDAP验证,该配置文件的其他配置项都无效。
+  
+* ldap_host = 127.0.0.1  
+  LDAP服务ip。
+  
+* ldap_port = 389  
+  LDAP服务端口,默认明文传输端口为389,目前Doris的LDAP功能仅支持明文密码传输。
+  
+* ldap_admin_name = cn=admin,dc=domain,dc=com    
+  LDAP管理员账户“Distinguished Name”。当用户使用LDAP验证登录Doris时,Doris会绑定该管理员账户在LDAP中搜索用户信息。
+  
+* ldap_user_basedn = ou=people,dc=domain,dc=com  
+  Doris在LDAP中搜索用户信息时的base dn。
+  
+* ldap_user_filter = (&(uid={login}))  
+* 
+  Doris在LDAP中搜索用户信息时的过滤条件,占位符“{login}”会被替换为登录用户名。必须保证通过该过滤条件搜索的用户唯一,否则Doris无法通过LDAP验证密码,登录时会出现“ERROR 5081 (42000): user is not unique in LDAP server.”的错误信息。
+  
+  例如使用LDAP用户节点uid属性作为登录Doris的用户名可以配置该项为:  
+  ldap_user_filter = (&(uid={login}));  
+  使用LDAP用户邮箱前缀作为用户名可配置该项:  
+  ldap_user_filter = (&(mail={login}@baidu.com))。
+
+* ldap_group_basedn = ou=group,dc=domain,dc=com  
+  Doris在LDAP中搜索组信息时的base dn。如果不配置该项,将不启用LDAP组授权。
+
+#### 设置LDAP管理员密码:
+配置好ldap.conf文件后启动fe,使用root或admin账号登录Doris,执行sql:
+```
+set ldap_admin_password = 'ldap_admin_password';
+```
+
+### Client端配置
+客户端使用LDAP验证需要启用mysql客户端明文验证插件,使用命令行登录Doris可以使用下面两种方式之一启用mysql明文验证插件:
+
+* 设置环境变量LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN值1。
+  
+  例如在linux或者max环境中可以使用:
+  ```
+  echo "export LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN=1" >> ~/.bash_profile && source ~/.bash_profile
+  ```
+  
+* 每次登录Doris时添加参数“--enable-cleartext-plugin”:
+  ```
+  mysql -hDORIS_HOST -PDORIS_PORT -u user -p --enable-cleartext-plugin
+
+  输入ldap密码
+  ```
+  
+## LDAP认证详解
+LDAP密码验证和组授权是Doris密码验证和授权的补充,开启LDAP功能并不能完全替代Doris的密码验证和授权,而是与Doris密码验证和授权并存。
+
+### LDAP验证登录详解
+开启LDAP后,用户在Doris和DLAP中存在一下几种情况:  
+
+|LDAP用户|Doris用户|密码|登录情况|登录Doris的用户|
+|--|--|--|--|--|
+|存在|存在|LDAP密码|登录成功|Doris用户|
+|存在|存在|Doris密码|登录失败|无|
+|不存在|存在|Doris密码|登录成功|Doris用户|
+|存在|不存在|LDAP密码|登录成功|Ldap临时用户|
+
+开启LDAP后,用户使用mysql client登录时,Doris会先通过LDAP服务验证用户密码,如果LDAP存在用户且密码正确,Doris则使用该用户登录;此时Doris若存在对应账户则直接登录该账户,如果不存在对应账户则为用户创建临时账户并登录该账户。临时账户具有具有相应对权限(参见LDAP组授权),仅对当前连接有效,doris不会创建该用户,也不会产生创建用户对元数据。  
+如果LDAP服务中不存在登录用户,则使用Doris进行密码认证。
+
+以下假设已开启LDAP认证,配置ldap_user_filter = (&(uid={login})),且其他配置项都正确,客户端设置环境变量LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN=1
+
+例如:
+
+#### 1:Doris和LDAP中都存在账户:
+
+存在Doris账户:jack@'172.10.1.10',密码:123456  
+LDAP用户节点存在属性:uid: jack 用户密码:abcdef  
+使用以下命令登录Doris可以登录jack@'172.10.1.10'账户:
+```
+mysql -hDoris_HOST -PDoris_PORT -ujack -p abcdef
+```
+
+使用以下命令将登录失败:
+```
+mysql -hDoris_HOST -PDoris_PORT -ujack -p 123456
+```
+
+#### 2:LDAP中存在用户,Doris中不存在对应账户:
+
+LDAP用户节点存在属性:uid: jack 用户密码:abcdef  
+使用以下命令创建临时用户并登录jack@'%',临时用户具有基本权限 DatabasePrivs:Select_priv, 用户退出登录后Doris将删除该临时用户:
+```
+mysql -hDoris_HOST -PDoris_PORT -ujack -p abcdef
+```
+
+#### 3:LDAP不存在用户:
+
+存在Doris账户:jack@'172.10.1.10',密码:123456  
+使用Doris密码登录账户,成功:
+```
+mysql -hDoris_HOST -PDoris_PORT -ujack -p 123456
+```
+
+### LDAP组授权详解
+
+DLAP用户dn是LDAP组节点的“member”属性则Doris认为用户属于该组。LDAP组授权是将LDAP中的group映射到Doris中的role,并将所有对应的role权限授予登录用户,用户退出登录后Doris会撤销对应的role权限。在使用LDAP组授权前应该在Doris中创建相应对role,并为role授权。
+
+登录用户权限跟Doris用户和组权限有关,见下表:
+|LDAP用户|Doris用户|登录用户的权限|
+|--|--|--|
+|存在|存在|LDAP组权限 + Doris用户权限|
+|不存在|存在|Doris用户权限|
+|存在|不存在|LDAP组权限|
+
+如果登录的用户为临时用户,且不存在组权限,则该用户默认具有information_schema的select_priv权限
+
+举例:  
+LDAP用户dn是LDAP组节点的“member”属性则认为用户属于该组,Doris会截取组dn的第一个Rdn作为组名。  
+例如用户dn为“uid=jack,ou=aidp,dc=domain,dc=com”, 组信息如下:  
+```
+dn: cn=doris_rd,ou=group,dc=domain,dc=com  
+objectClass: groupOfNames  
+member: uid=jack,ou=aidp,dc=domain,dc=com  
+```
+则组名为doris_rd。
+
+假如jack还属于LDAP组doris_qa、doris_pm;Doris存在role:doris_rd、doris_qa、doris_pm,在使用LDAP验证登录后,用户不但具有该账户原有的权限,还将获得role doris_rd、doris_qa和doris_pm的权限。
+
+## LDAP验证的局限
+
+* 目前Doris的LDAP功能只支持明文密码验证,即用户登录时,密码在client与fe之间、fe与LDAP服务之间以明文的形式传输。
+* 当前的LDAP验证只支持在mysql协议下进行密码验证,如果使用Http接口则无法使用LDAP用户进行验证。
+* 临时用户不具有用户属性。
\ No newline at end of file
diff --git a/docs/zh-CN/sql-reference/sql-statements/Administration/SET LDAP_ADMIN_PASSWORD.md b/docs/zh-CN/sql-reference/sql-statements/Administration/SET LDAP_ADMIN_PASSWORD.md
new file mode 100644
index 0000000..cd3b600
--- /dev/null
+++ b/docs/zh-CN/sql-reference/sql-statements/Administration/SET LDAP_ADMIN_PASSWORD.md	
@@ -0,0 +1,45 @@
+---
+{
+    "title": "SET LDAP_ADMIN_PASSWORD",
+    "language": "zh-CN"
+}
+---
+
+<!-- 
+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.
+-->
+
+# SET LDAP_ADMIN_PASSWORD
+## description
+
+Syntax:
+
+    SET LDAP_ADMIN_PASSWORD = 'plain password'
+
+    SET LDAP_ADMIN_PASSWORD 命令用于设置LDAP管理员密码。使用LDAP认证时,doris需使用管理员账户和密码来向LDAP服务查询登录用户的信息。
+
+## example
+
+1. 设置LDAP管理员密码
+```
+SET LDAP_ADMIN_PASSWORD = '123456'
+```
+
+## keyword
+    SET, LDAP, LDAP_ADMIN_PASSWORD
+
diff --git a/fe/conf/ldap.conf b/fe/conf/ldap.conf
new file mode 100644
index 0000000..4c36566
--- /dev/null
+++ b/fe/conf/ldap.conf
@@ -0,0 +1,54 @@
+# 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.
+
+#####################################################################
+## To see all LDAP configurations,
+## see fe/src/org/apache/doris/common/LdapConfig.java
+#####################################################################
+
+# LDAP server configuration
+# If you want to enable LDAP authentication, you should:
+## step1: Configure the following parameters:
+# ldap_authentication_enabled - Flag to enable LDAP authentication. Specify 'true' to enable ldap authentication.
+# ldap_host - LDAP server ip.
+# ldap_port - LDAP server port.
+# ldap_admin_name - The DN to bind as connection, this admin DN will be used to lookup information about other users.
+# ldap_user_basedn - Search base for users.
+# ldap_user_filter - User lookup filter, the placeholder {login} will be replaced by the user supplied login.
+# ldap_group_basedn - Search base for groups.
+## step2: Restart fe, and use root or admin account to log in to doris.
+## setp3: Execute sql statement to set ldap admin password:
+# set ldap_admin_password = 'password';
+ldap_authentication_enabled = false
+ldap_host = 127.0.0.1
+ldap_port = 389
+ldap_admin_name = cn=admin,dc=domain,dc=com
+ldap_user_basedn = ou=people,dc=domain,dc=com
+ldap_user_filter = (&(uid={login}))
+ldap_group_basedn = ou=group,dc=domain,dc=com
+
+# LDAP pool configuration
+# https://docs.spring.io/spring-ldap/docs/2.3.3.RELEASE/reference/#pool-configuration
+#max_active = 8
+#max_total = -1
+#max_idle = 8
+#min_idle = 0
+#max_wait = -1
+#when_exhausted = 1
+#test_on_borrow = false
+#test_on_return = false
+#test_while_idle = false
diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup
index 5b670f3..a846807 100644
--- a/fe/fe-core/src/main/cup/sql_parser.cup
+++ b/fe/fe-core/src/main/cup/sql_parser.cup
@@ -254,7 +254,7 @@ terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_A
     KW_MAP, KW_MATERIALIZED, KW_MAX, KW_MAX_VALUE, KW_MERGE, KW_MIN, KW_MINUTE, KW_MINUS, KW_MIGRATE, KW_MIGRATIONS, KW_MODIFY, KW_MONTH,
     KW_NAME, KW_NAMED_STRUCT, KW_NAMES, KW_NEGATIVE, KW_NO, KW_NOT, KW_NULL, KW_NULLS,
     KW_OBSERVER, KW_OFFSET, KW_ON, KW_ONLY, KW_OPEN, KW_OR, KW_ORDER, KW_OUTER, KW_OUTFILE, KW_OVER,
-    KW_PARTITION, KW_PARTITIONS, KW_PASSWORD, KW_PATH, KW_PAUSE, KW_PIPE, KW_PRECEDING,
+    KW_PARTITION, KW_PARTITIONS, KW_PASSWORD, KW_LDAP_ADMIN_PASSWORD, KW_PATH, KW_PAUSE, KW_PIPE, KW_PRECEDING,
     KW_PLUGIN, KW_PLUGINS,
     KW_PROC, KW_PROCEDURE, KW_PROCESSLIST, KW_PROFILE, KW_PROPERTIES, KW_PROPERTY,
     KW_QUERY, KW_QUOTA,
@@ -3352,6 +3352,10 @@ option_value_no_option_type ::=
     {:
         RESULT = new SetPassVar(userId, passwd);
     :}
+    | KW_LDAP_ADMIN_PASSWORD equal text_or_password:passwd
+    {:
+        RESULT = new SetLdapPassVar(passwd);
+    :}
     ;
 
 variable_name ::=
@@ -5078,6 +5082,8 @@ keyword ::=
     {: RESULT = id; :}
     | KW_PASSWORD:id
     {: RESULT = id; :}
+    | KW_LDAP_ADMIN_PASSWORD:id
+    {: RESULT = id; :}
     | KW_PLUGIN:id
     {: RESULT = id; :}
     | KW_PLUGINS:id
diff --git a/fe/fe-core/src/main/java/org/apache/doris/PaloFe.java b/fe/fe-core/src/main/java/org/apache/doris/PaloFe.java
index 9d4cc9d..c1aa1e9 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/PaloFe.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/PaloFe.java
@@ -20,6 +20,7 @@ package org.apache.doris;
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.common.CommandLineOptions;
 import org.apache.doris.common.Config;
+import org.apache.doris.common.LdapConfig;
 import org.apache.doris.common.Log4jConfig;
 import org.apache.doris.common.ThreadPoolManager;
 import org.apache.doris.common.Version;
@@ -33,9 +34,6 @@ import org.apache.doris.service.ExecuteEnv;
 import org.apache.doris.service.FeServer;
 import org.apache.doris.service.FrontendOptions;
 
-import com.google.common.base.Charsets;
-import com.google.common.base.Strings;
-
 import org.apache.commons.cli.BasicParser;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
@@ -44,6 +42,9 @@ import org.apache.commons.cli.ParseException;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
@@ -88,6 +89,11 @@ public class PaloFe {
             // Because the path of custom config file is defined in fe.conf
             config.initCustom(Config.custom_config_dir + "/fe_custom.conf");
 
+            LdapConfig ldapConfig = new LdapConfig();
+            if (new File(dorisHomeDir + "/conf/ldap.conf").exists()) {
+                ldapConfig.init(dorisHomeDir + "/conf/ldap.conf");
+            }
+
             // check it after Config is initialized, otherwise the config 'check_java_version' won't work.
             if (!JdkUtils.checkJavaVersion()) {
                 throw new IllegalArgumentException("Java version doesn't match");
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/SetLdapPassVar.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/SetLdapPassVar.java
new file mode 100644
index 0000000..cb86aed
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SetLdapPassVar.java
@@ -0,0 +1,63 @@
+// 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.analysis;
+
+import com.google.common.base.Strings;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.util.SymmetricEncryption;
+import org.apache.doris.mysql.privilege.PaloAuth;
+import org.apache.doris.qe.ConnectContext;
+
+public class SetLdapPassVar extends SetVar {
+    private String passwd;
+
+    public SetLdapPassVar(String passwd) {
+        //  Encrypted password
+        this.passwd = SymmetricEncryption.encrypt(passwd);
+    }
+
+    public String getLdapPassword() {
+        return passwd;
+    }
+
+    @Override
+    public void analyze(Analyzer analyzer) throws AnalysisException {
+        if (Strings.isNullOrEmpty(analyzer.getClusterName())) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_SELECT_CLUSTER);
+        }
+
+        if (!ConnectContext.get().getCurrentUserIdentity().getQualifiedUser().equals(PaloAuth.ROOT_USER)
+                && !ConnectContext.get().getCurrentUserIdentity().getQualifiedUser().equals(PaloAuth.ADMIN_USER)) {
+            throw new AnalysisException("Only root and admin user can set ldap admin password.");
+        }
+    }
+
+    @Override
+    public String toString() {
+        return toSql();
+    }
+
+    @Override
+    public String toSql() {
+        StringBuilder sb = new StringBuilder("SET LDAP_ADMIN_PASSWORD");
+        sb.append(" = '*XXX'");
+        return sb.toString();
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/ConfigBase.java b/fe/fe-core/src/main/java/org/apache/doris/common/ConfigBase.java
index 1011ead..2c43e51 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/ConfigBase.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/ConfigBase.java
@@ -17,13 +17,13 @@
 
 package org.apache.doris.common;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileReader;
@@ -55,10 +55,23 @@ public class ConfigBase {
     private static String customConfFile;
     public static Class<? extends ConfigBase> confClass;
 
-    public void init(String confFile) throws Exception {
-        confClass = this.getClass();
-        this.confFile = confFile;
-        initConf(confFile);
+    private static String ldapConfFile;
+    private static String ldapCustomConfFile;
+    public static Class<? extends ConfigBase> ldapConfClass;
+
+    private boolean isLdapConfig = false;
+
+    public void init(String configFile) throws Exception {
+        this.isLdapConfig = (this instanceof LdapConfig);
+        if (!isLdapConfig) {
+            confClass = this.getClass();
+            confFile = configFile;
+            initConf(confFile);
+        } else {
+            ldapConfClass = this.getClass();
+            ldapConfFile = configFile;
+            initConf(ldapConfFile);
+        }
     }
 
     public void initCustom(String customConfFile) throws Exception {
@@ -75,7 +88,7 @@ public class ConfigBase {
         Properties props = new Properties();
         props.load(new FileReader(confFile));
         replacedByEnv(props);
-        setFields(props);
+        setFields(props, isLdapConfig);
     }
     
     public static HashMap<String, String> dump() throws Exception { 
@@ -137,8 +150,9 @@ public class ConfigBase {
         }
     }
 
-    private static void setFields(Properties props) throws Exception {
-        Field[] fields = confClass.getFields();     
+    private static void setFields(Properties props, boolean isLdapConfig) throws Exception {
+        Class<? extends ConfigBase> theClass = isLdapConfig ? ldapConfClass : confClass;
+        Field[] fields = theClass.getFields();
         for (Field f : fields) {
             // ensure that field has "@ConfField" annotation
             ConfField anno = f.getAnnotation(ConfField.class);
@@ -159,7 +173,7 @@ public class ConfigBase {
             if (confKey.equalsIgnoreCase("async_load_task_pool_size")) {
                 Config.async_loading_load_task_pool_size = Config.async_load_task_pool_size;
             }
-        }       
+        }
     }
 
     public static void setConfigField(Field f, String confVal) throws IllegalAccessException, Exception {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java b/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java
index 10819a7..56254e5 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/ErrorCode.java
@@ -243,7 +243,11 @@ public enum ErrorCode {
     ERROR_DYNAMIC_PARTITION_CREATE_HISTORY_PARTITION(5074, new byte[]{'4', '2', '0', '0', '0'},
             "Invalid dynamic partition create_history_partition: %s. Expected true or false"),
     ERROR_DYNAMIC_PARTITION_HISTORY_PARTITION_NUM_ZERO(5075, new byte[] {'4', '2', '0', '0', '0'},
-            "Dynamic history partition num must greater than 0");
+            "Dynamic history partition num must greater than 0"),
+    ERROR_LDAP_CONFIGURATION_ERR(5080, new byte[] {'4', '2', '0', '0', '0'},
+            "LDAP configuration is incorrect or LDAP admin password is not set."),
+    ERROR_LDAP_USER_NOT_UNIQUE_ERR(5081, new byte[] {'4', '2', '0', '0', '0'},
+            "%s is not unique in LDAP server.");
 
     ErrorCode(int code, byte[] sqlState, String errorMsg) {
         this.code = code;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/LdapConfig.java b/fe/fe-core/src/main/java/org/apache/doris/common/LdapConfig.java
new file mode 100644
index 0000000..e05a228
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/LdapConfig.java
@@ -0,0 +1,145 @@
+// 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;
+
+/**
+ * LDAP configuration
+ */
+public class LdapConfig extends ConfigBase {
+
+    /**
+     * Flag to enable LDAP authentication.
+     */
+    @ConfigBase.ConfField
+    public static boolean ldap_authentication_enabled = false;
+
+    /**
+     * LDAP server ip.
+     */
+    @ConfigBase.ConfField
+    public static String ldap_host = "";
+
+    /**
+     * LDAP server port.
+     */
+    @ConfigBase.ConfField
+    public static int ldap_port = 389;
+
+    /**
+     * Search base for users.
+     * LDAP is a tree structure, and this specifies the base of the subtree in which the search is to be constrained.
+     */
+    @ConfigBase.ConfField
+    public static String ldap_user_basedn = "";
+
+    /**
+     * The DN to bind as connection, this value will be used to lookup information about other users.
+     */
+    @ConfigBase.ConfField
+    public static String ldap_admin_name = "";
+
+    /**
+     * User lookup filter, the placeholder {login} will be replaced by the user supplied login.
+     */
+    @ConfigBase.ConfField
+    public static String ldap_user_filter = "(&(uid={login}))";
+
+    /**
+     * Search base for groups.
+     */
+    @ConfigBase.ConfField
+    public static String ldap_group_basedn = "";
+
+    /**
+     * Maximum number of user connections. This value should be between 1 and 10000.
+     */
+    @ConfigBase.ConfField
+    public static long user_max_connections = 100L;
+
+    /**
+     * LDAP pool configuration:
+     * https://docs.spring.io/spring-ldap/docs/2.3.3.RELEASE/reference/#pool-configuration
+     */
+    /**
+     * The maximum number of active connections of each type (read-only or read-write) that can be allocated
+     * from this pool at the same time. You can use a non-positive number for no limit.
+     */
+    @ConfigBase.ConfField
+    public static int max_active = 8;
+
+    /**
+     * The overall maximum number of active connections (for all types) that can be allocated from this pool
+     * at the same time. You can use a non-positive number for no limit.
+     */
+    @ConfigBase.ConfField
+    public static int max_total = -1;
+
+    /**
+     * The maximum number of active connections of each type (read-only or read-write) that can remain idle
+     * in the pool without extra connections being released. You can use a non-positive number for no limit.
+     */
+    @ConfigBase.ConfField
+    public static int max_idle = 8;
+
+    /**
+     * The minimum number of active connections of each type (read-only or read-write) that can remain idle
+     * in the pool without extra connections being created. You can use zero (the default) to create none.
+     */
+    @ConfigBase.ConfField
+    public static int min_idle = 0;
+
+    /**
+     * The maximum number of milliseconds that the pool waits (when no connections are available) for a connection
+     * to be returned before throwing an exception. You can use a non-positive number to wait indefinitely.
+     */
+    @ConfigBase.ConfField
+    public static int max_wait = -1;
+
+    /**
+     * Specifies the behavior when the pool is exhausted.
+     *
+     * The '0' option throws NoSuchElementException when the pool is exhausted.
+     *
+     * The '1' option waits until a new object is available. If max-wait is positive and no new object is available
+     * after the max-wait time expires, NoSuchElementException is thrown.
+     *
+     * The '2' option creates and returns a new object (essentially making max-active meaningless).
+     */
+    @ConfigBase.ConfField
+    public static byte when_exhausted = 1;
+
+    /**
+     * Whether objects are validated before being borrowed from the pool. If the object fails to validate,
+     * it is dropped from the pool, and an attempt to borrow another is made.
+     */
+    @ConfigBase.ConfField
+    public static boolean test_on_borrow = false;
+
+    /**
+     * Whether objects are validated before being returned to the pool.
+     */
+    @ConfigBase.ConfField
+    public static boolean test_on_return = false;
+
+    /**
+     * Whether objects are validated by the idle object evictor (if any). If an object fails to validate,
+     * it is dropped from the pool.
+     */
+    @ConfigBase.ConfField
+    public static boolean test_while_idle = false;
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/SymmetricEncryption.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/SymmetricEncryption.java
new file mode 100644
index 0000000..da70d94
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/SymmetricEncryption.java
@@ -0,0 +1,76 @@
+// 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.util;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * This is borrowed from apache kylin:
+ * https://github.com/apache/kylin/blob/master/core-common/src/main/java/org/apache/kylin/common/util/EncryptUtil.java
+ */
+public class SymmetricEncryption {
+    private static byte[] key = { 0x56, 0x73, 0x36, 0x68, 0x4b, 0x56, 0x27, 0x67, 0x24, 0x46, 0x77, 0x57, 0x75, 0x5a,
+            0x46, 0x74 };
+
+    private static final Cipher getCipher(int cipherMode) throws InvalidAlgorithmParameterException,
+            InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, UnsupportedEncodingException {
+        Cipher cipher = Cipher.getInstance("AES/CFB/PKCS5Padding");
+        final SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
+        IvParameterSpec ivSpec = new IvParameterSpec("AAAAAAAAAAAAAAAA".getBytes("UTF-8"));
+        cipher.init(cipherMode, secretKey, ivSpec);
+        return cipher;
+    }
+
+    public static String encrypt(String strToEncrypt) {
+        if (strToEncrypt == null) {
+            return null;
+        }
+        try {
+            Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
+            final String encryptedString = Base64.encodeBase64String(cipher.doFinal(strToEncrypt.getBytes(
+                    StandardCharsets.UTF_8)));
+            return encryptedString;
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    public static String decrypt(String strToDecrypt) {
+        if (strToDecrypt == null) {
+            return null;
+        }
+        try {
+            Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
+            final String decryptedString = new String(cipher.doFinal(Base64.decodeBase64(strToDecrypt)), StandardCharsets.UTF_8);
+            return decryptedString;
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+}
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 4c73f05..f648358 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
@@ -73,6 +73,7 @@ import org.apache.doris.persist.ModifyTablePropertyOperationLog;
 import org.apache.doris.persist.OperationType;
 import org.apache.doris.persist.PartitionPersistInfo;
 import org.apache.doris.persist.PrivInfo;
+import org.apache.doris.persist.LdapInfo;
 import org.apache.doris.persist.RecoverInfo;
 import org.apache.doris.persist.RefreshExternalTableInfo;
 import org.apache.doris.persist.RemoveAlterJobV2OperationLog;
@@ -348,6 +349,11 @@ public class JournalEntity implements Writable {
                 isRead = true;
                 break;
             }
+            case OperationType.OP_SET_LDAP_PASSWORD: {
+                data = LdapInfo.read(in);
+                isRead = true;
+                break;
+            }
             case OperationType.OP_UPDATE_USER_PROPERTY: {
                 data = UserPropertyInfo.read(in);
                 isRead = true;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapAuthenticate.java b/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapAuthenticate.java
new file mode 100644
index 0000000..532a6b5
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapAuthenticate.java
@@ -0,0 +1,145 @@
+// 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.ldap;
+
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Catalog;
+import org.apache.doris.cluster.ClusterNamespace;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.mysql.privilege.PaloRole;
+import org.apache.doris.qe.ConnectContext;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import java.util.List;
+
+/**
+ * This class is used for LDAP authentication login and LDAP group authorization.
+ * This means that users can log in to Doris with a user name and LDAP password,
+ * and the user will get the privileges of all roles corresponding to the LDAP group.
+ */
+public class LdapAuthenticate {
+    private static final Logger LOG = LogManager.getLogger(LdapAuthenticate.class);
+
+    private static final String LDAP_GROUPS_PRIVS_NAME = "ldapGroupsPrivs";
+
+    // Maximum number of the user LDAP authentication login connections.
+    private static long userMaxConn = 100;
+
+    {
+        if (LdapConfig.user_max_connections <= 0 || LdapConfig.user_max_connections > 10000) {
+            LOG.warn("Ldap config user_max_connections is invalid. It should be set between 1 and 10000. " +
+                    "And now, it is set to the default value.");
+        } else {
+            userMaxConn = LdapConfig.user_max_connections;
+        }
+    }
+
+    /**
+     * The LDAP authentication process is as follows:
+     * step1: Check the LDAP password.
+     * step2: Get the LDAP groups privileges as a role, saved into ConnectContext.
+     * step3: Set current userIdentity. If the user account does not exist in Doris, login as a temporary user.
+     * Otherwise, login to the Doris account.
+     */
+    public static boolean authenticate(ConnectContext context, String password, String qualifiedUser) {
+        String usePasswd = (Strings.isNullOrEmpty(password)) ? "NO" : "YES";
+        String userName = ClusterNamespace.getNameFromFullName(qualifiedUser);
+        String clusterName = ClusterNamespace.getClusterNameFromFullName(qualifiedUser);
+        LOG.debug("user:{}, cluster:{}", userName, clusterName);
+
+        // check user password by ldap server.
+        try {
+            if (!LdapClient.checkPassword(userName, password)) {
+                LOG.debug("user:{} use error LDAP password.", userName);
+                ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, usePasswd);
+                return false;
+            }
+        } catch (Exception e) {
+            LOG.error("Check ldap password error.", e);
+            return false;
+        }
+
+        // Get the LDAP groups privileges as a role.
+        PaloRole ldapGroupsPrivs;
+        try {
+            ldapGroupsPrivs = getLdapGroupsPrivs(userName, clusterName);
+        } catch (Exception e) {
+            LOG.error("Get ldap groups error.", e);
+            return false;
+        }
+
+        String remoteIp = context.getMysqlChannel().getRemoteIp();
+        UserIdentity tempUserIdentity = UserIdentity.createAnalyzedUserIdentWithIp(qualifiedUser, remoteIp);
+        // Search the user in doris.
+        UserIdentity userIdentity = Catalog.getCurrentCatalog().getAuth().getCurrentUserIdentity(tempUserIdentity);
+        if (userIdentity == null) {
+            userIdentity = tempUserIdentity;
+            LOG.debug("User:{} does not exists in doris, login as temporary users.", userName);
+            context.setIsTempUser(true);
+            if (ldapGroupsPrivs == null) {
+                ldapGroupsPrivs = new PaloRole(LDAP_GROUPS_PRIVS_NAME);
+            }
+            LdapPrivsChecker.grantDefaultPrivToTempUser(ldapGroupsPrivs, clusterName);
+        }
+
+        context.setCurrentUserIdentity(userIdentity);
+        context.setRemoteIP(remoteIp);
+        context.setLdapGroupsPrivs(ldapGroupsPrivs);
+        LOG.debug("ldap authentication success: identity:{}, privs:{}",
+                context.getCurrentUserIdentity(), context.getLdapGroupsPrivs());
+        return true;
+    }
+
+    /**
+     * Step1: get ldap groups from ldap server;
+     * Step2: get roles by ldap groups;
+     * Step3: merge the roles;
+     */
+    private static PaloRole getLdapGroupsPrivs(String userName, String clusterName) {
+        //get user ldap group. the ldap group name should be the same as the doris role name
+        List<String> ldapGroups = LdapClient.getGroups(userName);
+        List<String> rolesNames = Lists.newArrayList();
+        for (String group : ldapGroups) {
+            String qualifiedRole = ClusterNamespace.getFullName(clusterName, group);
+            if (Catalog.getCurrentCatalog().getAuth().doesRoleExist(qualifiedRole)) {
+                rolesNames.add(qualifiedRole);
+            }
+        }
+        LOG.debug("get user:{} ldap groups:{} and doris roles:{}", userName, ldapGroups, rolesNames);
+
+        // merge the roles
+        if (rolesNames.isEmpty()) {
+            return null;
+        } else {
+            PaloRole ldapGroupsPrivs = new PaloRole(LDAP_GROUPS_PRIVS_NAME);
+            Catalog.getCurrentCatalog().getAuth().mergeRolesNoCheckName(rolesNames, ldapGroupsPrivs);
+            return ldapGroupsPrivs;
+        }
+    }
+
+    public static long getMaxConn() {
+        return userMaxConn;
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapClient.java b/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapClient.java
new file mode 100644
index 0000000..f850155
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapClient.java
@@ -0,0 +1,182 @@
+// 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.ldap;
+
+import static org.springframework.ldap.query.LdapQueryBuilder.query;
+
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.common.util.SymmetricEncryption;
+
+import com.google.common.collect.Lists;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.AbstractContextMapper;
+import org.springframework.ldap.core.support.LdapContextSource;
+import org.springframework.ldap.pool.factory.PoolingContextSource;
+import org.springframework.ldap.pool.validation.DefaultDirContextValidator;
+import org.springframework.ldap.query.LdapQuery;
+import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy;
+
+import java.util.List;
+
+// This class is used to connect to the LDAP service.
+public class LdapClient {
+    private static final Logger LOG = LogManager.getLogger(LdapClient.class);
+
+    // Checking the user password requires creating a new connection with the user dn and password.
+    // Due to this, these connections cannot be pooled.
+    private volatile static LdapTemplate ldapTemplateNoPool;
+    // Use ldap connection pool, connect to bind ldap admin dn and admin password.
+    private volatile static LdapTemplate ldapTemplatePool;
+
+    public static void init(String ldapPassword) {
+        LOG.info("Init ldap client.");
+        setLdapTemplateNoPool(ldapPassword);
+        setLdapTemplatePool(ldapPassword);
+    }
+
+    private static void setLdapTemplateNoPool(String ldapPassword) {
+        LdapContextSource contextSource = new LdapContextSource();
+        String url = "ldap://" + LdapConfig.ldap_host + ":" + LdapConfig.ldap_port;
+
+        contextSource.setUrl(url);
+        contextSource.setUserDn(LdapConfig.ldap_admin_name);
+        contextSource.setPassword(SymmetricEncryption.decrypt(ldapPassword));
+        contextSource.afterPropertiesSet();
+        ldapTemplateNoPool = new LdapTemplate(contextSource);
+    }
+
+    private static void setLdapTemplatePool(String ldapPassword) {
+        LdapContextSource contextSource = new LdapContextSource();
+        String url = "ldap://" + LdapConfig.ldap_host + ":" + LdapConfig.ldap_port;
+
+        contextSource.setUrl(url);
+        contextSource.setUserDn(LdapConfig.ldap_admin_name);
+        contextSource.setPassword(SymmetricEncryption.decrypt(ldapPassword));
+        contextSource.setPooled(true);
+        contextSource.afterPropertiesSet();
+
+        PoolingContextSource poolingContextSource = new PoolingContextSource();
+        poolingContextSource.setDirContextValidator(new DefaultDirContextValidator());
+        poolingContextSource.setContextSource(contextSource);
+        poolingContextSource.setMaxActive(LdapConfig.max_active);
+        poolingContextSource.setMaxTotal(LdapConfig.max_total);
+        poolingContextSource.setMaxIdle(LdapConfig.max_idle);
+        poolingContextSource.setMaxWait(LdapConfig.max_wait);
+        poolingContextSource.setMinIdle(LdapConfig.min_idle);
+        poolingContextSource.setWhenExhaustedAction(LdapConfig.when_exhausted);
+        poolingContextSource.setTestOnBorrow(LdapConfig.test_on_borrow);
+        poolingContextSource.setTestOnReturn(LdapConfig.test_on_return);
+        poolingContextSource.setTestWhileIdle(LdapConfig.test_while_idle);
+
+        TransactionAwareContextSourceProxy proxy = new TransactionAwareContextSourceProxy(poolingContextSource);
+        ldapTemplatePool = new LdapTemplate(proxy);
+    }
+
+    public static boolean doesUserExist(String userName) {
+        String user = getUserDn(userName);
+        if (user == null) {
+            LOG.debug("User:{} does not exist in LDAP.", userName);
+            return false;
+        }
+        return true;
+    }
+
+    public static boolean checkPassword(String userName, String password) {
+        checkLdapTemplate();
+        try {
+            ldapTemplateNoPool.authenticate(query().base(LdapConfig.ldap_user_basedn)
+                    .filter(getUserFilter(LdapConfig.ldap_user_filter, userName)), password);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    // Search group DNs by 'member' attribution.
+    public static List<String> getGroups(String userName) {
+        List<String> groups = Lists.newArrayList();
+        if (LdapConfig.ldap_group_basedn.isEmpty()){
+            return groups;
+        }
+        String userDn = getUserDn(userName);
+        if (userDn == null) {
+            return groups;
+        }
+        List<String> groupDns = getDn(query().base(LdapConfig.ldap_group_basedn)
+                .where("member").is(userDn));
+        if (groupDns == null) {
+            return groups;
+        }
+
+        // group dn like: 'cn=groupName,ou=groups,dc=example,dc=com', we only need the groupName.
+        for (String dn : groupDns) {
+            String[] strings = dn.split("[,=]", 3);
+            if (strings.length > 2) {
+                groups.add(strings[1]);
+            }
+        }
+        return groups;
+    }
+
+    private static String getUserDn(String userName) {
+        List<String> userDns = getDn(query().base(LdapConfig.ldap_user_basedn)
+                .filter(getUserFilter(LdapConfig.ldap_user_filter, userName)));
+        if (userDns == null || userDns.isEmpty()) {
+            return null;
+        }
+        if (userDns.size() > 1) {
+            LOG.error("{} not unique in LDAP server:{}", getUserFilter(LdapConfig.ldap_user_filter, userName), userDns);
+            ErrorReport.report(ErrorCode.ERROR_LDAP_USER_NOT_UNIQUE_ERR, userName);
+            throw new RuntimeException("User is not unique");
+        }
+        return userDns.get(0);
+    }
+
+    private static List<String> getDn(LdapQuery query) {
+        checkLdapTemplate();
+        try {
+            return ldapTemplatePool.search(query, new AbstractContextMapper() {
+                protected String doMapFromContext(DirContextOperations ctx) {
+                    return ctx.getNameInNamespace();
+                }
+            });
+        } catch (Exception e) {
+            LOG.error("Get user dn fail.", e);
+            ErrorReport.report(ErrorCode.ERROR_LDAP_CONFIGURATION_ERR);
+            throw e;
+        }
+    }
+
+    private static String getUserFilter(String userFilter, String userName) {
+        return userFilter.replaceAll("\\{login}", userName);
+    }
+
+    private static boolean checkLdapTemplate() {
+        if (ldapTemplatePool == null || ldapTemplateNoPool == null) {
+            LOG.error("ldapTemplate is not initialized.");
+            ErrorReport.report(ErrorCode.ERROR_LDAP_CONFIGURATION_ERR);
+            throw new RuntimeException("ldapTemplate is not initialized");
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapPrivsChecker.java b/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapPrivsChecker.java
new file mode 100644
index 0000000..f13257b
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapPrivsChecker.java
@@ -0,0 +1,263 @@
+// 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.ldap;
+
+import com.google.common.collect.Maps;
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.InfoSchemaDb;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.mysql.privilege.PaloAuth;
+import org.apache.doris.mysql.privilege.PaloPrivilege;
+import org.apache.doris.mysql.privilege.PaloRole;
+import org.apache.doris.mysql.privilege.PrivBitSet;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
+import com.google.common.base.Preconditions;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Map;
+
+/**
+ * If the user logs in with LDAP authentication, the user LDAP group privileges will be saved in 'ldapGroupsPrivs' of ConnectContext.
+ * When checking user privileges, Doris need to check both the privileges granted by Doris and LDAP group privileges.
+ * This class is used for checking current user LDAP group privileges.
+ */
+public class LdapPrivsChecker {
+    private static final Logger LOG = LogManager.getLogger(LdapPrivsChecker.class);
+
+    public static boolean hasGlobalPrivFromLdap(UserIdentity currentUser, PrivPredicate wanted) {
+        return hasTblPatternPrivs(currentUser, wanted, null, null, PaloAuth.PrivLevel.GLOBAL)
+                || hasResourcePatternPrivs(currentUser, wanted, null, PaloAuth.PrivLevel.GLOBAL);
+    }
+
+    public static boolean hasDbPrivFromLdap(UserIdentity currentUser, String db, PrivPredicate wanted) {
+        return hasTblPatternPrivs(currentUser, wanted, db, null, PaloAuth.PrivLevel.DATABASE);
+    }
+
+    // Any database has wanted priv return true.
+    public static boolean hasDbPrivFromLdap(UserIdentity currentUser, PrivPredicate wanted) {
+        return hasPrivs(currentUser, wanted, PaloAuth.PrivLevel.DATABASE);
+    }
+
+    public static boolean hasTblPrivFromLdap(UserIdentity currentUser, String db, String tbl, PrivPredicate wanted) {
+        return hasTblPatternPrivs(currentUser, wanted, db, tbl, PaloAuth.PrivLevel.TABLE);
+    }
+
+    // Any table has wanted priv return true.
+    public static boolean hasTblPrivFromLdap(UserIdentity currentUser, PrivPredicate wanted) {
+        return hasPrivs(currentUser, wanted, PaloAuth.PrivLevel.TABLE);
+    }
+
+    public static boolean hasResourcePrivFromLdap(UserIdentity currentUser, String resourceName, PrivPredicate wanted) {
+        return hasResourcePatternPrivs(currentUser, wanted, resourceName, PaloAuth.PrivLevel.RESOURCE);
+    }
+
+    private static boolean hasTblPatternPrivs(UserIdentity currentUser, PrivPredicate wanted, String db, String tbl,
+                                              PaloAuth.PrivLevel level) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        getCurrentUserTblPrivs(currentUser, db, tbl, savedPrivs, level);
+        return PaloPrivilege.satisfy(savedPrivs, wanted);
+    }
+
+    private static boolean hasResourcePatternPrivs(UserIdentity currentUser, PrivPredicate wanted, String resourceName,
+                                                   PaloAuth.PrivLevel level) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        getCurrentUserResourcePrivs(currentUser, resourceName, savedPrivs, level);
+        return PaloPrivilege.satisfy(savedPrivs, wanted);
+    }
+
+    public static PrivBitSet getGlobalPrivFromLdap(UserIdentity currentUser) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        getCurrentUserTblPrivs(currentUser, null, null, savedPrivs, PaloAuth.PrivLevel.GLOBAL);
+        getCurrentUserResourcePrivs(currentUser, null, savedPrivs, PaloAuth.PrivLevel.GLOBAL);
+        return savedPrivs;
+    }
+
+    public static PrivBitSet getDbPrivFromLdap(UserIdentity currentUser, String db) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        getCurrentUserTblPrivs(currentUser, db, null, savedPrivs, PaloAuth.PrivLevel.DATABASE);
+        return savedPrivs;
+    }
+
+    public static PrivBitSet getTblPrivFromLdap(UserIdentity currentUser, String db, String tbl) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        getCurrentUserTblPrivs(currentUser, db, tbl, savedPrivs, PaloAuth.PrivLevel.TABLE);
+        return savedPrivs;
+    }
+
+    public static PrivBitSet getResourcePrivFromLdap(UserIdentity currentUser, String resourceName) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        getCurrentUserResourcePrivs(currentUser, resourceName, savedPrivs, PaloAuth.PrivLevel.RESOURCE);
+        return savedPrivs;
+    }
+
+    private static void getCurrentUserTblPrivs(UserIdentity currentUser, String db, String tbl, PrivBitSet savedPrivs,
+                                               PaloAuth.PrivLevel level) {
+        if (!hasLdapPrivs(currentUser)) {
+            return;
+        }
+        PaloRole currentUserLdapPrivs = ConnectContext.get().getLdapGroupsPrivs();
+        for (Map.Entry<TablePattern, PrivBitSet> entry : currentUserLdapPrivs.getTblPatternToPrivs().entrySet()) {
+            switch (entry.getKey().getPrivLevel()) {
+                case GLOBAL:
+                    if (level.equals(PaloAuth.PrivLevel.GLOBAL)) {
+                        savedPrivs.or(entry.getValue());
+                        return;
+                    }
+                    break;
+                case DATABASE:
+                    if (level.equals(PaloAuth.PrivLevel.DATABASE) && db != null
+                            && entry.getKey().getQualifiedDb().equals(db)) {
+                        savedPrivs.or(entry.getValue());
+                        return;
+                    }
+                    break;
+                case TABLE:
+                    if (level.equals(PaloAuth.PrivLevel.TABLE) && db != null && tbl != null
+                            && entry.getKey().getQualifiedDb().equals(db) && entry.getKey().getTbl().equals(tbl)) {
+                        savedPrivs.or(entry.getValue());
+                        return;
+                    }
+                    break;
+                default:
+                    Preconditions.checkNotNull(null, entry.getKey().getPrivLevel());
+            }
+        }
+    }
+
+    private static void getCurrentUserResourcePrivs(UserIdentity currentUser, String resourceName, PrivBitSet savedPrivs,
+                                                    PaloAuth.PrivLevel level) {
+        if (!hasLdapPrivs(currentUser)) {
+            return;
+        }
+        PaloRole currentUserLdapPrivs = ConnectContext.get().getLdapGroupsPrivs();
+        for (Map.Entry<ResourcePattern, PrivBitSet> entry : currentUserLdapPrivs.getResourcePatternToPrivs().entrySet()) {
+            switch (entry.getKey().getPrivLevel()) {
+                case GLOBAL:
+                    if (level.equals(PaloAuth.PrivLevel.GLOBAL)) {
+                        savedPrivs.or(entry.getValue());
+                        return;
+                    }
+                    break;
+                case RESOURCE:
+                    if (level.equals(PaloAuth.PrivLevel.RESOURCE) && resourceName != null
+                            && entry.getKey().getResourceName().equals(resourceName)) {
+                        savedPrivs.or(entry.getValue());
+                        return;
+                    }
+                    break;
+                default:
+                    Preconditions.checkNotNull(null, entry.getKey().getPrivLevel());
+            }
+        }
+    }
+
+    private static boolean hasPrivs(UserIdentity currentUser, PrivPredicate wanted, PaloAuth.PrivLevel level) {
+        if (!hasLdapPrivs(currentUser)) {
+            return false;
+        }
+        PaloRole currentUserLdapPrivs = ConnectContext.get().getLdapGroupsPrivs();
+        for (Map.Entry<TablePattern, PrivBitSet> entry : currentUserLdapPrivs.getTblPatternToPrivs().entrySet()) {
+            if (entry.getKey().getPrivLevel().equals(level) && PaloPrivilege.satisfy(entry.getValue(), wanted)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Check if user has any privs of tables in this database.
+    public static boolean hasPrivsOfDb(UserIdentity currentUser, String db) {
+        if (!hasLdapPrivs(currentUser)) {
+            return false;
+        }
+        PaloRole currentUserLdapPrivs = ConnectContext.get().getLdapGroupsPrivs();
+        for (Map.Entry<TablePattern, PrivBitSet> entry : currentUserLdapPrivs.getTblPatternToPrivs().entrySet()) {
+            if (entry.getKey().getPrivLevel().equals(PaloAuth.PrivLevel.TABLE) && entry.getKey().getQualifiedDb().equals(db)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isCurrentUser(UserIdentity userIdent) {
+        ConnectContext context = ConnectContext.get();
+        if (context == null) {
+            return false;
+        }
+        UserIdentity currentUser = context.getCurrentUserIdentity();
+        return currentUser.getQualifiedUser().equals(userIdent.getQualifiedUser())
+                && currentUser.getHost().equals(userIdent.getHost());
+    }
+
+    public static boolean hasLdapPrivs(UserIdentity userIdent) {
+        return LdapConfig.ldap_authentication_enabled && isCurrentUser(userIdent)
+                && ConnectContext.get().getLdapGroupsPrivs() != null;
+    }
+
+    public static Map<TablePattern, PrivBitSet> getLdapAllDbPrivs(UserIdentity userIdentity) {
+        Map<TablePattern, PrivBitSet> ldapDbPrivs = Maps.newConcurrentMap();
+        if (!hasLdapPrivs(userIdentity)) return ldapDbPrivs;
+        for (Map.Entry<TablePattern, PrivBitSet> entry : ConnectContext.get().getLdapGroupsPrivs()
+                .getTblPatternToPrivs().entrySet()) {
+            if (entry.getKey().getPrivLevel().equals(PaloAuth.PrivLevel.DATABASE)) {
+                ldapDbPrivs.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return ldapDbPrivs;
+    }
+
+    public static Map<TablePattern, PrivBitSet> getLdapAllTblPrivs(UserIdentity userIdentity) {
+        Map<TablePattern, PrivBitSet> ldapTblPrivs = Maps.newConcurrentMap();
+        if (!hasLdapPrivs(userIdentity)) return ldapTblPrivs;
+        for (Map.Entry<TablePattern, PrivBitSet> entry : ConnectContext.get().getLdapGroupsPrivs()
+                .getTblPatternToPrivs().entrySet()) {
+            if (entry.getKey().getPrivLevel().equals(PaloAuth.PrivLevel.TABLE)) {
+                ldapTblPrivs.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return ldapTblPrivs;
+    }
+
+    public static Map<ResourcePattern, PrivBitSet> getLdapAllResourcePrivs(UserIdentity userIdentity) {
+        Map<ResourcePattern, PrivBitSet> ldapResourcePrivs = Maps.newConcurrentMap();
+        if (!hasLdapPrivs(userIdentity)) return ldapResourcePrivs;
+        for (Map.Entry<ResourcePattern, PrivBitSet> entry : ConnectContext.get().getLdapGroupsPrivs()
+                .getResourcePatternToPrivs().entrySet()) {
+            if (entry.getKey().getPrivLevel().equals(PaloAuth.PrivLevel.RESOURCE)) {
+                ldapResourcePrivs.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return ldapResourcePrivs;
+    }
+
+    // Temporary user has information_schema 'Select_priv' priv by default.
+    public static void grantDefaultPrivToTempUser(PaloRole role, String clusterName) {
+        TablePattern tblPattern = new TablePattern(InfoSchemaDb.DATABASE_NAME, "*");
+        try {
+            tblPattern.analyze(clusterName);
+        } catch (AnalysisException e) {
+            LOG.warn("should not happen.", e);
+        }
+        PaloRole newRole = new PaloRole(role.getRoleName(), tblPattern, PrivBitSet.of(PaloPrivilege.SELECT_PRIV));
+        role.merge(newRole);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlAuthSwitchPacket.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlAuthSwitchPacket.java
new file mode 100644
index 0000000..6195575
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlAuthSwitchPacket.java
@@ -0,0 +1,31 @@
+// 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.mysql;
+
+public class MysqlAuthSwitchPacket extends MysqlPacket {
+    private static final int STATUS = 0xfe;
+    private static final String AUTH_PLUGIN_NAME = "mysql_clear_password";
+    private static final String DATA = "";
+
+    @Override
+    public void writeTo(MysqlSerializer serializer) {
+        serializer.writeInt1(STATUS);
+        serializer.writeNulTerminateString(AUTH_PLUGIN_NAME);
+        serializer.writeNulTerminateString(DATA);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlClearTextPacket.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlClearTextPacket.java
new file mode 100644
index 0000000..5b16af2
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlClearTextPacket.java
@@ -0,0 +1,40 @@
+// 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.mysql;
+
+import java.nio.ByteBuffer;
+
+public class MysqlClearTextPacket extends MysqlPacket {
+
+    private String password = "";
+
+    public String getPassword() {
+        return password;
+    }
+
+    @Override
+    public boolean readFrom(ByteBuffer buffer) {
+        password = new String(MysqlProto.readNulTerminateString(buffer));
+        return true;
+    }
+
+    @Override
+    public void writeTo(MysqlSerializer serializer) {
+
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java
index 5aa9eb2..12f53c3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java
@@ -24,6 +24,10 @@ import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.ldap.LdapAuthenticate;
+import org.apache.doris.ldap.LdapClient;
+import org.apache.doris.mysql.privilege.PaloAuth;
 import org.apache.doris.mysql.privilege.UserResource;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.system.SystemInfoService;
@@ -45,13 +49,29 @@ public class MysqlProto {
     // scramble: data receive from server.
     // randomString: data send by server in plug-in data field
     // user_name#HIGH@cluster_name
-    private static boolean authenticate(ConnectContext context, byte[] scramble, byte[] randomString, String user) {
+    private static boolean authenticate(ConnectContext context, byte[] scramble, byte[] randomString, String qualifiedUser) {
         String usePasswd = scramble.length == 0 ? "NO" : "YES";
-        
+        String remoteIp = context.getMysqlChannel().getRemoteIp();
+
+        List<UserIdentity> currentUserIdentity = Lists.newArrayList();
+        if (!Catalog.getCurrentCatalog().getAuth().checkPassword(qualifiedUser, remoteIp,
+                scramble, randomString, currentUserIdentity)) {
+            ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, usePasswd);
+            return false;
+        }
+
+        context.setCurrentUserIdentity(currentUserIdentity.get(0));
+        context.setRemoteIP(remoteIp);
+        return true;
+    }
+
+    private static String parseUser(ConnectContext context, byte[] scramble, String user) {
+        String usePasswd = scramble.length == 0 ? "NO" : "YES";
+
         String tmpUser = user;
         if (tmpUser == null || tmpUser.isEmpty()) {
             ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, "", usePasswd);
-            return false;
+            return null;
         }
 
         // check cluster, user name may contains cluster name or cluster id.
@@ -64,14 +84,14 @@ public class MysqlProto {
             clusterName = strList[1];
             try {
                 // if cluster does not exist and it is not a valid cluster id, authenticate failed
-                if (Catalog.getCurrentCatalog().getCluster(clusterName) == null 
+                if (Catalog.getCurrentCatalog().getCluster(clusterName) == null
                         && Integer.valueOf(strList[1]) != context.getCatalog().getClusterId()) {
                     ErrorReport.report(ErrorCode.ERR_UNKNOWN_CLUSTER_ID, strList[1]);
-                    return false;
+                    return null;
                 }
             } catch (Throwable e) {
                 ErrorReport.report(ErrorCode.ERR_UNKNOWN_CLUSTER_ID, strList[1]);
-                return false;
+                return null;
             }
         }
         if (Strings.isNullOrEmpty(clusterName)) {
@@ -90,22 +110,11 @@ public class MysqlProto {
                 context.getSessionVariable().setResourceGroup(strList[1]);
             }
         }
-        
+
         LOG.debug("parse cluster: {}", clusterName);
         String qualifiedUser = ClusterNamespace.getFullName(clusterName, tmpUser);
-        String remoteIp = context.getMysqlChannel().getRemoteIp();
-
-        List<UserIdentity> currentUserIdentity = Lists.newArrayList();
-        if (!Catalog.getCurrentCatalog().getAuth().checkPassword(qualifiedUser, remoteIp,
-                scramble, randomString, currentUserIdentity)) {
-            ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, usePasswd);
-            return false;
-        }
-       
-        context.setCurrentUserIdentity(currentUserIdentity.get(0));
         context.setQualifiedUser(qualifiedUser);
-        context.setRemoteIP(remoteIp);
-        return true;
+        return qualifiedUser;
     }
 
     // send response packet(OK/EOF/ERR).
@@ -121,10 +130,28 @@ public class MysqlProto {
         channel.sendAndFlush(serializer.toByteBuffer());
     }
 
+    private static boolean useLdapAuthenticate(String qualifiedUser) {
+        // The root and admin are used to set the ldap admin password and cannot use ldap authentication.
+        if (qualifiedUser.equals(PaloAuth.ROOT_USER) || qualifiedUser.equals(PaloAuth.ADMIN_USER)) {
+            return false;
+        }
+        // If LDAP authentication is enabled and the user exists in LDAP, use LDAP authentication,
+        // otherwise use Doris authentication.
+        if (LdapConfig.ldap_authentication_enabled
+                && LdapClient.doesUserExist(ClusterNamespace.getNameFromFullName(qualifiedUser))) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * negotiate with client, use MySQL protocol
      * server ---handshake---> client
      * server <--- authenticate --- client
+     * if enable ldap: {
+     * server ---AuthSwitch---> client
+     * server <--- clear text password --- client
+     * }
      * server --- response(OK/ERR) ---> client
      * Exception:
      * IOException:
@@ -165,48 +192,89 @@ public class MysqlProto {
             return false;
         }
 
-        // Starting with MySQL 8.0.4, MySQL changed the default authentication plugin for MySQL client
-        // from mysql_native_password to caching_sha2_password.
-        // ref: https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/
-        // So, User use mysql client or ODBC Driver after 8.0.4 have problem to connect to Doris
-        // with password.
-        // So Doris support the Protocol::AuthSwitchRequest to tell client to keep the default password plugin
-        // which Doris is using now.
-        // Note: Check the authPacket whether support plugin auth firstly, before we check AuthPlugin between doris and client
-        // to compatible with older version: like mysql 5.1
-        if (authPacket.getCapability().isPluginAuth() &&
-                !handshakePacket.checkAuthPluginSameAsDoris(authPacket.getPluginName())) {
-            // 1. clear the serializer
-            serializer.reset();
-            // 2. build the auth switch request and send to the client
-            handshakePacket.buildAuthSwitchRequest(serializer);
-            channel.sendAndFlush(serializer.toByteBuffer());
-            // Server receive auth switch response packet from client.
-            ByteBuffer authSwitchResponse = channel.fetchOnePacket();
-            if (authSwitchResponse == null) {
-                // receive response failed.
-                return false;
-            }
-            // 3. the client use default password plugin of Doris to dispose
-            // password
-            authPacket.setAuthResponse(readEofString(authSwitchResponse));
-        }
-
         // change the capability of serializer
         context.setCapability(context.getServerCapability());
         serializer.setCapability(context.getCapability());
 
-        // NOTE: when we behind proxy, we need random string sent by proxy.
-        byte[] randomString = handshakePacket.getAuthPluginData();
-        if (Config.proxy_auth_enable && authPacket.getRandomString() != null) {
-            randomString = authPacket.getRandomString();
+        String qualifiedUser = parseUser(context, authPacket.getAuthResponse(), authPacket.getUser());
+        if (qualifiedUser == null) {
+            sendResponsePacket(context);
+            return false;
         }
-        // check authenticate
-        if (!authenticate(context, authPacket.getAuthResponse(), randomString, authPacket.getUser())) {
+
+        boolean useLdapAuthenticate;
+        try {
+            useLdapAuthenticate = useLdapAuthenticate(qualifiedUser);
+        } catch (Exception e) {
+            LOG.debug("Check if user exists in ldap error.", e);
             sendResponsePacket(context);
             return false;
         }
 
+        if (useLdapAuthenticate) {
+            LOG.debug("user:{} start to ldap authenticate.", qualifiedUser);
+            // server send authentication switch packet to request password clear text.
+            // https://dev.mysql.com/doc/internals/en/authentication-method-change.html
+            serializer.reset();
+            MysqlAuthSwitchPacket mysqlAuthSwitchPacket = new MysqlAuthSwitchPacket();
+            mysqlAuthSwitchPacket.writeTo(serializer);
+            channel.sendAndFlush(serializer.toByteBuffer());
+
+            // Server receive password clear text.
+            ByteBuffer authSwitchResponse = channel.fetchOnePacket();
+            if (authSwitchResponse == null) {
+                return false;
+            }
+            MysqlClearTextPacket clearTextPacket = new MysqlClearTextPacket();
+            if (!clearTextPacket.readFrom(authSwitchResponse)) {
+                ErrorReport.report(ErrorCode.ERR_NOT_SUPPORTED_AUTH_MODE);
+                sendResponsePacket(context);
+                return false;
+            }
+            if (!LdapAuthenticate.authenticate(context, clearTextPacket.getPassword(), qualifiedUser)) {
+                sendResponsePacket(context);
+                return false;
+            }
+        } else {
+            // Starting with MySQL 8.0.4, MySQL changed the default authentication plugin for MySQL client
+            // from mysql_native_password to caching_sha2_password.
+            // ref: https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/
+            // So, User use mysql client or ODBC Driver after 8.0.4 have problem to connect to Doris
+            // with password.
+            // So Doris support the Protocol::AuthSwitchRequest to tell client to keep the default password plugin
+            // which Doris is using now.
+            // Note: Check the authPacket whether support plugin auth firstly, before we check AuthPlugin between doris and client
+            // to compatible with older version: like mysql 5.1
+            if (authPacket.getCapability().isPluginAuth() &&
+                    !handshakePacket.checkAuthPluginSameAsDoris(authPacket.getPluginName())) {
+                // 1. clear the serializer
+                serializer.reset();
+                // 2. build the auth switch request and send to the client
+                handshakePacket.buildAuthSwitchRequest(serializer);
+                channel.sendAndFlush(serializer.toByteBuffer());
+                // Server receive auth switch response packet from client.
+                ByteBuffer authSwitchResponse = channel.fetchOnePacket();
+                if (authSwitchResponse == null) {
+                    // receive response failed.
+                    return false;
+                }
+                // 3. the client use default password plugin of Doris to dispose
+                // password
+                authPacket.setAuthResponse(readEofString(authSwitchResponse));
+            }
+
+            // NOTE: when we behind proxy, we need random string sent by proxy.
+            byte[] randomString = handshakePacket.getAuthPluginData();
+            if (Config.proxy_auth_enable && authPacket.getRandomString() != null) {
+                randomString = authPacket.getRandomString();
+            }
+            // check authenticate
+            if (!authenticate(context, authPacket.getAuthResponse(), randomString, qualifiedUser)) {
+                sendResponsePacket(context);
+                return false;
+            }
+        }
+
         // set database
         String db = authPacket.getDb();
         if (!Strings.isNullOrEmpty(db)) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java
index 2937413..209ed36 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloAuth.java
@@ -25,6 +25,7 @@ import org.apache.doris.analysis.GrantStmt;
 import org.apache.doris.analysis.ResourcePattern;
 import org.apache.doris.analysis.RevokeStmt;
 import org.apache.doris.analysis.SetPassVar;
+import org.apache.doris.analysis.SetLdapPassVar;
 import org.apache.doris.analysis.SetUserPropertyStmt;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserIdentity;
@@ -37,9 +38,13 @@ import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.FeConstants;
 import org.apache.doris.common.FeMetaVersion;
+import org.apache.doris.common.LdapConfig;
 import org.apache.doris.common.Pair;
 import org.apache.doris.common.io.Writable;
+import org.apache.doris.ldap.LdapClient;
+import org.apache.doris.ldap.LdapPrivsChecker;
 import org.apache.doris.load.DppConfig;
+import org.apache.doris.persist.LdapInfo;
 import org.apache.doris.persist.PrivInfo;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.thrift.TFetchResourceResult;
@@ -230,6 +235,24 @@ public class PaloAuth implements Writable {
         resourcePrivTable.revoke(entry, errOnNonExist, true /* delete entry when empty */);
     }
 
+    public boolean doesRoleExist(String qualifiedRole) {
+        return roleManager.getRole(qualifiedRole) != null;
+    }
+
+    public void mergeRolesNoCheckName(List<String> roles, PaloRole savedRole) {
+        readLock();
+        try {
+            for (String roleName : roles) {
+                if (doesRoleExist(roleName)) {
+                    PaloRole role = roleManager.getRole(roleName);
+                    savedRole.mergeNotCheck(role);
+                }
+            }
+        } finally {
+            readUnlock();
+        }
+    }
+
     /*
      * check password, if matched, save the userIdentity in matched entry.
      * the following auth checking should use userIdentity saved in currentUser.
@@ -328,7 +351,8 @@ public class PaloAuth implements Writable {
     private boolean checkTblWithDb(UserIdentity currentUser, String db) {
         readLock();
         try {
-            return tablePrivTable.hasPrivsOfDb(currentUser, db);
+            return (isLdapAuthEnabled() && LdapPrivsChecker.hasPrivsOfDb(currentUser, db))
+                    || tablePrivTable.hasPrivsOfDb(currentUser, db);
         } finally {
             readUnlock();
         }
@@ -401,35 +425,43 @@ public class PaloAuth implements Writable {
      * This method will check the given privilege levels
      */
     public boolean checkHasPriv(ConnectContext ctx, PrivPredicate priv, PrivLevel... levels) {
-        return checkHasPrivInternal(ctx.getRemoteIP(), ctx.getQualifiedUser(), priv, levels);
+        return checkHasPrivInternal(ctx.getCurrentUserIdentity(), ctx.getRemoteIP(), ctx.getQualifiedUser(), priv, levels);
     }
 
-    private boolean checkHasPrivInternal(String host, String user, PrivPredicate priv, PrivLevel... levels) {
+    private boolean checkHasPrivInternal(UserIdentity currentUser, String host, String user, PrivPredicate priv,
+                                         PrivLevel... levels) {
         for (PrivLevel privLevel : levels) {
             switch (privLevel) {
-            case GLOBAL:
-                if (userPrivTable.hasPriv(host, user, priv)) {
-                    return true;
-                }
-                break;
-            case DATABASE:
-                if (dbPrivTable.hasPriv(host, user, priv)) {
-                    return true;
-                }
-                break;
-            case TABLE:
-                if (tablePrivTable.hasPriv(host, user, priv)) {
-                    return true;
-                }
-                break;
-            default:
-                break;
+                case GLOBAL:
+                    if ((isLdapAuthEnabled() && LdapPrivsChecker.hasGlobalPrivFromLdap(currentUser, priv))
+                            || userPrivTable.hasPriv(host, user, priv)) {
+                        return true;
+                    }
+                    break;
+                case DATABASE:
+                    if ((isLdapAuthEnabled() && LdapPrivsChecker.hasDbPrivFromLdap(currentUser, priv))
+                            || dbPrivTable.hasPriv(host, user, priv)) {
+                        return true;
+                    }
+                    break;
+                case TABLE:
+                    if ((isLdapAuthEnabled() && LdapPrivsChecker.hasTblPrivFromLdap(currentUser, priv))
+                            || tablePrivTable.hasPriv(host, user, priv)) {
+                        return true;
+                    }
+                    break;
+                default:
+                    break;
             }
         }
         return false;
     }
 
     private boolean checkGlobalInternal(UserIdentity currentUser, PrivPredicate wanted, PrivBitSet savedPrivs) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasGlobalPrivFromLdap(currentUser, wanted)) {
+            return true;
+        }
+
         readLock();
         try {
             userPrivTable.getPrivs(currentUser, savedPrivs);
@@ -443,7 +475,11 @@ public class PaloAuth implements Writable {
     }
 
     private boolean checkDbInternal(UserIdentity currentUser, String db, PrivPredicate wanted,
-            PrivBitSet savedPrivs) {
+                                    PrivBitSet savedPrivs) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasDbPrivFromLdap(currentUser, db, wanted)) {
+            return true;
+        }
+
         readLock();
         try {
             dbPrivTable.getPrivs(currentUser, db, savedPrivs);
@@ -457,7 +493,11 @@ public class PaloAuth implements Writable {
     }
 
     private boolean checkTblInternal(UserIdentity currentUser, String db, String tbl,
-            PrivPredicate wanted, PrivBitSet savedPrivs) {
+                                     PrivPredicate wanted, PrivBitSet savedPrivs) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasTblPrivFromLdap(currentUser, db, tbl, wanted)) {
+            return true;
+        }
+
         readLock();
         try {
             tablePrivTable.getPrivs(currentUser, db, tbl, savedPrivs);
@@ -472,6 +512,10 @@ public class PaloAuth implements Writable {
 
     private boolean checkResourceInternal(UserIdentity currentUser, String resourceName,
                                           PrivPredicate wanted, PrivBitSet savedPrivs) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasResourcePrivFromLdap(currentUser, resourceName, wanted)) {
+            return true;
+        }
+
         readLock();
         try {
             resourcePrivTable.getPrivs(currentUser, resourceName, savedPrivs);
@@ -484,6 +528,11 @@ public class PaloAuth implements Writable {
         }
     }
 
+    // Check if LDAP authentication is enabled.
+    private boolean isLdapAuthEnabled(){
+        return LdapConfig.ldap_authentication_enabled;
+    }
+
     // for test only
     public void clear() {
         userPrivTable.clear();
@@ -535,23 +584,7 @@ public class PaloAuth implements Writable {
                     false /* set by resolver */, true /* is replay */);
 
             // 4. grant privs of role to user
-            if (role != null) {
-                for (Map.Entry<TablePattern, PrivBitSet> entry : role.getTblPatternToPrivs().entrySet()) {
-                    // use PrivBitSet copy to avoid same object being changed synchronously
-                    grantInternal(userIdent, null /* role */, entry.getKey(), entry.getValue().copy(),
-                            false /* err on non exist */, true /* is replay */);
-                }
-                for (Map.Entry<ResourcePattern, PrivBitSet> entry : role.getResourcePatternToPrivs().entrySet()) {
-                    // use PrivBitSet copy to avoid same object being changed synchronously
-                    grantInternal(userIdent, null /* role */, entry.getKey(), entry.getValue().copy(),
-                                  false /* err on non exist */, true /* is replay */);
-                }
-            }
-
-            if (role != null) {
-                // add user to this role
-                role.addUser(userIdent);
-            }
+            grantPrivsByRole(userIdent, role);
 
             // other user properties
             propertyMgr.addUserResource(userIdent.getQualifiedUser(), false /* not system user */);
@@ -578,6 +611,28 @@ public class PaloAuth implements Writable {
         }
     }
 
+    private void grantPrivsByRole(UserIdentity userIdent, PaloRole role) throws DdlException {
+        writeLock();
+        try {
+            if (role != null) {
+                for (Map.Entry<TablePattern, PrivBitSet> entry : role.getTblPatternToPrivs().entrySet()) {
+                    // use PrivBitSet copy to avoid same object being changed synchronously
+                    grantInternal(userIdent, null /* role */, entry.getKey(), entry.getValue().copy(),
+                            false /* err on non exist */, true /* is replay */);
+                }
+                for (Map.Entry<ResourcePattern, PrivBitSet> entry : role.getResourcePatternToPrivs().entrySet()) {
+                    // use PrivBitSet copy to avoid same object being changed synchronously
+                    grantInternal(userIdent, null /* role */, entry.getKey(), entry.getValue().copy(),
+                            false /* err on non exist */, true /* is replay */);
+                }
+                // add user to this role
+                role.addUser(userIdent);
+            }
+        } finally {
+            writeUnlock();
+        }
+    }
+
     // drop user
     public void dropUser(DropUserStmt stmt) throws DdlException {
         dropUserInternal(stmt.getUserIdentity(), false);
@@ -788,6 +843,16 @@ public class PaloAuth implements Writable {
         }
     }
 
+    // Check whether the user exists. If the user exists, return UserIdentity, otherwise return null.
+    public UserIdentity getCurrentUserIdentity(UserIdentity userIdent) {
+        readLock();
+        try {
+            return userPrivTable.getCurrentUserIdentity(userIdent);
+        } finally {
+            readUnlock();
+        }
+    }
+
     // revoke
     public void revoke(RevokeStmt stmt) throws DdlException {
         PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
@@ -957,6 +1022,18 @@ public class PaloAuth implements Writable {
         LOG.info("finished to set password for {}. is replay: {}", userIdent, isReplay);
     }
 
+    // set ldap admin password.
+    public void setLdapPassword(SetLdapPassVar stmt) {
+        LdapClient.init(stmt.getLdapPassword());
+        LdapInfo info = new LdapInfo(stmt.getLdapPassword());
+        Catalog.getCurrentCatalog().getEditLog().logSetLdapPassword(info);
+    }
+
+    public void replaySetLdapPassword(LdapInfo info) {
+        LdapClient.init(info.getLdapPasswd());
+        LOG.debug("finish replaying ldap admin password.");
+    }
+
     // create role
     public void createRole(CreateRoleStmt stmt) throws DdlException {
         createRoleInternal(stmt.getQualifiedRole(), false);
@@ -1108,6 +1185,8 @@ public class PaloAuth implements Writable {
         List<String> userAuthInfo = Lists.newArrayList();
 
         // global
+        // ldap global privs.
+        PrivBitSet ldapGlobalPrivs = LdapPrivsChecker.getGlobalPrivFromLdap(userIdent);
         for (PrivEntry entry : userPrivTable.entries) {
             if (!entry.match(userIdent, true /* exact match */)) {
                 continue;
@@ -1120,23 +1199,27 @@ public class PaloAuth implements Writable {
             } else {
                 userAuthInfo.add((gEntry.getPassword() == null || gEntry.getPassword().length == 0) ? "No" : "Yes");
             }
-            userAuthInfo.add(gEntry.getPrivSet().toString() + " (" + gEntry.isSetByDomainResolver() + ")");
+            PrivBitSet savedPrivs = gEntry.getPrivSet().copy();
+            savedPrivs.or(ldapGlobalPrivs);
+            userAuthInfo.add(savedPrivs.toString() + " (" + gEntry.isSetByDomainResolver() + ")");
             break;
         }
 
         if (userAuthInfo.isEmpty()) {
-            if (!userIdent.isDomain()) {
+            userAuthInfo.add(userIdent.toString());
+            if (LdapPrivsChecker.hasLdapPrivs(userIdent)) {
+                userAuthInfo.add("No");
+                userAuthInfo.add(ldapGlobalPrivs.toString() + " (false)");
+            } else if (!userIdent.isDomain()) {
                 // If this is not a domain user identity, it must have global priv entry.
                 // TODO(cmy): I don't know why previous comment said:
                 // This may happen when we grant non global privs to a non exist user via GRANT stmt.
                 LOG.warn("user identity does not have global priv entry: {}", userIdent);
-                userAuthInfo.add(userIdent.toString());
                 userAuthInfo.add(FeConstants.null_string);
                 userAuthInfo.add(FeConstants.null_string);
             } else {
                 // this is a domain user identity and fall in here, which means this user identity does not
                 // have global priv, we need to check user property to see if it has password.
-                userAuthInfo.add(userIdent.toString());
                 userAuthInfo.add(propertyMgr.doesUserHasPassword(userIdent) ? "No" : "Yes");
                 userAuthInfo.add(FeConstants.null_string);
             }
@@ -1144,14 +1227,32 @@ public class PaloAuth implements Writable {
 
         // db
         List<String> dbPrivs = Lists.newArrayList();
+        Set<String> addedDbs = Sets.newHashSet();
         for (PrivEntry entry : dbPrivTable.entries) {
             if (!entry.match(userIdent, true /* exact match */)) {
                 continue;
             }
             DbPrivEntry dEntry = (DbPrivEntry) entry;
-            dbPrivs.add(dEntry.getOrigDb() + ": " + dEntry.getPrivSet().toString()
+            /**
+             * Doris and Ldap may have different privs on one database.
+             * Merge these privs and add.
+             */
+            PrivBitSet savedPrivs = dEntry.getPrivSet().copy();
+            savedPrivs.or(LdapPrivsChecker.getDbPrivFromLdap(userIdent, dEntry.getOrigDb()));
+            addedDbs.add(dEntry.getOrigDb());
+            dbPrivs.add(dEntry.getOrigDb() + ": " + savedPrivs.toString()
                     + " (" + entry.isSetByDomainResolver() + ")");
         }
+        // Add privs from ldap groups that have not been added in Doris.
+        if (LdapPrivsChecker.hasLdapPrivs(userIdent)) {
+            Map<TablePattern, PrivBitSet> ldapDbPrivs = LdapPrivsChecker.getLdapAllDbPrivs(userIdent);
+            for (Map.Entry<TablePattern, PrivBitSet> entry : ldapDbPrivs.entrySet()) {
+                if (!addedDbs.contains(entry.getKey().getQualifiedDb())) {
+                    dbPrivs.add(entry.getKey().getQualifiedDb() + ": " + entry.getValue().toString() + " (" + false + ")");
+                }
+            }
+        }
+
         if (dbPrivs.isEmpty()) {
             userAuthInfo.add(FeConstants.null_string);
         } else {
@@ -1160,15 +1261,34 @@ public class PaloAuth implements Writable {
 
         // tbl
         List<String> tblPrivs = Lists.newArrayList();
+        Set<String> addedtbls = Sets.newHashSet();
         for (PrivEntry entry : tablePrivTable.entries) {
             if (!entry.match(userIdent, true /* exact match */)) {
                 continue;
             }
             TablePrivEntry tEntry = (TablePrivEntry) entry;
+            /**
+             * Doris and Ldap may have different privs on one table.
+             * Merge these privs and add.
+             */
+            PrivBitSet savedPrivs = tEntry.getPrivSet().copy();
+            savedPrivs.or(LdapPrivsChecker.getTblPrivFromLdap(userIdent, tEntry.getOrigDb(), tEntry.getOrigTbl()));
+            addedtbls.add(tEntry.getOrigDb().concat(".").concat(tEntry.getOrigTbl()));
             tblPrivs.add(tEntry.getOrigDb() + "." + tEntry.getOrigTbl() + ": "
-                    + tEntry.getPrivSet().toString()
+                    + savedPrivs.toString()
                     + " (" + entry.isSetByDomainResolver() + ")");
         }
+        // Add privs from ldap groups that have not been added in Doris.
+        if (LdapPrivsChecker.hasLdapPrivs(userIdent)) {
+            Map<TablePattern, PrivBitSet> ldapTblPrivs = LdapPrivsChecker.getLdapAllTblPrivs(userIdent);
+            for (Map.Entry<TablePattern, PrivBitSet> entry : ldapTblPrivs.entrySet()) {
+                if (!addedtbls.contains(entry.getKey().getQualifiedDb().concat(".").concat(entry.getKey().getTbl()))) {
+                    tblPrivs.add(entry.getKey().getQualifiedDb().concat(".").concat(entry.getKey().getTbl())
+                            .concat(": ").concat(entry.getValue().toString()).concat(" (false)"));
+                }
+            }
+        }
+
         if (tblPrivs.isEmpty()) {
             userAuthInfo.add(FeConstants.null_string);
         } else {
@@ -1177,14 +1297,33 @@ public class PaloAuth implements Writable {
 
         // resource
         List<String> resourcePrivs = Lists.newArrayList();
+        Set<String> addedResources = Sets.newHashSet();
         for (PrivEntry entry : resourcePrivTable.entries) {
             if (!entry.match(userIdent, true /* exact match */)) {
                 continue;
             }
             ResourcePrivEntry rEntry = (ResourcePrivEntry) entry;
-            resourcePrivs.add(rEntry.getOrigResource() + ": " + rEntry.getPrivSet().toString()
+            /**
+             * Doris and Ldap may have different privs on one resource.
+             * Merge these privs and add.
+             */
+            PrivBitSet savedPrivs = rEntry.getPrivSet().copy();
+            savedPrivs.or(LdapPrivsChecker.getResourcePrivFromLdap(userIdent, rEntry.getOrigResource()));
+            addedResources.add(rEntry.getOrigResource());
+            resourcePrivs.add(rEntry.getOrigResource() + ": " + savedPrivs.toString()
                                       + " (" + entry.isSetByDomainResolver() + ")");
         }
+        // Add privs from ldap groups that have not been added in Doris.
+        if (LdapPrivsChecker.hasLdapPrivs(userIdent)) {
+            Map<ResourcePattern, PrivBitSet> ldapResourcePrivs = LdapPrivsChecker.getLdapAllResourcePrivs(userIdent);
+            for (Map.Entry<ResourcePattern, PrivBitSet> entry : ldapResourcePrivs.entrySet()) {
+                if (!addedResources.contains(entry.getKey().getResourceName())) {
+                    tblPrivs.add(entry.getKey().getResourceName().concat(": ").concat(entry.getValue().toString())
+                            .concat(" (false)"));
+                }
+            }
+        }
+
         if (resourcePrivs.isEmpty()) {
             userAuthInfo.add(FeConstants.null_string);
         } else {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloRole.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloRole.java
index d9d77e4..2956409 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloRole.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PaloRole.java
@@ -96,8 +96,8 @@ public class PaloRole implements Writable {
         return users;
     }
 
-    public void merge(PaloRole other) {
-        Preconditions.checkState(roleName.equalsIgnoreCase(other.getRoleName()));
+    // merge role not check role name.
+    public void mergeNotCheck(PaloRole other) {
         for (Map.Entry<TablePattern, PrivBitSet> entry : other.getTblPatternToPrivs().entrySet()) {
             if (tblPatternToPrivs.containsKey(entry.getKey())) {
                 PrivBitSet existPrivs = tblPatternToPrivs.get(entry.getKey());
@@ -116,6 +116,11 @@ public class PaloRole implements Writable {
         }
     }
 
+    public void merge(PaloRole other) {
+        Preconditions.checkState(roleName.equalsIgnoreCase(other.getRoleName()));
+        mergeNotCheck(other);
+    }
+
     public void addUser(UserIdentity userIdent) {
         users.add(userIdent);
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java
index 927517e..2ae20c3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPrivTable.java
@@ -175,6 +175,17 @@ public class UserPrivTable extends PrivTable {
         return false;
     }
 
+    // Check whether the user exists and return the UserIdentity.
+    public UserIdentity getCurrentUserIdentity(UserIdentity userIdent) {
+        for (PrivEntry privEntry : entries) {
+            GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) privEntry;
+            if (globalPrivEntry.match(userIdent, false)) {
+                return globalPrivEntry.getDomainUserIdent();
+            }
+        }
+        return null;
+    }
+
     @Override
     public void write(DataOutput out) throws IOException {
         if (!isClassNameWrote) {
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 38ea66d..728e89e 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
@@ -485,6 +485,11 @@ public class EditLog {
                     catalog.getAuth().replaySetPassword(privInfo);
                     break;
                 }
+                case OperationType.OP_SET_LDAP_PASSWORD: {
+                    LdapInfo ldapInfo = (LdapInfo) journal.getData();
+                    catalog.getAuth().replaySetLdapPassword(ldapInfo);
+                    break;
+                }
                 case OperationType.OP_CREATE_ROLE: {
                     PrivInfo privInfo = (PrivInfo) journal.getData();
                     catalog.getAuth().replayCreateRole(privInfo);
@@ -1128,6 +1133,10 @@ public class EditLog {
         logEdit(OperationType.OP_SET_PASSWORD, info);
     }
 
+    public void logSetLdapPassword(LdapInfo info) {
+        logEdit(OperationType.OP_SET_LDAP_PASSWORD, info);
+    }
+
     public void logCreateRole(PrivInfo info) {
         logEdit(OperationType.OP_CREATE_ROLE, info);
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/LdapInfo.java b/fe/fe-core/src/main/java/org/apache/doris/persist/LdapInfo.java
new file mode 100644
index 0000000..de637d9
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/LdapInfo.java
@@ -0,0 +1,51 @@
+// 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 com.google.gson.annotations.SerializedName;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class LdapInfo implements Writable {
+    @SerializedName(value = "ldapPasswd")
+    private String ldapPasswd;
+
+    public LdapInfo(String ldapPasswd) {
+        this.ldapPasswd = ldapPasswd;
+    }
+
+    public String getLdapPasswd() {
+        return ldapPasswd;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        String json = GsonUtils.GSON.toJson(this);
+        Text.writeString(out, json);
+    }
+
+    public static LdapInfo read(DataInput in) throws IOException {
+        String json = Text.readString(in);
+        return GsonUtils.GSON.fromJson(json, LdapInfo.class);
+    }
+}
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 48b8bf3..f591e5b 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
@@ -202,6 +202,8 @@ public class OperationType {
     // alter external table
     public static final short OP_ALTER_EXTERNAL_TABLE_SCHEMA = 280;
 
+    public static final short OP_SET_LDAP_PASSWORD = 290;
+
     // get opcode name by op codeStri
     public static String getOpName(short opCode) {
         try {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java
index 0aeadd8..16fc2d7 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java
@@ -26,6 +26,7 @@ import org.apache.doris.mysql.MysqlCapability;
 import org.apache.doris.mysql.MysqlChannel;
 import org.apache.doris.mysql.MysqlCommand;
 import org.apache.doris.mysql.MysqlSerializer;
+import org.apache.doris.mysql.privilege.PaloRole;
 import org.apache.doris.plugin.AuditEvent.AuditEventBuilder;
 import org.apache.doris.thrift.TResourceInfo;
 import org.apache.doris.thrift.TUniqueId;
@@ -74,6 +75,10 @@ public class ConnectContext {
     protected volatile String clusterName = "";
     // username@host of current login user
     protected volatile String qualifiedUser;
+    // LDAP authenticated but the Doris account does not exist, set the flag, and the user login Doris as Temporary user.
+    protected volatile boolean isTempUser = false;
+    // Save the privs from the ldap groups.
+    protected volatile PaloRole ldapGroupsPrivs = null;
     // username@host combination for the Doris account
     // that the server used to authenticate the current client.
     // In other word, currentUserIdentity is the entry that matched in Doris auth table.
@@ -261,6 +266,16 @@ public class ConnectContext {
         this.qualifiedUser = qualifiedUser;
     }
 
+    public boolean getIsTempUser() { return isTempUser;}
+
+    public void setIsTempUser(boolean isTempUser) { this.isTempUser = isTempUser;}
+
+    public PaloRole getLdapGroupsPrivs() { return ldapGroupsPrivs; }
+
+    public void setLdapGroupsPrivs(PaloRole ldapGroupsPrivs) {
+        this.ldapGroupsPrivs = ldapGroupsPrivs;
+    }
+
     // for USER() function
     public UserIdentity getUserIdentity() {
         return new UserIdentity(qualifiedUser, remoteIP);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectScheduler.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectScheduler.java
index 80f7a92..6501a9a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectScheduler.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectScheduler.java
@@ -20,6 +20,7 @@ package org.apache.doris.qe;
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.ThreadPoolManager;
+import org.apache.doris.ldap.LdapAuthenticate;
 import org.apache.doris.mysql.MysqlProto;
 import org.apache.doris.mysql.nio.NConnectContext;
 import org.apache.doris.mysql.privilege.PrivPredicate;
@@ -103,7 +104,11 @@ public class ConnectScheduler {
             connByUser.put(ctx.getQualifiedUser(), new AtomicInteger(0));
         }
         int conns = connByUser.get(ctx.getQualifiedUser()).get();
-        if (conns >= ctx.getCatalog().getAuth().getMaxConn(ctx.getQualifiedUser())) {
+        if (ctx.getIsTempUser()) {
+            if (conns >= LdapAuthenticate.getMaxConn()) {
+                return false;
+            }
+        } else if (conns >= ctx.getCatalog().getAuth().getMaxConn(ctx.getQualifiedUser())) {
             return false;
         }
         numberConnection++;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SetExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/SetExecutor.java
index 6e6ab38..32db27f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SetExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SetExecutor.java
@@ -19,6 +19,7 @@ package org.apache.doris.qe;
 
 import org.apache.doris.analysis.SetNamesVar;
 import org.apache.doris.analysis.SetPassVar;
+import org.apache.doris.analysis.SetLdapPassVar;
 import org.apache.doris.analysis.SetStmt;
 import org.apache.doris.analysis.SetTransaction;
 import org.apache.doris.analysis.SetVar;
@@ -44,6 +45,9 @@ public class SetExecutor {
             // Set password
             SetPassVar setPassVar = (SetPassVar) var;
             ctx.getCatalog().getAuth().setPassword(setPassVar);
+        } else if(var instanceof SetLdapPassVar){
+            SetLdapPassVar setLdapPassVar = (SetLdapPassVar) var;
+            ctx.getCatalog().getAuth().setLdapPassword(setLdapPassVar);
         } else if (var instanceof SetNamesVar) {
             // do nothing
             return;
diff --git a/fe/fe-core/src/main/jflex/sql_scanner.flex b/fe/fe-core/src/main/jflex/sql_scanner.flex
index a8c1d7e..3ba2ab7 100644
--- a/fe/fe-core/src/main/jflex/sql_scanner.flex
+++ b/fe/fe-core/src/main/jflex/sql_scanner.flex
@@ -284,6 +284,7 @@ import org.apache.doris.qe.SqlModeHelper;
         keywordMap.put("partition", new Integer(SqlParserSymbols.KW_PARTITION));
         keywordMap.put("partitions", new Integer(SqlParserSymbols.KW_PARTITIONS));
         keywordMap.put("password", new Integer(SqlParserSymbols.KW_PASSWORD));
+        keywordMap.put("ldap_admin_password", new Integer(SqlParserSymbols.KW_LDAP_ADMIN_PASSWORD));
         keywordMap.put("path", new Integer(SqlParserSymbols.KW_PATH));
         keywordMap.put("pause", new Integer(SqlParserSymbols.KW_PAUSE));
         keywordMap.put("plugin", new Integer(SqlParserSymbols.KW_PLUGIN));
diff --git a/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapAuthenticateTest.java b/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapAuthenticateTest.java
new file mode 100644
index 0000000..63b6c06
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapAuthenticateTest.java
@@ -0,0 +1,212 @@
+package org.apache.doris.ldap;
+
+import com.google.common.collect.Lists;
+import mockit.Delegate;
+import mockit.Expectations;
+import mockit.Mocked;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Catalog;
+import org.apache.doris.cluster.ClusterNamespace;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.mysql.privilege.PaloAuth;
+import org.apache.doris.mysql.privilege.PaloRole;
+import org.apache.doris.qe.ConnectContext;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LdapAuthenticateTest {
+    private static final String DEFAULT_CLUSTER = "default_cluster";
+    private static final String USER_NAME = "user";
+    private static final String IP = "192.168.1.1";
+    private static final String TABLE_RD = "palo_rd";
+
+    private PaloRole ldapGroupsPrivs;
+
+    @Mocked
+    private LdapClient ldapClient;
+    @Mocked
+    private LdapPrivsChecker ldapPrivsChecker;
+    @Mocked
+    private Catalog catalog;
+    @Mocked
+    private PaloAuth auth;
+
+    @Before
+    public void setUp() throws DdlException {
+        new Expectations() {
+            {
+                auth.doesRoleExist(anyString);
+                minTimes = 0;
+                result = true;
+
+                auth.mergeRolesNoCheckName((List<String>) any, (PaloRole) any);
+                minTimes = 0;
+                result = new Delegate() {
+                    void fakeMergeRolesNoCheckName(List<String> roles, PaloRole savedRole) {
+                        ldapGroupsPrivs = savedRole;
+                    }
+                };
+
+                catalog.getAuth();
+                minTimes = 0;
+                result = auth;
+
+                Catalog.getCurrentCatalog();
+                minTimes = 0;
+                result = catalog;
+            }
+        };
+    }
+
+    private void setCheckPassword(boolean res) {
+        new Expectations() {
+            {
+                LdapClient.checkPassword(anyString, anyString);
+                minTimes = 0;
+                result = res;
+            }
+        };
+    }
+
+    private void setCheckPasswordException() {
+        new Expectations() {
+            {
+                LdapClient.checkPassword(anyString, anyString);
+                minTimes = 0;
+                result = new RuntimeException("exception");
+            }
+        };
+    }
+
+    private void setGetGroups(boolean res) {
+        new Expectations() {
+            {
+                if (res) {
+                    LdapClient.getGroups(anyString);
+                    minTimes = 0;
+                    result = new Delegate() {
+                        List<String> fakeGetGroups(String user) {
+                            List<String> list = new ArrayList<>();
+                            list.add(TABLE_RD);
+                            return list;
+                        }
+                    };
+                } else {
+                    LdapClient.getGroups(anyString);
+                    minTimes = 0;
+                    result = Lists.newArrayList();
+                }
+            }
+        };
+    }
+
+    private void setGetGroupsException() {
+        new Expectations() {
+            {
+                LdapClient.getGroups(anyString);
+                minTimes = 0;
+                result = new RuntimeException("exception");
+            }
+        };
+    }
+
+    private void setGetCurrentUserIdentity(boolean res) {
+        new Expectations() {
+            {
+                if (res) {
+                    auth.getCurrentUserIdentity((UserIdentity) any);
+                    minTimes = 0;
+                    result = new UserIdentity(ClusterNamespace.getFullName(DEFAULT_CLUSTER, USER_NAME), IP);
+                } else {
+                    auth.getCurrentUserIdentity((UserIdentity) any);
+                    minTimes = 0;
+                    result = null;
+                }
+            }
+        };
+    }
+
+    private ConnectContext getContext() {
+        ConnectContext context = new ConnectContext(null);
+        context.setCatalog(catalog);
+        context.setThreadLocalInfo();
+        return context;
+    }
+
+
+    @Test
+    public void testAuthenticate() {
+        ConnectContext context = getContext();
+        setCheckPassword(true);
+        setGetGroups(true);
+        setGetCurrentUserIdentity(true);
+        String qualifiedUser = ClusterNamespace.getFullName(DEFAULT_CLUSTER, USER_NAME);
+        Assert.assertTrue(LdapAuthenticate.authenticate(context, "123", qualifiedUser));
+        Assert.assertFalse(context.getIsTempUser());
+        Assert.assertSame(ldapGroupsPrivs, context.getLdapGroupsPrivs());
+    }
+
+    @Test
+    public void testAuthenticateWithWrongPassword() {
+        ConnectContext context = getContext();
+        setCheckPassword(false);
+        setGetGroups(true);
+        setGetCurrentUserIdentity(true);
+        String qualifiedUser = ClusterNamespace.getFullName(DEFAULT_CLUSTER, USER_NAME);
+        Assert.assertFalse(LdapAuthenticate.authenticate(context, "123", qualifiedUser));
+        Assert.assertFalse(context.getIsTempUser());
+        Assert.assertNull(context.getLdapGroupsPrivs());
+    }
+
+    @Test
+    public void testAuthenticateWithCheckPasswordException() {
+        ConnectContext context = getContext();
+        setCheckPasswordException();
+        setGetGroups(true);
+        setGetCurrentUserIdentity(true);
+        String qualifiedUser = ClusterNamespace.getFullName(DEFAULT_CLUSTER, USER_NAME);
+        Assert.assertFalse(LdapAuthenticate.authenticate(context, "123", qualifiedUser));
+        Assert.assertFalse(context.getIsTempUser());
+        Assert.assertNull(context.getLdapGroupsPrivs());
+    }
+
+    @Test
+    public void testAuthenticateGetGroupsNull() {
+        ConnectContext context = getContext();
+        setCheckPassword(true);
+        setGetGroups(false);
+        setGetCurrentUserIdentity(true);
+        String qualifiedUser = ClusterNamespace.getFullName(DEFAULT_CLUSTER, USER_NAME);
+        Assert.assertTrue(LdapAuthenticate.authenticate(context, "123", qualifiedUser));
+        Assert.assertFalse(context.getIsTempUser());
+        Assert.assertNull(context.getLdapGroupsPrivs());
+    }
+
+    @Test
+    public void testAuthenticateGetGroupsException() {
+        ConnectContext context = getContext();
+        setCheckPassword(true);
+        setGetGroupsException();
+        setGetCurrentUserIdentity(true);
+        String qualifiedUser = ClusterNamespace.getFullName(DEFAULT_CLUSTER, USER_NAME);
+        Assert.assertFalse(LdapAuthenticate.authenticate(context, "123", qualifiedUser));
+        Assert.assertFalse(context.getIsTempUser());
+        Assert.assertNull(context.getLdapGroupsPrivs());
+    }
+
+    @Test
+    public void testAuthenticateUserNotExistInDoris() {
+        ConnectContext context = getContext();
+        setCheckPassword(true);
+        setGetGroups(true);
+        setGetCurrentUserIdentity(false);
+        String qualifiedUser = ClusterNamespace.getFullName(DEFAULT_CLUSTER, USER_NAME);
+        Assert.assertTrue(LdapAuthenticate.authenticate(context, "123", qualifiedUser));
+        Assert.assertTrue(context.getIsTempUser());
+        Assert.assertSame(ldapGroupsPrivs, context.getLdapGroupsPrivs());
+    }
+}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapClientTest.java b/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapClientTest.java
new file mode 100644
index 0000000..3918e9a
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapClientTest.java
@@ -0,0 +1,102 @@
+package org.apache.doris.ldap;
+
+import com.clearspring.analytics.util.Lists;
+import mockit.Delegate;
+import mockit.Expectations;
+import mockit.Mocked;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.common.util.SymmetricEncryption;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.AbstractContextMapper;
+import org.springframework.ldap.query.LdapQuery;
+
+import java.util.List;
+
+public class LdapClientTest {
+    private static final String ADMIN_PASSWORD = "admin";
+
+    @Mocked
+    private LdapTemplate ldapTemplate;
+
+    @Before
+    public void setUp() {
+        LdapConfig.ldap_authentication_enabled = true;
+        LdapConfig.ldap_host = "127.0.0.1";
+        LdapConfig.ldap_port = 389;
+        LdapConfig.ldap_admin_name = "cn=admin,dc=baidu,dc=com";
+        LdapConfig.ldap_user_basedn = "dc=baidu,dc=com";
+        LdapConfig.ldap_group_basedn = "ou=group,dc=baidu,dc=com";
+        LdapConfig.ldap_user_filter = "(&(uid={login}))";
+        LdapClient.init(SymmetricEncryption.encrypt(ADMIN_PASSWORD));
+    }
+    
+    private void mockLdapTemplateSearch(List list) {
+        new Expectations() {
+            {
+                ldapTemplate.search((LdapQuery) any, (AbstractContextMapper) any);
+                minTimes = 0;
+                result = list;
+            }
+        };
+    }
+
+    private void mockLdapTemplateAuthenticate(String password) {
+        new Expectations() {
+            {
+                ldapTemplate.authenticate((LdapQuery) any, anyString);
+                minTimes = 0;
+                result = new Delegate() {
+                    void fakeAuthenticate(LdapQuery query, String passwd) {
+                        if (passwd.equals(password)) {
+                            return;
+                        } else {
+                            throw new RuntimeException("exception");
+                        }
+                    }
+                };
+            }
+        };
+    }
+
+    @Test
+    public void testDoesUserExist() {
+        List<String> list = Lists.newArrayList();
+        list.add("zhangsan");
+        mockLdapTemplateSearch(list);
+        Assert.assertTrue(LdapClient.doesUserExist("zhangsan"));
+    }
+
+    @Test
+    public void testDoesUserExistFail() {
+        mockLdapTemplateSearch(null);
+        Assert.assertFalse(LdapClient.doesUserExist("zhangsan"));
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testDoesUserExistException() {
+        List<String> list = Lists.newArrayList();
+        list.add("zhangsan");
+        list.add("zhangsan");
+        mockLdapTemplateSearch(list);
+        Assert.assertTrue(LdapClient.doesUserExist("zhangsan"));
+        Assert.fail("No Exception throws.");
+    }
+
+    @Test
+    public void testCheckPassword() {
+        mockLdapTemplateAuthenticate(ADMIN_PASSWORD);
+        Assert.assertTrue(LdapClient.checkPassword("zhangsan", ADMIN_PASSWORD));
+        Assert.assertFalse(LdapClient.checkPassword("zhangsan", "123"));
+    }
+
+    @Test
+    public void testGetGroups() {
+        List<String> list = Lists.newArrayList();
+        list.add("cn=groupName,ou=groups,dc=example,dc=com");
+        mockLdapTemplateSearch(list);
+        Assert.assertEquals(1, LdapClient.getGroups("zhangsan").size());
+    }
+}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapPrivsCheckerTest.java b/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapPrivsCheckerTest.java
new file mode 100644
index 0000000..e5cb54a
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/ldap/LdapPrivsCheckerTest.java
@@ -0,0 +1,189 @@
+package org.apache.doris.ldap;
+
+import mockit.Expectations;
+import mockit.Mocked;
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.mysql.privilege.PaloPrivilege;
+import org.apache.doris.mysql.privilege.PaloRole;
+import org.apache.doris.mysql.privilege.PrivBitSet;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+
+public class LdapPrivsCheckerTest {
+    private static final String CLUSTER = "default_cluster";
+    private static final String DB = "palodb";
+    private static final String TABLE_DB = "tabledb";
+    private static final String TABLE1 = "table1";
+    private static final String TABLE2 = "table2";
+    private static final String RESOURCE1 = "spark_resource";
+    private static final String RESOURCE2 = "resource";
+    private static final String USER = "default_cluster:zhangsan";
+    private static final String IP = "192.168.0.1";
+    private UserIdentity userIdent = UserIdentity.createAnalyzedUserIdentWithIp(USER, IP);
+
+    @Mocked
+    private ConnectContext context;
+
+    @Before
+    public void setUp() {
+        LdapConfig.ldap_authentication_enabled = true;
+        new Expectations() {
+            {
+                ConnectContext.get();
+                minTimes = 0;
+                result = context;
+
+                PaloRole role = new PaloRole("");
+                Map<TablePattern, PrivBitSet> tblPatternToPrivs = role.getTblPatternToPrivs();
+
+                TablePattern global = new TablePattern("*", "*");
+                tblPatternToPrivs.put(global, PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.CREATE_PRIV));
+                TablePattern db = new TablePattern(DB, "*");
+                tblPatternToPrivs.put(db, PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.LOAD_PRIV));
+                TablePattern tbl1 = new TablePattern(TABLE_DB, TABLE1);
+                tblPatternToPrivs.put(tbl1, PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.ALTER_PRIV));
+                TablePattern tbl2 = new TablePattern(TABLE_DB, TABLE2);
+                tblPatternToPrivs.put(tbl2, PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.DROP_PRIV));
+
+                Map<ResourcePattern, PrivBitSet> resourcePatternToPrivs = role.getResourcePatternToPrivs();
+                ResourcePattern globalResource = new ResourcePattern("*");
+                resourcePatternToPrivs.put(globalResource, PrivBitSet.of(PaloPrivilege.USAGE_PRIV));
+                ResourcePattern resource1 = new ResourcePattern(RESOURCE1);
+                resourcePatternToPrivs.put(resource1, PrivBitSet.of(PaloPrivilege.USAGE_PRIV));
+                ResourcePattern resource2 = new ResourcePattern(RESOURCE1);
+                resourcePatternToPrivs.put(resource2, PrivBitSet.of(PaloPrivilege.USAGE_PRIV));
+                try {
+                    global.analyze(CLUSTER);
+                    db.analyze(CLUSTER);
+                    tbl1.analyze(CLUSTER);
+                    tbl2.analyze(CLUSTER);
+                    resource1.analyze();
+                    resource2.analyze();
+                } catch (AnalysisException e) {
+                    e.printStackTrace();
+                }
+                context.getLdapGroupsPrivs();
+                minTimes = 0;
+                result = role;
+
+                context.getCurrentUserIdentity();
+                minTimes = 0;
+                result = UserIdentity.createAnalyzedUserIdentWithIp(USER, IP);
+            }
+        };
+    }
+
+    @Test
+    public void testHasGlobalPrivFromLdap() {
+        Assert.assertTrue(LdapPrivsChecker.hasGlobalPrivFromLdap(userIdent, PrivPredicate.CREATE));
+        Assert.assertTrue(LdapPrivsChecker.hasGlobalPrivFromLdap(userIdent, PrivPredicate.USAGE));
+        Assert.assertFalse(LdapPrivsChecker.hasGlobalPrivFromLdap(userIdent, PrivPredicate.DROP));
+    }
+
+    @Test
+    public void testHasDbPrivFromLdap() {
+        Assert.assertTrue(LdapPrivsChecker.hasDbPrivFromLdap(userIdent, CLUSTER + ":" + DB, PrivPredicate.LOAD));
+        Assert.assertFalse(LdapPrivsChecker.hasDbPrivFromLdap(userIdent, CLUSTER + ":" + DB, PrivPredicate.DROP));
+        Assert.assertTrue(LdapPrivsChecker.hasDbPrivFromLdap(userIdent, PrivPredicate.LOAD));
+        Assert.assertFalse(LdapPrivsChecker.hasDbPrivFromLdap(userIdent, PrivPredicate.DROP));
+    }
+
+    @Test
+    public void testHasTblPrivFromLdap() {
+        Assert.assertTrue(LdapPrivsChecker.hasTblPrivFromLdap(userIdent, CLUSTER + ":" + TABLE_DB, TABLE1,
+                PrivPredicate.ALTER));
+        Assert.assertFalse(LdapPrivsChecker.hasTblPrivFromLdap(userIdent, CLUSTER + ":" + TABLE_DB, TABLE1,
+                PrivPredicate.DROP));
+        Assert.assertTrue(LdapPrivsChecker.hasTblPrivFromLdap(userIdent, CLUSTER + ":" + TABLE_DB, TABLE2,
+                PrivPredicate.DROP));
+        Assert.assertFalse(LdapPrivsChecker.hasTblPrivFromLdap(userIdent, CLUSTER + ":" + TABLE_DB, TABLE2,
+                PrivPredicate.CREATE));
+        Assert.assertTrue(LdapPrivsChecker.hasTblPrivFromLdap(userIdent, PrivPredicate.ALTER));
+        Assert.assertFalse(LdapPrivsChecker.hasTblPrivFromLdap(userIdent, PrivPredicate.LOAD));
+    }
+
+    @Test
+    public void testHasResourcePrivFromLdap() {
+        Assert.assertTrue(LdapPrivsChecker.hasResourcePrivFromLdap(userIdent, RESOURCE1, PrivPredicate.USAGE));
+        Assert.assertFalse(LdapPrivsChecker.hasResourcePrivFromLdap(userIdent, "resource",
+                PrivPredicate.USAGE));
+    }
+
+    @Test
+    public void testGetGlobalPrivFromLdap() {
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.CREATE_PRIV, PaloPrivilege.USAGE_PRIV).toString(),
+                LdapPrivsChecker.getGlobalPrivFromLdap(userIdent).toString());
+    }
+
+    @Test
+    public void testGetDbPrivFromLdap() {
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.LOAD_PRIV).toString(),
+                LdapPrivsChecker.getDbPrivFromLdap(userIdent, CLUSTER + ":" + DB).toString());
+    }
+
+    @Test
+    public void testGetTblPrivFromLdap() {
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.ALTER_PRIV).toString(),
+                LdapPrivsChecker.getTblPrivFromLdap(userIdent, CLUSTER + ":" + TABLE_DB, TABLE1).toString());
+    }
+
+    @Test
+    public void testGetResourcePrivFromLdap() {
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.USAGE_PRIV).toString(),
+                LdapPrivsChecker.getResourcePrivFromLdap(userIdent, RESOURCE1).toString());
+    }
+
+    @Test
+    public void testHasPrivsOfDb() {
+        Assert.assertTrue(LdapPrivsChecker.hasPrivsOfDb(userIdent, CLUSTER + ":" + TABLE_DB));
+    }
+
+    @Test
+    public void testIsCurrentUser() {
+        Assert.assertTrue(LdapPrivsChecker.isCurrentUser(userIdent));
+        Assert.assertFalse(LdapPrivsChecker.isCurrentUser(UserIdentity.
+                createAnalyzedUserIdentWithIp("default_cluster:lisi", IP)));
+        Assert.assertFalse(LdapPrivsChecker.isCurrentUser(UserIdentity.
+                createAnalyzedUserIdentWithIp(USER, "127.0.0.1")));
+    }
+
+    @Test
+    public void testGetLdapAllDbPrivs() throws AnalysisException {
+        Map<TablePattern, PrivBitSet> allDb = LdapPrivsChecker.getLdapAllDbPrivs(userIdent);
+        TablePattern db = new TablePattern(DB, "*");
+        db.analyze(CLUSTER);
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.LOAD_PRIV).toString(),
+                allDb.get(db).toString());
+    }
+
+    @Test
+    public void testGetLdapAllTblPrivs() throws AnalysisException {
+        Map<TablePattern, PrivBitSet> allTbl = LdapPrivsChecker.getLdapAllTblPrivs(userIdent);
+        TablePattern tbl1 = new TablePattern(TABLE_DB, TABLE1);
+        TablePattern tbl2 = new TablePattern(TABLE_DB, TABLE2);
+        tbl1.analyze(CLUSTER);
+        tbl2.analyze(CLUSTER);
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.ALTER_PRIV).toString(),
+                allTbl.get(tbl1).toString());
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.DROP_PRIV).toString(),
+                allTbl.get(tbl2).toString());
+    }
+
+    @Test
+    public void testGetLdapAllResourcePrivs() {
+        Map<ResourcePattern, PrivBitSet> allResource = LdapPrivsChecker.getLdapAllResourcePrivs(userIdent);
+        ResourcePattern resource1 = new ResourcePattern(RESOURCE1);
+        ResourcePattern resource2 = new ResourcePattern(RESOURCE1);
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.USAGE_PRIV).toString(), allResource.get(resource1).toString());
+        Assert.assertEquals(PrivBitSet.of(PaloPrivilege.USAGE_PRIV).toString(), allResource.get(resource2).toString());
+    }
+}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java
index ebd1f96..e9ac2f0 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java
@@ -23,7 +23,11 @@ import mockit.Mocked;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.catalog.Database;
+import org.apache.doris.cluster.ClusterNamespace;
 import org.apache.doris.common.DdlException;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.ldap.LdapAuthenticate;
+import org.apache.doris.ldap.LdapClient;
 import org.apache.doris.mysql.privilege.PaloAuth;
 import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.qe.ConnectContext;
@@ -40,6 +44,8 @@ import java.util.List;
 
 public class MysqlProtoTest {
     private static final Logger LOG = org.slf4j.LoggerFactory.getLogger(MysqlProtoTest.class);
+    private static final String PASSWORD_CLEAR_TEXT = "123456";
+
     @Mocked
     private MysqlChannel channel;
     @Mocked
@@ -48,6 +54,12 @@ public class MysqlProtoTest {
     private Catalog catalog;
     @Mocked
     private PaloAuth auth;
+    @Mocked
+    private LdapClient ldapClient;
+    @Mocked
+    private LdapAuthenticate ldapAuthenticate;
+    @Mocked
+    private MysqlClearTextPacket clearTextPacket;
 
     @Before
     public void setUp() throws DdlException {
@@ -64,7 +76,7 @@ public class MysqlProtoTest {
                 result = new Delegate() {
                     boolean fakeCheckPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
                                               List<UserIdentity> currentUser) {
-                        UserIdentity userIdentity = new UserIdentity("defaut_cluster:user", "192.168.1.1");
+                        UserIdentity userIdentity = new UserIdentity("default_cluster:user", "192.168.1.1");
                         currentUser.add(userIdentity);
                         return true;
                     }
@@ -150,6 +162,20 @@ public class MysqlProtoTest {
         };
     }
 
+    private void mockMysqlClearTextPacket(String password) throws IOException {
+        new Expectations() {
+            {
+                clearTextPacket.getPassword();
+                minTimes = 0;
+                result = password;
+
+                clearTextPacket.readFrom((ByteBuffer) any);
+                minTimes = 0;
+                result = true;
+            }
+        };
+    }
+
     private void mockPassword(boolean res) {
         // mock password
         new Expectations(password) {
@@ -172,6 +198,27 @@ public class MysqlProtoTest {
     private void mockAccess() throws Exception {
     }
 
+    private void mockLdap(String user, boolean userExist) {
+        LdapConfig.ldap_authentication_enabled = true;
+
+        new Expectations() {
+            {
+                LdapAuthenticate.authenticate((ConnectContext) any, anyString, anyString);
+                minTimes = 0;
+                result = new Delegate() {
+                    boolean fakeLdapAuthenticate(ConnectContext context, String password, String qualifiedUser) {
+                        return password.equals(PASSWORD_CLEAR_TEXT)
+                                && ClusterNamespace.getNameFromFullName(qualifiedUser).equals(user);
+                    }
+                };
+
+                LdapClient.doesUserExist(anyString);
+                minTimes = 0;
+                result = userExist;
+            }
+        };
+    }
+
     @Test
     public void testNegotiate() throws Exception {
         mockChannel("user", true);
@@ -212,6 +259,45 @@ public class MysqlProtoTest {
     }
 
     @Test
+    public void testNegotiateLdap() throws Exception {
+        mockChannel("user", true);
+        mockPassword(true);
+        mockAccess();
+        mockMysqlClearTextPacket(PASSWORD_CLEAR_TEXT);
+        mockLdap("user", true);
+        ConnectContext context = new ConnectContext(null);
+        context.setCatalog(catalog);
+        context.setThreadLocalInfo();
+        Assert.assertTrue(MysqlProto.negotiate(context));
+    }
+
+    @Test
+    public void testNegotiateLdapInvalidPasswd() throws Exception {
+        mockChannel("user", true);
+        mockPassword(true);
+        mockAccess();
+        mockMysqlClearTextPacket("654321");
+        mockLdap("user", true);
+        ConnectContext context = new ConnectContext(null);
+        context.setCatalog(catalog);
+        context.setThreadLocalInfo();
+        Assert.assertFalse(MysqlProto.negotiate(context));
+    }
+
+    @Test
+    public void testNegotiateLdapRoot() throws Exception {
+        mockChannel("root", true);
+        mockPassword(true);
+        mockAccess();
+        mockLdap("root", false);
+        mockMysqlClearTextPacket("654321");
+        ConnectContext context = new ConnectContext(null);
+        context.setCatalog(catalog);
+        context.setThreadLocalInfo();
+        Assert.assertTrue(MysqlProto.negotiate(context));
+    }
+
+    @Test
     public void testRead() throws UnsupportedEncodingException {
         MysqlSerializer serializer = MysqlSerializer.newInstance();
         serializer.writeInt1(200);
diff --git a/fe/fe-core/src/test/java/org/apache/doris/persist/LdapInfoTest.java b/fe/fe-core/src/test/java/org/apache/doris/persist/LdapInfoTest.java
new file mode 100644
index 0000000..ea5535d
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/persist/LdapInfoTest.java
@@ -0,0 +1,35 @@
+package org.apache.doris.persist;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class LdapInfoTest {
+
+    @Test
+    public void test() throws IOException {
+        LdapInfo ldapInfo = new LdapInfo("123456");
+
+        // 1. Write objects to file
+        File file = new File("./ldapInfo");
+        file.createNewFile();
+        DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
+        ldapInfo.write(dos);
+        dos.flush();
+        dos.close();
+
+        // 2. Read objects from file
+        DataInputStream dis = new DataInputStream(new FileInputStream(file));
+        Assert.assertEquals("123456", LdapInfo.read(dis).getLdapPasswd());
+
+        // 3. delete files
+        dis.close();
+        file.delete();
+    }
+}

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