1、AD域操作类
@Slf4j
public class ADOperator {
private final DomainConfigModel adConfig;
private static final int ADS_UF_ACCOUNTDISABLE = 0x00000002;//账号禁用位
private static final int ADS_UF_NORMAL_ACCOUNT = 0x0200;//普通账号
/**
* 构造函数
*
* @param adConfig AD域配置信息
*/
public ADOperator(DomainConfigModel adConfig) {
this.adConfig = adConfig;
}
/**
* 构造函数
*
* @param path AD域路径
* @param adminUser AD域用户名
* @param adminPwd AD域密码
* @param suffixPath AD域根路径
*/
public ADOperator(String path, String adminUser, String adminPwd, String suffixPath) {
this.adConfig = new DomainConfigModel(adminUser, adminPwd, path, suffixPath);
}
/**
* 添加用户
*
* @param userInfo 域用户信息
* @return
*/
public String addUser(DomainUser userInfo) {
StringBuilder message = new StringBuilder();
if (!createOrg(userInfo.getDepartment()))
return message.append("组织创建失败").toString();
message.append("组织创建成功;");
if (!createGroup(userInfo.getDepartment(), userInfo.getDepartment()))
return message.append("用户组创建失败").toString();
message.append("用户组创建成功;");
try {
DirContext ldapContext = getLdapContext();
if (!existsUser(userInfo.getAdUserName())) {
BasicAttributes attrs = new BasicAttributes(true);
attrs.put("objectClass", "user");
attrs.put("displayName", userInfo.getName());// 显示名称
attrs.put("sAMAccountName", userInfo.getAdUserName());
attrs.put("mail", userInfo.getEmail());
attrs.put("telephoneNumber", userInfo.getMobile());
ldapContext.createSubcontext("CN=" + userInfo.getUserName() + "," + getOrgADPath(userInfo.getDepartment()), attrs);
}
message.append("用户账号创建成功;");
if (disabledOrOnUser(userInfo.getAdUserName(), false))
message.append("用户账号启用成功;");
else
message.append("用户账号启用失败;");
SearchResult result = findObject(adConfig.getSuffixPath(), "person", userInfo.getAdUserName());
assert result != null;
//设置密码
if (StringUtils.isNotEmpty(userInfo.getPwd())) {
ModificationItem[] mods1 = new ModificationItem[1];
mods1[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("unicodePwd", createUnicodePassword(userInfo.getPwd())));
ldapContext.modifyAttributes(result.getName() + "," + adConfig.getSuffixPath(), mods1);
}
message.append("用户账号密码设置成功;");
//添加用户至用户组
ModificationItem[] mods2 = new ModificationItem[1];
mods2[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", result.getName() + "," + adConfig.getSuffixPath()));
ldapContext.modifyAttributes("CN=" + userInfo.getDepartment() + "," + getOrgADPath(userInfo.getDepartment()), mods2);
message.append("添加用户至组成功;正常");
} catch (NamingException e) {
log.error("创建用户过程中出错,用户信息为{},错误为{}", JSON.toJSONString(userInfo), e.getMessage());
message.append("失败");
}
return message.toString();
}
/** 更新AD用户信息(只能更新组织、手机号、邮箱)
* @param userInfo 需要更新的用户信息
* @return
*/
public boolean updateUser(DomainUser userInfo) {
if (!createOrg(userInfo.getDepartment()))
throw new SyncDataException("创建组织失败");
if (!createGroup(userInfo.getDepartment(), userInfo.getDepartment()))
throw new SyncDataException("创建用户组失败");
SearchResult result = findObject(adConfig.getSuffixPath(), "person", userInfo.getAdUserName());
assert result != null;
DirContext ldapContext = null;
try {
ldapContext = getLdapContext();
ModificationItem[] mods = new ModificationItem[2];
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("telephoneNumber", userInfo.getMobile()));
mods[1] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("mail", userInfo.getEmail()));
ldapContext.modifyAttributes(result.getName() + "," + adConfig.getSuffixPath(), mods);
//添加用户至用户组
ModificationItem[] mods1 = new ModificationItem[1];
mods1[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", result.getName() + "," + adConfig.getSuffixPath()));
ldapContext.modifyAttributes("CN=" + userInfo.getDepartment() + "," + getOrgADPath(userInfo.getDepartment()), mods1);
ldapContext.rename(result.getName() + "," + adConfig.getSuffixPath(), "CN=" + userInfo.getUserName() + "," + getOrgADPath(userInfo.getDepartment()));
return true;
} catch (NamingException e) {
log.error("更新用户过程中出错,用户信息为{},错误为{}", JSON.toJSONString(userInfo), e.getMessage());
return false;
}
}
/**
* 禁用/启用账号
*
* @param account 用户ad账号
* @param disabled 是否禁用
* @return
*/
public boolean disabledOrOnUser(String account, boolean disabled) {
if (!existsUser(account))
return false;
SearchResult result = findObject(adConfig.getSuffixPath(), "person", account);//根据账号查找到用户信息
try {
assert result != null;
Attributes attr = result.getAttributes();
Attribute controlAttr = attr.get("userAccountControl");//获取“账号选项”属性
ModificationItem[] mods = new ModificationItem[1];
if (null != controlAttr) {//若存在该属性,则替换,否则新增
int newControlVal;
if (disabled)//若禁用账号,则设置禁用标志位,否则取消禁用标志位
newControlVal = Integer.parseInt(controlAttr.get().toString()) | ADS_UF_ACCOUNTDISABLE;
else
newControlVal = Integer.parseInt(controlAttr.get().toString()) & ~ADS_UF_ACCOUNTDISABLE;
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl", Integer.toString(newControlVal)));
} else {
if (disabled)//若禁用账号,则设置禁用标志位,否则不操作
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("userAccountControl", Integer.toString(ADS_UF_ACCOUNTDISABLE)));
}
getLdapContext().modifyAttributes(result.getName() + "," + adConfig.getSuffixPath(), mods);
return true;
} catch (NamingException e) {
log.error("禁用/启用用户过程中出错,用户账号为{},错误为{}", account, e.getMessage());
return false;
}
}
/**
* 删除用户
*
* @param account 用户ad账号
* @return
*/
public boolean deleteUser(String account) {
if (!existsUser(account))
return true;
SearchResult result = findObject(adConfig.getSuffixPath(), "person", account);
try {
assert result != null;
getLdapContext().destroySubcontext(result.getName());
return true;
} catch (NamingException e) {
log.error("删除用户过程中出错,用户账号为{},错误为{}", account, e.getMessage());
return false;
}
}
/**
* 判断ad账号是否在AD域内
*
* @param account AD账号
* @return
*/
public boolean existsUser(String account) {
SearchResult result = findObject(adConfig.getSuffixPath(), "person", account);
return null != result;
}
/**
* 创建组织
*
* @param orgFullPath 组织全路径(父子路径间以-分隔)
* @return
*/
public boolean createOrg(String orgFullPath) {
String parentOrgPath = "";
String org = "";
if (orgFullPath.indexOf('-') > -1) {
int lastIndexOf = orgFullPath.lastIndexOf('-');
parentOrgPath = orgFullPath.substring(0, lastIndexOf);
org = orgFullPath.substring(lastIndexOf + 1);
} else
org = orgFullPath;
return createOrg(parentOrgPath, org);
}
/**
* 创建组织
*
* @param parentOrgPath 父组织全路径(父子路径间以-分隔),为空则表示父组织为根路径
* @param org 当前组织
* @return
*/
public boolean createOrg(String parentOrgPath, String org) {
if (existsOrg(parentOrgPath, org))
return true;
if (StringUtils.isNotEmpty(parentOrgPath)) {
String parent_parentOrgPath = "";
String parentOrg = "";
if (parentOrgPath.indexOf('-') > -1) {
int lastIndexOf = parentOrgPath.lastIndexOf('-');
parent_parentOrgPath = parentOrgPath.substring(0, lastIndexOf);
parentOrg = parentOrgPath.substring(lastIndexOf + 1);
} else
parentOrg = parentOrgPath;
if (existsOrg(parent_parentOrgPath, parentOrg) || createOrg(parent_parentOrgPath, parentOrg)) {
try {
DirContext ldapContext = getLdapContext();
Attributes attr = new BasicAttributes(true);
attr.put("objectClass", "organizationalUnit");
ldapContext.createSubcontext("OU=" + org + "," + getOrgADPath(parentOrgPath), attr);
return true;
} catch (NamingException e) {
log.error("创建组织过程中出错,父组织信息为{},当前组织信息为{},错误信息为{}", parentOrgPath, org, e.getMessage());
return false;
}
} else
return false;
} else {
if (existsOrg("", parentOrgPath))
return true;
else {
return createOrg("", parentOrgPath);
}
}
}
/**
* 判断父组织中是否存在该组织
*
* @param parentOrg 父组织全路径(父子路径间以-分隔),为空则表示父组织为根路径
* @param org 当前组织
* @return
*/
public boolean existsOrg(String parentOrg, String org) {
SearchResult result = findObject(getOrgADPath(parentOrg), "org", org);
return null != result;
}
/**
* 创建用户组
*
* @param orgPath 用户组所在组织全路径
* @param groupName 组名
* @return
*/
public boolean createGroup(String orgPath, String groupName) {
if (existsGroup(orgPath, groupName))
return true;
try {
DirContext ldapContext = getLdapContext();
Attributes attr = new BasicAttributes(true);
attr.put("objectClass", "group");
ldapContext.createSubcontext("CN=" + groupName + "," + getOrgADPath(orgPath), attr);
return true;
} catch (NamingException e) {
log.error("创建用户组过程中出错,组织路径为{},用户组名为{},错误为{}", orgPath, groupName, e.getMessage());
return false;
}
}
/**
* 判断组织中是否存在该组
*
* @param orgPath 组织全路径(父子路径间以-分隔),为空则表示组织路径为根路径
* @param group 组名
* @return
*/
public boolean existsGroup(String orgPath, String group) {
SearchResult result = findObject(getOrgADPath(orgPath), "group", group);
return null != result;
}
/**
* 密码加密
*
* @param password 密码
* @return
*/
private byte[] createUnicodePassword(String password) {
String quotedPassword = "\"" + password + "\"";
return quotedPassword.getBytes(StandardCharsets.UTF_16LE);
}
/**
* 根据组织路径生成AD组织路径
*
* @param orgPath 组织全路径(父子路径间以-分隔),为空则表示组织路径为根路径
* @return
*/
private String getOrgADPath(String orgPath) {
StringBuilder sb = new StringBuilder();
if (StringUtils.isNotEmpty(orgPath)) {
if (orgPath.indexOf('-') < 0) {
sb.append("OU=").append(orgPath).append(",");
} else {
String[] split = orgPath.split("-");
for (int i = split.length - 1; i >= 0; i--) {
if (StringUtils.isNotEmpty(split[i]))
sb.append("OU=").append(split[i]).append(",");
}
}
}
sb.append(adConfig.getSuffixPath());
return sb.toString();
}
/**
* 根据分类和用户名查找AD域下的项
*
* @param searchBase 查找节点
* @param category 分类(person、group、org)
* @param name 用户名
* @return
*/
private SearchResult findObject(String searchBase, String category, String name) {
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String filter = "";
if (category.equals("person"))
filter = String.format("(&(objectCategory=%s)(sAMAccountName=%s))", category, name);
else if (category.equals("group"))
filter = String.format("(&(objectCategory=%s)(cn=%s))", category, name);
else if (category.equals("org"))
filter = String.format("(&(objectCategory=organizationalUnit)(ou=%s))", name);
try {
DirContext ldapContext = getLdapContext();
NamingEnumeration<SearchResult> search = ldapContext.search(searchBase, filter, controls);
if (search.hasMoreElements())
return search.next();
} catch (NamingException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取ldap操作上下文
*
* @return
* @throws NamingException
*/
private DirContext getLdapContext() throws NamingException {
//解决No subject alternative DNS name xxxxx的错误
Security.setProperty("jdk.tls.disabledAlgorithms", "");
System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");
Properties prop = new Properties();
prop.setProperty(Context.SECURITY_PROTOCOL, "ssl");
prop.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
prop.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
prop.setProperty(Context.SECURITY_PRINCIPAL, adConfig.getAdminUser());
prop.setProperty(Context.SECURITY_CREDENTIALS, adConfig.getAdminPwd());
prop.setProperty(Context.PROVIDER_URL, adConfig.getPath());
prop.setProperty("java.naming.ldap.factory.socket", "com.simon.common.utils.AD.DummySSLSocketFactory");//注意,此处的com.simon.common.utils.AD.DummySSLSocketFactory指的是你本地的DummySSLSocketFactory类所在的路径
return new InitialLdapContext(prop, null);
}
}
2、跳过证书的配置类
public class DummySSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory factory;
public DummySSLSocketFactory() {
try {
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, new TrustManager[]{new DummyTrustManager()}, new java.security.SecureRandom());
factory = (SSLSocketFactory) sslcontext.getSocketFactory();
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static SocketFactory getDefault() {
return new DummySSLSocketFactory();
}
public Socket createSocket(Socket socket, String s, int i, boolean flag) throws IOException {
return factory.createSocket(socket, s, i, flag);
}
public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1, int j) throws IOException {
return factory.createSocket(inaddr, i, inaddr1, j);
}
public Socket createSocket(InetAddress inaddr, int i) throws IOException {
return factory.createSocket(inaddr, i);
}
public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
return factory.createSocket(s, i, inaddr, j);
}
public Socket createSocket(String s, int i) throws IOException {
return factory.createSocket(s, i);
}
public String[] getDefaultCipherSuites() {
return factory.getSupportedCipherSuites();
}
public String[] getSupportedCipherSuites() {
return factory.getSupportedCipherSuites();
}
}
public class DummyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] cert, String authType) {
return;
}
public void checkServerTrusted( X509Certificate[] cert, String authType) {
return;
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
3、AD域配置信息类
注意:
1、账号和密码必须是管理员账号
2、path为ldap路径,例如:LDAP://ip地址/
3、suffixPath为域组织的根路径,即所有域组织单位的公共路径。例如:OU=***,DC=***,DC=***
@Data
@AllArgsConstructor
public class DomainConfigModel {
/**
* 域服务器账号
*/
private String adminUser;
/**
* 域服务器密码
*/
private String adminPwd;
/**
* 域服务器地址
*/
private String path;
/**
* 域根路径
*/
private String suffixPath;
}