springBoot集成AD域
简介
Active Directory(AD)是由微软开发的用于管理网络资源和用户身份的目录服务。它是一种目录服务,用于存储组织内的网络资源(如计算机、打印机、用户等)的信息,并提供对这些资源的集中化管理。AD域的作用主要包括以下几个方面:
- 身份验证和授权:AD域用于存储和管理用户账号信息,包括用户名、密码、所属组织单位等。通过AD域,用户可以使用统一的账号来访问不同的网络资源,并通过集中管理的方式来进行身份验证和授权。
- 集中化管理:AD域提供了集中化的管理方式,管理员可以通过AD域来管理用户账号、计算机、打印机、文件共享等各种网络资源,而无需在每台计算机上单独进行管理。
- 安全策略和权限控制:AD域可以定义安全策略和权限控制规则,例如访问控制列表(ACL)、组策略、密码策略等,从而确保网络资源的安全性和合规性。
- 资源共享和查找:AD域提供了资源共享的功能,用户可以通过AD域来查找和访问网络中的共享文件夹、打印机等资源。
- 单点登录:通过AD域,用户可以实现单点登录,即使用一组凭证就可以访问整个网络中的资源,无需单独登录每个资源
springBoot集成Ldap
1.导入Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
2.yml中进行配置
# ldap连接信息
ldap:
url: ldap://192.168.140.61:389
base: OU=test,DC=account,DC=com
userDn: "[email protected]"
userPwd: test
referral: follow
domainName: "%[email protected]"
AD域中参数对应:
OU:代表AD域中的组织单位
DC:代表指定域的组成部分,其中第一个DC是子域,第二个DC是顶级域
一般情况下 OU=<组织单位>,DC=<子集域>,DC=<顶级域(com)>
顶级域都是com
referral介绍:
在
LdapContextSource
中用于设置 referral 等级的属性是referral
,它有以下几个可能的取值:
- follow:表示在遇到 referral 时,会自动跟随 referral 并完成操作。这意味着当请求需要跨域操作时,会自动向 referral 所指示的位置发出新的请求,并重复操作直到最终目标。
- ignore:表示在遇到 referral 时,会忽略 referral 并且不进行任何跟随操作。这个选项适用于希望明确控制每个 LDAP 操作的目标位置的情况。
- throw:表示在遇到 referral 时,会抛出异常。这意味着当遇到 referral 时,会立即终止操作并抛出异常,需要由客户端代码来处理。
3.编写ldap配置类
@Configuration
public class LdapConfiguration {
@Value("${ldap.url}")
private String ldapUrl;
@Value("${ldap.base}")
private String ldapBase;
@Value("${ldap.userDn}")
private String ldapUserDn;
@Value("${ldap.userPwd}")
private String ldapUserPwd;
@Value("${ldap.referral}")
private String ldapReferral;
private final String AD_PWD_KEY = "LDAP_USER_PASSWORD";
private final SystemConfigService service;
private final static Logger log = LoggerFactory.getLogger(LdapConfiguration.class);
public LdapConfiguration(SystemConfigService service) {
this.service = service;
}
@Bean
@ConfigurationProperties("spring.ldap")
public LdapProperties ldapProperties() {
return new LdapProperties();
}
@Bean
public LdapContextSource ldapContextSource() {
SSLLdapContextSource source = new SSLLdapContextSource();
// LdapContextSource source = new LdapContextSource();
SystemConfig value = service.getOne(new LambdaQueryWrapper<SystemConfig>().eq(SystemConfig::getCode, AD_PWD_KEY));
if (Objects.isNull(value)) {
value = new SystemConfig();
value.setValue(ldapUserPwd);
}
source.setUrl(ldapUrl);
source.setUserDn(ldapUserDn);
source.setPassword(value.getValue());
source.setReferral(ldapReferral);
return source;
}
@Bean
public LdapTemplate ldapTemplate() {
LdapTemplate ldapTemplate = new LdapTemplate(ldapContextSource());
ldapTemplate.setIgnoreNameNotFoundException(true);
try {
ldapTemplate.afterPropertiesSet();
} catch (Exception e) {
log.error(e.getMessage());
}
return ldapTemplate;
}
public void changeLdapPassword(String newPassword) {
ldapUserPwd = newPassword;
// 动态更新密码
ldapContextSource().setPassword(newPassword);
}
}
public class CustomSSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory socketFactory;
public CustomSSLSocketFactory() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[]{new DummyTrustmanager()}, new SecureRandom());
socketFactory = ctx.getSocketFactory();
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
public static SocketFactory getDefault() {
return new CustomSSLSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return socketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return socketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket socket, String string, int num, boolean bool) throws IOException {
return socketFactory.createSocket(socket, string, num, bool);
}
@Override
public Socket createSocket(String string, int num) throws IOException, UnknownHostException {
return socketFactory.createSocket(string, num);
}
@Override
public Socket createSocket(String string, int num, InetAddress netAdd, int i) throws IOException, UnknownHostException {
return socketFactory.createSocket(string, num, netAdd, i);
}
@Override
public Socket createSocket(InetAddress netAdd, int num) throws IOException {
return socketFactory.createSocket(netAdd, num);
}
@Override
public Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException {
return socketFactory.createSocket(netAdd1, num, netAdd2, i);
}
/**
* 绕过证书校验
*/
public static class DummyTrustmanager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] cert, String string) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] cert, String string) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}
它的作用是绕过证书校验,允许在建立 SSL 连接时不对服务器端的证书进行校验。通常情况下,SSL 连接需要验证服务器端的证书以确保连接的安全性,但在某些特殊情况下,可能希望绕过证书校验。
4.Java中实体与AD域的对应关系
@Entry(objectClasses = {"user"})
public class StaffByAd {
/**
* 标识id
*/
@Id
private Name id;
/**
* 用户名
*/
@Attribute(name = "userPrincipalName")
private String cn;
/**
* 邮箱
*/
@Attribute(name = "mail")
private String mail;
/**
* 手机号
*/
@Attribute(name = "telephoneNumber")
private String phoneNumber;
/**
* 密码
*/
@Attribute(name = "unicodePwd")
private String password;
/**
* 状态(1启用,其他禁用)
*/
@Attribute(name = "userAccountControl")
private Integer state;
/**
* 姓名
*/
@Attribute(name = "displayName")
private String surname;
@Attribute(name = "sn")
private String lastName;
@Attribute(name = "givenName")
private String firstName;
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
@Override
public String toString() {
return "Staff{" +
"id=" + id +
", cn='" + cn + '\'' +
", mail='" + mail + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", password='" + password + '\'' +
", state=" + state +
'}';
}
public String getCn() {
return cn;
}
public void setCn(String cn) {
this.cn = cn;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public Name getId() {
return id;
}
public void setId(Name id) {
this.id = id;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
@Attribute注解是将实体属性与AD域中的属性进行一一对应
userPrincipalName:代表用户的编号
mail:邮箱
unicodePwd:密码
displayName:显示名称(姓+名组合)
sn:姓
givenName:名
userAccountControl:表示用户的禁用状态,一般情况下AD域中512、544、66048、262656均表示启用状态,其余状态为禁用状态。
5.编写工具类
@Component
public class LdapUtil {
private final LdapTemplate ldapTemplate;
private static Logger log = LoggerFactory.getLogger(LdapUtil.class);
@Value("${ldap.domainName}")
private String ldapDomainName;
@Value("${ldap.base}")
private String defaultBase;
@Autowired
public LdapUtil(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
/**
* 获取所有用户信息
*
* @param base: AD域的根节点
* @return List<Staff>
*/
public List<StaffByAd> getUserByBase(String base) {
//获取用户信息
LdapQuery ldapQuery = LdapQueryBuilder
.query()
.base(base)
.searchScope(SearchScope.SUBTREE)
.filter("(objectClass=*)");
// 获取当前base下的所有人员
List<StaffByAd> staffByAds = ldapTemplate.find(ldapQuery, StaffByAd.class);
staffByAds.forEach(staffByAd -> {
String cn = staffByAd.getCn();
if (!cn.contains("@")) {
throw new BusinessException("数据错误,请检查数据的完整性!");
}
if (StringUtils.isNotBlank(cn)) {
staffByAd.setCn(cn.substring(0, cn.indexOf("@")));
}
});
return staffByAds;
}
/**
* 添加用户
*
* @param person: 用户对象
* @param base: AD域的根节点
*/
public void addUser(StaffByAd person, String base) {
String bind = buildUserDn(person, base);
//添加用户
ldapTemplate.bind(bind, buildContext(person), null);
log.info("添加人员数据成功,添加的数据为{}", person);
}
/**
* 删除用户
*
* @param username: 用户名
* @param base: AD域的根节点
*/
public void deleteUser(String username, String base) {
String bind = "CN=" + username + "," + base;
ldapTemplate.unbind(bind);
log.info("添加人员数据成功,添加的数据为{}", username);
}
/**
* 绑定用户所在节点
*
* @param person: person对象
* @param base:AD域的根节点
* @return String
*/
private String buildUserDn(StaffByAd person, String base) {
// 根据需要适配目录结构来构建用户的DN
// 例如:"cn=username,ou=users,dc=example,dc=com"
return "CN=" + person.getCn() + "," + base;
}
/**
* 设置用户属性
*
* @param person: 用户对象
* @return DirContextAdapter
*/
private DirContextAdapter buildContext(StaffByAd person) {
DirContextAdapter context = new DirContextAdapter();
// 设置用户属性
context.setAttributeValues("objectclass", new String[]{"user"});
context.setAttributeValue("unicodePwd", person.getPassword());
context.setAttributeValue("mail", person.getMail());
context.setAttributeValue("cn", new String[]{person.getSurname()});
context.setAttributeValue("telephoneNumber", person.getPhoneNumber());
context.setAttributeValue("sAMAccountName", person.getCn());
context.setAttributeValue("sn", person.getLastName());
context.setAttributeValue("givenName", person.getFirstName());
return context;
}
/**
* 进行账号过期时间的设置
*
* @param expireDate: 过期时间,格式为(20230823021045Z)
* @param base: AD域的根节点
* @param username: 用户名
*/
public void changeAccountExpire(String expireDate, String base, String username) {
String userDn = "CN=" + username + "," + base;
// 创建 ModificationItem 对象用于设置有效期属性
ModificationItem[] modificationItems = new ModificationItem[]{
new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("accountExpires", expireDate))
};
ldapTemplate.modifyAttributes(userDn, modificationItems);
}
/**
* 获取组织架构信息
*
* @param base: AD域的根节点
* @date 2023/8/18 13:59
*/
public List<Group> getOrgByBase(String base) {
// 获取组织架构信息
LdapQuery ldapQuery = LdapQueryBuilder
.query()
.base(base)
.searchScope(SearchScope.SUBTREE)
.filter("(objectClass=organizationalUnit)");
List<Group> groups = ldapTemplate.find(ldapQuery, Group.class);
ArrayList<String> strings = new ArrayList<>();
// 遍历获取父级
for (int i = 0; i < groups.size(); i++) {
Group group = groups.get(i);
String[] split = group.getId().toString().split(",");
for (String s : split) {
String[] ous = s.split("OU=");
// 如果长度大于1,说明split成功,就是需要的OU
if (ous.length > 1) {
strings.add(ous[1]);
}
}
// size>2说明有父级,并将父级存储到parent属性里面
if (strings.size() >= 2) {
String parentName = strings.get(1);
group.setParent(parentName);
}
System.out.println(strings);
strings.clear();
}
log.info("获取组织架构成功,{}", groups);
return groups;
}
/**
* 身份验证
*
* @param userName:用户名
* @param password:密码
* @return Boolean
*/
public Boolean authenticate(String userName, String password) {
// 进行参数拼接
String userDomainName = String.format(ldapDomainName, userName);
DirContext ctx = null;
try {
// 校验账号密码是否正确
ctx = ldapTemplate.getContextSource().getContext(userDomainName, password);
return true;
} catch (Exception e) {
log.error("认证失败,原因{}", e.getMessage());
}
// 返回
return false;
}
/**
* 根据条件查询用户
*
* @param person: 用户对象
* @param base: AD域的根节点
* @date 2023/8/21 15:58
*/
public List<StaffByAd> getUser(StaffByAd person, String base) {
// 构建查询的builder
ContainerCriteria containerCriteria = LdapQueryBuilder
.query()
.base(base)
.searchScope(SearchScope.SUBTREE)
.where("objectclass")
.is("user");
String cn = person.getCn();
// 判断获取到的用户名是否为空
if (!StringUtils.isBlank(cn)) {
// 封装查询条件
String userDomainName = String.format(ldapDomainName, person.getCn());
containerCriteria.and("userPrincipalName").is(userDomainName);
// 查询结果
List<StaffByAd> staffByAds = ldapTemplate.find(containerCriteria, StaffByAd.class);
log.info("获取用户数据成功,{}", staffByAds);
staffByAds.forEach(staffByAd -> {
String cn2 = staffByAd.getCn();
if (StringUtils.isNotBlank(cn2)) {
staffByAd.setCn(cn2.substring(0, cn2.indexOf("@")));
}
});
return staffByAds;
}
// 如果传递的用户名为空则返回空
log.info("无该用户信息{}", person);
return null;
}
/**
* 根据条件查询用户
*
* @param person
* @return List<StaffByAd>
*/
public List<StaffByAd> getUser(StaffByAd person) {
// 构建查询的builder
ContainerCriteria containerCriteria = LdapQueryBuilder
.query()
.base(defaultBase)
.searchScope(SearchScope.SUBTREE)
.where("objectclass")
.is("user");
String cn = person.getCn();
// 判断获取到的用户名是否为空
if (!StringUtils.isBlank(cn)) {
// 封装查询条件
String userDomainName = String.format(ldapDomainName, person.getCn());
containerCriteria.and("userPrincipalName").is(userDomainName);
// 查询结果
List<StaffByAd> staffByAds = ldapTemplate.find(containerCriteria, StaffByAd.class);
staffByAds.forEach(staffByAd -> {
String cn2 = staffByAd.getCn();
if (StringUtils.isNotBlank(cn2)) {
staffByAd.setCn(cn2.substring(0, cn2.indexOf("@")));
}
});
return staffByAds;
}
// 如果传递的用户名为空则返回空
log.info("无该用户信息{}", person);
return null;
}
/**
* 修改用户信息
*
* @param person: 用户对象
* @param base: AD域的根节点
*/
public void updateUser(StaffByAd person, String base) {
//根据条件获取用户
List<StaffByAd> users = getUser(person, base);
//判断list是否为空
if (users.size() == 0) {
return;
}
// 进行遍历修改
for (StaffByAd user : users) {
ldapTemplate.modifyAttributes(user.getId(), getModificationItems(person));
log.info("更改用户信息成功,{}", person);
}
}
/**
* 封装修改的用户属性
*
* @param person: 用户对象
* @return ModificationItem[]
* @author weilingfeng
* @date 2023/8/23 9:52
*/
private ModificationItem[] getModificationItems(StaffByAd person) {
// 创建集合用来存储属性
List<ModificationItem> modificationItems = new ArrayList<>();
// 判断person中的邮箱是否为空
if (StringUtils.isNotBlank(person.getMail())) {
// 将属性添加至集合中
modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("mail", person.getMail())));
}
// 判断person中的手机号是否为空
if (StringUtils.isNotBlank(person.getPhoneNumber())) {
// 将属性添加至集合中
modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("telephoneNumber", person.getPhoneNumber())));
}
// 判断密码是否为空
if (StringUtils.isNotBlank(person.getPassword())) {
// 将属性添加至集合中
byte[] bytes =("\""+ person.getPassword()+"\"").getBytes(StandardCharsets.UTF_16LE);
modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("unicodePwd", bytes)));
}
// 判断姓是否为空
if (StringUtils.isNotBlank(person.getSurname())) {
// 将属性添加至集合中
modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("sn", person.getLastName())));
}
// 判断名是否为空
if (StringUtils.isNotBlank(person.getSurname())) {
// 将属性添加至集合中
modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("givenName", person.getFirstName())));
}
// 判断姓名是否为空
if (StringUtils.isNotBlank(person.getSurname())) {
// 将属性添加至集合中
modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("displayName", person.getSurname())));
}
return modificationItems.toArray(new ModificationItem[0]);
}
/**
* 解除锁定状态
*
* @param username:用户名
* @param base:AD域的根节点
*/
public void unlockUser(String username, String base) {
String userDn = "CN=" + username + "," + base;
ModificationItem[] modificationItems = new ModificationItem[]{
new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("lockoutTime"))
};
ldapTemplate.modifyAttributes(userDn, modificationItems);
log.info("解除绑定成功,{}", username);
}
/**
* 禁用指定账户。
*
* @param uid 用户 uid
* @return 禁用成功返回 true,否则返回 false
*/
public boolean disableUser(String uid, String base) {
try {
// 根据 uid 查询用户 DN
Name userDn = getUserDn(uid, base);
// 使用修改项更新 userAccountControl 属性值
ldapTemplate.modifyAttributes(userDn,
new ModificationItem[]{
new ModificationItem(
DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("userAccountControl", "514")
)
}
);
// 禁用成功
return true;
} catch (Exception e) {
log.error("禁用失败,原因{}", e.getMessage());
// 禁用失败
return false;
}
}
/**
* 解禁指定账户。
*
* @param uid 用户 uid
* @return 解禁成功返回 true,否则返回 false
*/
public boolean enableUser(String uid, String base) {
try {
// 根据 uid 查询用户 DN
Name userDn = getUserDn(uid, base);
// 使用修改项更新 userAccountControl 属性值
ldapTemplate.modifyAttributes(userDn,
new ModificationItem[]{
new ModificationItem(
DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("userAccountControl", "512")
)
}
);
// 解禁成功
return true;
} catch (Exception e) {
log.error("解禁成功{}", e.getMessage());
// 解禁失败
return false;
}
}
/**
* 根据 uid 查询用户 DN。
*
* @param uid 用户 uid
* @return 用户 DN
*/
private Name getUserDn(String uid, String base) {
StaffByAd person = new StaffByAd();
person.setCn(uid);
List<StaffByAd> user = getUser(person, base);
if (user.size() != 0) {
return user.get(0).getId();
}
throw new RuntimeException("账号不存在");
}
}
标签:集成,return,String,person,base,new,AD,public,springBoot From: https://blog.csdn.net/m0_56512023/article/details/139472002注意事项
AD域操作时,如果需要添加用户的密码时需要保证使用的账号是超级管理员以及AD域需要导入SSL证书。二者条件不满足的话就会导致更新时报错.