基于Spring Boot的LDAP开发全教程

写在前面

协议概述

LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是一种用于访问和维护分布式目录服务的开放标准协议,是一种基于TCP/IP协议的客户端-服务器协议,用于访问和管理分布式目录服务,如企业内部的用户目录、组织结构和资源信息等。LDAP具有轻量级、高效性和可扩展性等特点,被广泛应用于AD域操作,身份验证、用户管理、电子邮件系统和网络存储等领域。

工作原理

连接建立:客户端通过TCP连接到LDAP服务器的默认端口389。
用户认证:客户端发送BIND请求进行身份认证。
目录搜索:客户端发送SEARCH请求查询目录信息。
数据操作:客户端发送ADD、DELETE、MODIFY等请求进行目录数据的增删改操作。
连接关闭:传输完成后,客户端发送UNBIND请求关闭连接。

协议结构

LDAP协议中的数据操作主要包括BIND、UNBIND、SEARCH、ADD、DELETE、MODIFY等请求

名词解释

o– organization(组织-公司)
ou – organization unit(组织单元-部门)
c - countryName(国家)
dc - domainComponent(域名)
sn – suer name(真实名称)
cn - common name(常用名称
版权声明:本文为CSDN博主「流子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://jiangguilong2000.blog.csdn.net/article/details/133893194

依赖库引入

spring-boot-starter-data-ldap是Spring Boot封装的对LDAP自动化配置的实现,它是基于spring-data-ldap来对LDAP服务端进行具体操作的。

implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-ldap', version: '2.7.5';

配置连接

# LDAP连接配置
spring:ldap:enable: trueurls: ldaps://10.10.18.181:636base: "DC=adgamioo,DC=com"username: p-account@gamioo.compassword: *********

注意:

  • ldap默认端口为389,ldaps默认端口为636 创建有密码的账号,重置密码操作必须使用ldaps协议;
  • 使用ldaps协议必须配置ssl证书,大部分解决方案是需要从ldap 服务器上导出证书,然后再通过Java的keytool 工具导入证书,比较繁琐,反正从服务器上导出证书那一步就很烦了。当然了也有办法绕过证书,下面,说一下如何代码配置ldap 跳过SSL。

配置信息读取:

@RefreshScope
@ConfigurationProperties(LdapProperties.PREFIX)
public class LdapProperties {public static final String PREFIX = "spring.ldap";private Boolean enable = true;private String urls;private String base;private String userName;/*** Secret key是你账户的密码*/private String password;
}

跳过证书:

import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;public class DummyTrustManager implements X509TrustManager {public void checkClientTrusted(X509Certificate[] cert, String authType) {}public void checkServerTrusted(X509Certificate[] cert, String authType) {}public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}
}
public class DummySSLSocketFactory extends SSLSocketFactory {private final static Logger logger = LoggerFactory.getLogger(DummySSLSocketFactory.class);private SSLSocketFactory factory;public DummySSLSocketFactory() {try {SSLContext sslcontext = SSLContext.getInstance("TLS");sslcontext.init(null, new TrustManager[]{new DummyTrustManager()}, new java.security.SecureRandom());factory = sslcontext.getSocketFactory();} catch (Exception ex) {logger.error(ex.getMessage(), ex);}}public static SocketFactory getDefault() {return new DummySSLSocketFactory();}@Overridepublic String[] getDefaultCipherSuites() {return factory.getDefaultCipherSuites();}@Overridepublic String[] getSupportedCipherSuites() {return factory.getSupportedCipherSuites();}@Overridepublic Socket createSocket(Socket socket, String string, int num, boolean bool) throws IOException {return factory.createSocket(socket, string, num, bool);}@Overridepublic Socket createSocket(String string, int num) throws IOException {return factory.createSocket(string, num);}@Overridepublic Socket createSocket(String string, int num, InetAddress netAdd, int i) throws IOException {return factory.createSocket(string, num, netAdd, i);}@Overridepublic Socket createSocket(InetAddress netAdd, int num) throws IOException {return factory.createSocket(netAdd, num);}@Overridepublic Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException {return factory.createSocket(netAdd1, num, netAdd2, i);}
}
@AutoConfiguration
@EnableConfigurationProperties(LdapProperties.class)
@ConditionalOnProperty(value = LdapProperties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
@EnableLdapRepositories(basePackages = "io.gamioo.core.ldap.dao")
public class LdapConfiguration {@Resourceprivate LdapProperties properties;//@Beanpublic ContextSource contextSource() {//   Security.setProperty("jdk.tls.disabledAlgorithms", "");System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");LdapContextSource source = new LdapContextSource();source.setUserDn(properties.getUserName());source.setPassword(properties.getPassword());source.setBase(properties.getBase());source.setUrl(properties.getUrls());Map<String, Object> config = new HashMap<>();config.put(Context.AUTHORITATIVE, "true");config.put(Context.SECURITY_PROTOCOL, "ssl");config.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");config.put(Context.SECURITY_AUTHENTICATION, "simple");//  解决乱码config.put("java.naming.ldap.attributes.binary", "objectGUID");config.put("java.naming.ldap.factory.socket", DummySSLSocketFactory.class.getName());source.setBaseEnvironmentProperties(config);return source;}@Beanpublic LdapTemplate ldapTemplate(ContextSource contextSource) {LdapTemplate template = new LdapTemplate(contextSource);template.setIgnorePartialResultException(true);return template;}
}

DAO层:

/*** UserRepository继承LdapRepository接口实现基于Ldap的增删改查操作*/public interface UserRepository extends LdapRepository<LdapUser> {LdapUser findByCommonName(String cn);
}

操作对象:

import org.springframework.data.domain.Persistable;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import org.springframework.ldap.odm.annotations.Transient;import javax.naming.Name;@Entry(base = "", objectClasses = {"person", "user", "top", "organizationalPerson"})
public final class LdapUser implements Persistable {@Idprivate Name id;@Transientprivate boolean isNew;@Attribute(name = "userPrincipalName")private String userPrincipalName;@Attribute(name = "userAccountControl")private String status;@Attribute(name = "distinguishedName")private String dn;@Attribute(name = "cn")private String commonName;@Attribute(name = "givenName")private String givenName;@Attribute(name = "unicodePwd", type = Attribute.Type.BINARY)private byte[] unicodePassword;@Attribute(name = "sAMAccountName")private String accountName;@Attribute(name = "displayName")private String displayName;}

常量类LdapConstant ,主要用于控制账号的禁用还是正常使用:

public interface LdapConstant {int ACCOUNT_DISABLE = 0x0001 << 1; // 账户已禁用int LOCKOUT = 0x0001 << 4; // 账户已锁定int PASSWD_NOTREQD = 0x0001 << 5; // 不需要密码int PASSWD_CANT_CHANGE = 0x0001 << 6; // 用户不能更改密码(只读,不能修改)int NORMAL_ACCOUNT = 0x0001 << 9; // 正常账户int DONT_EXPIRE_PASSWORD = 0x0001 << 16; // 密码永不过期int PASSWORD_EXPIRED = 0x0001 << 23; // 密码已过期
}

实现AD域用户创建,认证、查询用户、更新用户,重置密码,禁用用户等操作

@Service
@Transactional(rollbackFor = Exception.class)
public class LdapServiceImpl implements ILdapService {private final static Logger logger = LoggerFactory.getLogger(LdapServiceImpl.class);@Resourceprivate UserRepository repository;@Resourcepublic LdapTemplate ldapTemplate;/*** 禁用用户** @param userId 用户id*/@Overridepublic void disableUser(String userId) {logger.info("disable user:{}", userId);LdapUser user = this.findUserBy(userId);if (user != null) {user.setStatus(String.valueOf(LdapConstant.ACCOUNT_DISABLE));repository.save(user);}}/*** 激活用户** @param userId 用户id*/@Overridepublic void activeUser(String userId) {logger.info("active user:{}", userId);LdapUser user = this.findUserBy(userId);if (user != null) {user.setStatus(String.valueOf(LdapConstant.NORMAL_ACCOUNT));repository.save(user);}}/*** 查询所有用户信息** @return List<LdapUser>*/@Overridepublic List<LdapUser> findAll() {return repository.findAll();}/*** 根据userId查询用户信息** @param userId 用户id* @return User*/@Overridepublic LdapUser findUserBy(String userId) {LdapUser ret = repository.findByCommonName(userId);return ret;}/*** 删除用户** @param userId 用户id*/@Overridepublic void deleteUser(String userId) {logger.info("delete user:{}", userId);LdapUser user = this.findUserBy(userId);if (user != null) {repository.delete(user);}}/*** 创建用户(账号 + 密码)** @param userId   用户id* @param password 密码*/@Overridepublic void createUser(String userId, String password) {logger.info("create user:{},password:{}", userId, password);Name name = LdapNameBuilder.newInstance().add("CN", "Users").add("CN", userId).build();LdapUser user = new LdapUser();user.setCommonName(userId);user.setDisplayName(userId);user.setGivenName(userId);user.setNew(true);user.setAccountName(userId);user.setStatus(String.valueOf(LdapConstant.NORMAL_ACCOUNT));user.setUserPrincipalName(userId + "@adgamioo.com");user.setId(name);user.setUnicodePassword(this.encodePwd(password));repository.save(user);}/*** 修改用户** @param user user*/public void updateUser(LdapUser user) {logger.info("update user:{}", user.getAccountName());repository.save(user);}/*** 重置密码** @param userId      用户id* @param newPassword 新密码*/@Overridepublic void resetPwd(String userId, String newPassword) {logger.info("resetPwd user:{},{}", userId, newPassword);// 1. 查找AD用户LdapUser user = repository.findByCommonName(userId);ModificationItem[] mods = new ModificationItem[1];mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", encodePwd(newPassword)));ldapTemplate.modifyAttributes(user.getId(), mods);}/*** 密码加密** @param source 密文* @return 加密后密码*/private byte[] encodePwd(String source) {String quotedPassword = "\"" + source + "\""; // 注意:必须在密码前后加上双引号return quotedPassword.getBytes(StandardCharsets.UTF_16LE);}}

以上代码亲测有效!

常见异常

javax.naming.NameAlreadyBoundException: [LDAP: error code 68 - 00000524: UpdErr: DSID-031A11F8, problem 6005 (ENTRY_EXISTS), data 0
同名的实体已经存在

javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100245, problem 2001 (NO_OBJECT), data 0, best match of:
‘DC=adgamioo,DC=com’
一般是路径节点下没有找到对应实体,可能是base路径已经配置了,id中又去加了路径

javax.naming.CommunicationException: simple bind failed: adyorha.com:636
java.net.SocketException: Connection or outbound has closed
连接失败,比如ldaps服务没开启等

org.springframework.ldap.OperationNotSupportedException: [LDAP: error code 53 - 0000001F: SvcErr: DSID-031A126A, problem 5003 (WILL_NOT_PERFORM), data 0
比如在389端口下进行密码修改或者创建有密码的用户,又或是修改userAccountControl

Q&A

Q:为什么修改密码后,新老密码在一段时间内都有效?
A:经过查阅资料发现,在server 2008级别的AD下,旧密码生存期为5分钟,在server 2003级别的AD下,旧密码生存期为60分钟。
这个5分钟就是为了防止AD同步延时问题,防止DC数量比较多时,用户登录所在的站点内还没有成功的更新到密码的修改的情况。。这样,即使新密码没有生效,旧密码依然可用。有些网络效率不高的情况下,是会发生密码同步需要一定时间的情况的。鉴于这样的考虑,我们的旧密码,就有启用了一种生存时间的概念。
值得注意的是,这个缓存,在LDAP验证方式中存在,但却不存在于kerberos验证方式中。换句话说,也就是我们最常见的使用Ctrl-Alt-Del的交互式方式登录到桌面系统是不会存在旧密码可用的情况的。

参考链接

Spring LDAP Reference官方文档
ldap常见错误码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/108881.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go的函数选项模式

使用场景 在go的开发过程中&#xff0c;有的时候我们常常会碰到这样的场景&#xff1a;new一个结构体的时候参数不确定&#xff0c;但是我们又需要根据我们的需求来进行结构体的初始化赋值&#xff0c;那么碰到这样场景的时候&#xff0c;我们除了为不同的初始化方法写多个结构…

基于SSM框架的安全教育平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

微信小程序仿苹果负一屏由弱到强的高斯模糊

进入下面小程序可以体验效果&#xff0c;然后进入更多。查看模糊效果 一、创建小程序组件 二、代码 wxml: <view class"topBar-15"></view> <view class"topBar-14"></view> <view class"topBar-13"></view&…

Kotlin中布尔类型、字符类型、字符串类型和数组类型

在Kotlin中&#xff0c;布尔类型、字符类型、字符串类型和数组类型是常用的数据类型之一。下面我将对它们进行详细描述并提供示例代码。 布尔类型&#xff08;Boolean&#xff09;&#xff1a; 布尔类型表示逻辑值&#xff0c;只有两个可能的取值&#xff1a;true和false。在K…

JavaScript中的特殊数据类型和其具体用法

在实际开发中&#xff0c;还可以使用其他数据类型&#xff0c;如正则表达式&#xff08;RegExp&#xff09;、Map、Set等&#xff0c;以及自定义的复杂数据结构。 以下是一些常见的特殊数据类型和它们的具体用法&#xff1a; 1&#xff1a;正则表达式&#xff08;RegExp&…

node多版本管理器nvm

node多版本管理器nvm 1、为何要使用node版本管理器2、nvm安装步骤2-1、卸载系统中的node2-2、下载nvm2-3、安装 3、维护node版本3-1、安装指定版本node3-2、查看本机已安装的所有node版本3-3、切换本机node版本 1、为何要使用node版本管理器 在日常开发中&#xff0c;难免会遇…

基于react18+arco+zustand通用后台管理系统React18Admin

React-Arco-Admin轻量级后台管理系统解决方案 基于vite4构建react18后台项目ReactAdmin。使用了reactarco-designzustandbizcharts等技术架构非凡后台管理框架。支持 dark/light主题、i18n国际化、动态路由鉴权、3种经典布局、tabs路由标签 等功能。 技术框架 编辑器&#xff…

MPLS基础

1. MPLS原理与配置 MPLS基础 &#xff08;1&#xff09;MPLS概念 MPLS位于TCP/IP协议栈中的数据链路层和网络层之间&#xff0c;可以向所有网络层提供服务。 通过在数据链路层和网络层之间增加额外的MPLS头部&#xff0c;基于MPLS头部实现数据快速转发。 本课程仅介绍MPLS在…

Nginx的代理和负载均衡

一、nginx的代理方式 1.1 七层代理 七层代理&#xff1a;基于http协议&#xff0c;对请求的内容进行处理&#xff0c;然后转发到后端服务器 七层代理是客户端请求代理服务器&#xff0c;由代理服务器转发客户端的http请求&#xff0c;转发到内部的服务器进行处理(服务器可以是…

【2023研电赛】基于三维视觉感知的可重构智能表面通信方案

该作品参与极术社区组织的研电赛作品征集活动&#xff0c;欢迎同学们投稿&#xff0c;获取作品传播推广&#xff0c;并有丰富礼品哦~ 基于三维视觉感知的可重构智能表面通信方案 参赛单位&#xff1a;华北水利水电大学 参赛队伍&#xff1a;智能队 指导老师&#xff1a;邵霞 参…

[SQL] union all

UNION ALL 是一个用于合并多个查询结果集的操作符。它将多个 SELECT 查询的结果合并成一个结果集&#xff0c;并且保留所有的行&#xff0c;包括重复的行。 具体语法如下&#xff1a; SELECT column1, column2, ... FROM table1 UNION ALL SELECT column1, column2, ... FROM…

QT_day1

#include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//窗口相关设置this->setWindowTitle("登录窗口");this->setWindowIcon(QIcon("C:\\Users\\EDY\\Desktop\\pictrue\\qq.png"));this->setWindowFlag(Qt::…

自动化的采集链接和自动推送必应的在线工具

搜索LMCJL在线工具 进入后点击站长工具类型&#xff0c;选择必应自动推送 进去后&#xff0c;添加域名&#xff0c;点击数据管理&#xff0c;输入必应的token 然后开启推送&#xff0c;就可以实现&#xff0c;自动化采集链接&#xff0c;自动推送给必应。 必应的站长后台官网…

Java包装类

在Java中不能自己定义基本数据类型对象&#xff0c;为了将基本数据类型视为对象进行处理&#xff0c;并能连接相关方法&#xff0c;Java为每个基本数据类型都提供了【包装类】如int型数值的包装类【Integer】,boolean型数值的包装类【Boolean】,这样就可以把这些基本数据类型转…

搭建 Hadoop 生态集群大数据监控告警平台

目录 一、部署 prometheus 环境 1.1 下载安装包 1.2 解压安装 1.3 修改配置文件 1.3.1 hadoop-env.sh 1.3.2 prometheus_config.yml 1.3.3 zkServer.sh 1.3.4 prometheus_zookeeper.yaml 1.3.5 alertmanager.yml 1.3.6 prometheus.yml 1.3.7 config.yml 1.3.8 t…

创新与重塑,佛塑科技打造集团型 CRM 建设标杆

“十四五”时期是我国全面建成小康社会、实现第一个百年奋斗目标之后&#xff0c;乘势而上开启全面建设社会主义现代化国家新征程、向第二个百年奋斗目标进军的第一个五年。 在政府有序推进“十四五”规划的进程中&#xff0c;佛山佛塑科技集团股份有限公司&#xff08;证券简…

论文阅读:Segment Any Point Cloud Sequences by Distilling Vision Foundation Models

目录 概要 Motivation 整体架构流程 技术细节 小结 论文地址&#xff1a;[2306.09347] Segment Any Point Cloud Sequences by Distilling Vision Foundation Models (arxiv.org) 代码地址&#xff1a;GitHub - youquanl/Segment-Any-Point-Cloud: [NeurIPS23 Spotlight]…

思科拟推出PuzzleFS驱动,采用Rust语言开发

据了解&#xff0c;PuzzleFS宣称是“下一代 Linux 容器文件系统”&#xff0c;并使用Rust语言编写&#xff0c;具有“快速镜像构建”、“直接挂载支持”、“内存安全保证”等功能mroeoyw。 Multiable万达宝制造ERP(www.multiable.com.cn/solutions_zz)支持自定义栏位,并智能制…

车载开发前景广阔,分析市场变化赢未来

车载开发行业在未来具有广阔的前景&#xff0c;主要受益于汽车科技的快速发展和智能出行概念的普及。随着科技的不断进步&#xff0c;车载开发行业将继续受益于创新和需求的推动。车载行业的分布未来也是非常之多&#xff0c;分析现在的车载智能发展&#xff0c;可以得出以下车…

本地安装telepresence,访问K8S集群 Mac(m1) 非管理員

kubeconfig 一&#xff0e;安装telepresence 1.安装 Telepresence Quickstart | Telepresence &#xff08;1&#xff09;brew install datawire/blackbird/telepresence 2.配置 目录kubectl 将使用默认的 kubeconfig 文件&#xff1a;$HOME/.kube/config 创建文件夹&…