【LDAP】Spring项目同步LDAP域用户信息总览(含ldapTemplate.search仅查询1000条数据的解决方案)

3 篇文章 0 订阅
1 篇文章 0 订阅

【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。它们之间的主要区别在于目标服务器和所执行的操作。

  1. LDAP端口 389:
    • 用途: 389端口是LDAP的标准端口,主要用于非安全的LDAP通信。
    • 目标服务器: 通常用于与单个LDAP服务器建立连接,执行标准的LDAP操作。
    • 操作类型: 适用于基本的LDAP查询、添加、修改、删除等操作。
    • 加密: 数据在传输过程中不加密,可能存在安全风险。
  2. 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中的用户信息:

image-20231201112455054

MySQL中的用户信息:

image-20231201112710818

Over!

加粗样式

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值