【LDAP】Spring项目同步LDAP域用户信息总览(含ldapTemplate.search仅查询1000条数据的解决方案)
一、LDAP简介
- 维基百科介绍:
轻量级目录访问协议( LDAP) 是一种开放的、供应商中立的行业标准应用协议,用于通过Internet 协议(IP) 网络访问和维护分布式目录信息服务。目录服务允许在整个网络中共享有关用户、系统、网络、服务和应用程序的信息,因此在开发Intranet和 Internet 应用程序中发挥着重要作用。例如,目录服务可以提供任何有组织的记录集,通常具有分层结构,例如公司电子邮件目录。类似地,电话簿也是具有地址和电话号码的用户列表。
LDAP广泛应用于组织中的身份管理、访问控制、电子邮件系统、单点登录等领域,提供了一种高效、灵活的方式来组织和检索分布式信息。
二、关于LDAP的一些关键概念
- 目录服务(Directory Service): LDAP主要用于访问目录服务,而目录服务是一种层次结构化的数据库,用于存储和检索有关组织中各种实体(如用户、组、设备)的信息。
- 层次结构(Hierarchy): LDAP目录数据以层次结构的形式组织,类似于文件系统的树状结构。每个目录项(entry)都有一个唯一的标识符(Distinguished Name,DN),表示在层次结构中的位置。
- 条目(Entry): 目录中的每个实体都被称为一个条目。每个条目包含一组属性-值对,描述了实体的特征和属性。
- 属性(Attribute): 属性是条目的特定信息,例如用户的姓名、电子邮件地址等。每个属性都有一个唯一的标识符(Attribute Type),对应着属性值。
- LDAP协议: LDAP定义了一套客户端和服务器之间进行通信的规范,以实现对目录服务的操作。常见的LDAP操作包括搜索、添加、删除、修改等。
- 端口: LDAP通常使用TCP协议,标准端口号是389。同时,LDAP也可以通过安全套接字层(SSL)进行加密通信,使用的端口号是636。
- 认证: LDAP支持基于用户名和密码的身份验证,确保只有授权用户能够访问和修改目录信息。
三、LDAP用户信息案例
以下是一个简单的LDAP用户信息的例子:
- Distinguished Name (DN):
uid=johndoe,ou=people,dc=example,dc=com
- 表示该用户在LDAP层次结构中的唯一标识符。
- Attributes (属性):
- ‘uid’: johndoe
- 用户的唯一标识符,通常是用户名。
- ‘cn’:John Doe
- 用户的通用名称,即用户的全名。
- ‘sn’: Doe
- 用户的姓氏。
- ‘givenName’: John
- 用户的名字。
- ‘mail’: johndoe@example.com
- 用户的电子邮件地址。
- ‘userPassword’: {SHA}5en6G6MezRroT3XKqkdPOmY/BfQ=
- 用户的密码,通常以加密形式存储。
这是一个基本的LDAP用户信息示例,其中包含了用户的基本身份信息、电子邮件地址以及密码。这样的信息可以用于实现身份验证、访问控制等功能。在实际应用中,用户信息的属性和结构可能会根据组织的需求而有所不同。
四、Java同步LDAP用户信息到MySQL
1、引入Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
2、LDAP认证信息配置
注:LDAP 389端口和3268的区别
LDAP(Lightweight Directory Access Protocol)通常使用两个不同的端口进行通信,分别是389和3268。它们之间的主要区别在于目标服务器和所执行的操作。
- LDAP端口 389:
- 用途: 389端口是LDAP的标准端口,主要用于非安全的LDAP通信。
- 目标服务器: 通常用于与单个LDAP服务器建立连接,执行标准的LDAP操作。
- 操作类型: 适用于基本的LDAP查询、添加、修改、删除等操作。
- 加密: 数据在传输过程中不加密,可能存在安全风险。
- LDAP端口 3268:
- 用途: 3268端口也是LDAP端口,但用于LDAP全局编录服务。
- 目标服务器: 用于在整个Active Directory森林中搜索(查询)用户信息。
- 操作类型: 主要用于执行LDAP搜索操作,支持在多个域控制器之间进行全局搜索。
- 加密: 支持加密通信,提供更安全的数据传输。
总结:
- 使用389端口时,连接的是特定的LDAP服务器,适用于单个域或单个目录树。
- 使用3268端口时,连接的是全局编录服务,适用于在整个Active Directory森林中进行全局搜索,跨越多个域。
由于当前案例为全局检索用户信息,故使用 3268 端口
# ldap 连接信息
ldap:
urls: ldap://10.*.*.*:3268
username: *********
password: *********
3、用户实体
用于将检索到的用户信息映射为Java实例对象
import lombok.Data;
@Data
public class LdapUser {
/**
* 唯一标识(code)
*/
private String code;
/**
* 用户名(account)
*/
private String account;
/**
* 昵称(name)
*/
private String name;
/**
* 真名(realName)
*/
private String realName;
/**
* 用户邮箱(email)
*/
private String email;
/**
* 手机号
*/
private String phone;
}
4、同步功能代码
package org.springblade.modules.system.timing;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.cache.SysCache;
import org.springblade.common.constant.DictBizConstant;
import org.springblade.common.constant.TenantConstant;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tenant.BladeTenantProperties;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.modules.system.entity.Tenant;
import org.springblade.modules.system.entity.User;
import org.springblade.modules.system.ldap.entity.LdapUser;
import org.springblade.modules.system.service.impl.UserServiceImpl;
import org.springframework.ldap.control.PagedResultsDirContextProcessor;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.naming.directory.SearchControls;
import java.util.ArrayList;
import java.util.List;
import static org.springblade.common.constant.DictBizConstant.*;
import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
/**
* LDAP用户信息同步定时任务
*/
@Component
@AllArgsConstructor
@Slf4j
public class GotionUserTask {
private final UserServiceImpl userService;
private final LdapTemplate ldapTemplate;
/**
* 每日凌晨执行一次
*/
@Scheduled(cron = "0 0 0 * * ?")
public void userSyncTask() {
List<LdapUser> allLdapUsers = this.queryUsersPage();
// 所有用户唯一值
for (LdapUser ldapUser : allLdapUsers) {
if (!"null".equals(ldapUser.getAccount()) && ldapUser.getAccount() != null){
CacheUtil.clear(USER_CACHE);
// 将 LdapUser 转换为 User
User user = this.ldapUserToSystemUser(ldapUser);
if (StringUtil.isBlank(user.getTenantId())) {
user.setTenantId(BladeConstant.ADMIN_TENANT_ID);
}
Long userCount = userService.getBaseMapper().selectCount(Wrappers.<User>query().lambda().eq(User::getTenantId, user.getTenantId()).eq(User::getAccount, user.getAccount()));
if (userCount > 0L && Func.isEmpty(user.getId())) {
// 若当前用户已存在,则更新用户信息
User oldUser = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getAccount, user.getAccount()));
oldUser.setCode(ldapUser.getCode());
oldUser.setName(ldapUser.getName());
oldUser.setRealName(!"null".equals(ldapUser.getRealName()) ? ldapUser.getRealName() : null);
oldUser.setEmail(!"null".equals(ldapUser.getEmail()) ? ldapUser.getEmail() : null);
oldUser.setPhone(!"null".equals(ldapUser.getPhone()) ? ldapUser.getPhone() : null);
userService.updateUser(oldUser);
} else {
userService.submit(user);
}
}
}
}
/**
* 分页查询LDAP中的所有的用户息
*
* @return 结果
*/
public List<LdapUser> queryUsersPage() {
List<LdapUser> list = new ArrayList<>();
SearchControls searchControls = new SearchControls();
/*
* 0:OBJECT_SCOPE,搜索指定的命名对象。
* 1:ONE LEVEL_SCOPE,只搜索指定命名对象的一个级别,这是缺省值。
* 2:SUBTREE_SCOPE,搜索以指定命名对象为根结点的整棵树
*/
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
// 每次查询条数:默认1000条
PagedResultsDirContextProcessor processor = new PagedResultsDirContextProcessor(1000);
//返回的参数
// 映射对象
AttributesMapper<LdapUser> CN_ATTRIBUTES_MAPPER = attributes -> {
LdapUser ldapUser = new LdapUser();
ldapUser.setCode(String.valueOf(attributes.get("distinguishedName") != null ? attributes.get("distinguishedName").get() : null));
ldapUser.setAccount(String.valueOf(attributes.get("sAMAccountName") != null ? attributes.get("sAMAccountName").get() : null));
ldapUser.setName(String.valueOf(attributes.get("cn") != null ? attributes.get("cn").get() : null));
ldapUser.setRealName(String.valueOf(attributes.get("name") != null ? attributes.get("name").get() : null));
ldapUser.setEmail(String.valueOf(attributes.get("mail") != null ? attributes.get("mail").get() : null));
ldapUser.setPhone(String.valueOf(attributes.get("telephoneNumber") != null ? attributes.get("telephoneNumber").get() : null));
return ldapUser;
};
//查询条件
LdapQuery queryPerson = query().base(DictBizConstant.GOTION_CODE_PUBLIC_SIGNS).where("objectClass").is("person");
do {
List<LdapUser> search = ldapTemplate.search(queryPerson.base(),
queryPerson.filter().encode(),
searchControls,
CN_ATTRIBUTES_MAPPER,
processor);
list.addAll(search);
} while (processor.hasMore());
return list;
}
/**
将映射的LDAP用户对象 转换为 当前系统的用户对象
*/
public User ldapUserToSystemUser(LdapUser ldapUser) {
User user = new User();
user.setCode(!"null".equals(ldapUser.getCode()) ? ldapUser.getCode() : null);
user.setUserType(GOTION_DEF_USER_TYPE); // 默认用户类型
user.setAccount(ldapUser.getAccount()); // 登录名称(用户在当前系统的登录名,应为)
user.setPassword(GOTION_DEF_PWD); // 默认密码
user.setName(ldapUser.getName());
user.setRealName(!"null".equals(ldapUser.getRealName()) ? ldapUser.getRealName() : null);
user.setAvatar(null);
user.setEmail(!"null".equals(ldapUser.getEmail()) ? ldapUser.getEmail() : null);
user.setPhone(!"null".equals(ldapUser.getPhone()) ? ldapUser.getPhone() : null);
user.setSex(null);
user.setRoleId(GOTION_DEF_ROLE); // 默认权限
user.setDeptId(GOTION_DEF_DEPT); // 默认部门
user.setPostId(GOTION_DEF_POST); // 默认岗位
return user;
}
}
5、结果
LDAP中的用户信息:
MySQL中的用户信息:
Over!
加粗样式