|
|
@ -9,17 +9,19 @@ import org.springframework.stereotype.Component; |
|
|
import javax.naming.Context; |
|
|
import javax.naming.Context; |
|
|
import javax.naming.NamingEnumeration; |
|
|
import javax.naming.NamingEnumeration; |
|
|
import javax.naming.NamingException; |
|
|
import javax.naming.NamingException; |
|
|
|
|
|
import javax.naming.directory.Attribute; |
|
|
|
|
|
import javax.naming.directory.Attributes; |
|
|
import javax.naming.directory.SearchControls; |
|
|
import javax.naming.directory.SearchControls; |
|
|
import javax.naming.directory.SearchResult; |
|
|
import javax.naming.directory.SearchResult; |
|
|
import javax.naming.ldap.InitialLdapContext; |
|
|
import javax.naming.ldap.InitialLdapContext; |
|
|
import javax.naming.ldap.LdapContext; |
|
|
import javax.naming.ldap.LdapContext; |
|
|
import java.util.Hashtable; |
|
|
|
|
|
import java.util.HashMap; |
|
|
|
|
|
import java.util.Map; |
|
|
|
|
|
|
|
|
import java.util.*; |
|
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 纯JDK实现的AD域控核心接口(Spring Bean化) |
|
|
* 纯JDK实现的AD域控核心接口(Spring Bean化) |
|
|
* 核心功能:1. 账号密码鉴权 2. 验证账号是否存在(基于只读账号) |
|
|
* 核心功能:1. 账号密码鉴权 2. 验证账号是否存在(基于只读账号) |
|
|
|
|
|
* 扩展功能:返回用户中文名、部门信息 |
|
|
*/ |
|
|
*/ |
|
|
@Component |
|
|
@Component |
|
|
public class JdkADAuthUtil { |
|
|
public class JdkADAuthUtil { |
|
|
@ -33,10 +35,17 @@ public class JdkADAuthUtil { |
|
|
|
|
|
|
|
|
// ====================== dev 环境多测试账号配置 ====================== |
|
|
// ====================== dev 环境多测试账号配置 ====================== |
|
|
private static final Map<String, String> DEV_TEST_ACCOUNTS = new HashMap<>(); |
|
|
private static final Map<String, String> DEV_TEST_ACCOUNTS = new HashMap<>(); |
|
|
|
|
|
// 测试账号的中文名和部门信息 |
|
|
|
|
|
private static final Map<String, Map<String, String>> DEV_TEST_USER_INFO = new HashMap<>(); |
|
|
|
|
|
|
|
|
static { |
|
|
static { |
|
|
|
|
|
|
|
|
DEV_TEST_ACCOUNTS.put("hxhq", "hxhq123"); |
|
|
DEV_TEST_ACCOUNTS.put("hxhq", "hxhq123"); |
|
|
|
|
|
|
|
|
|
|
|
// 测试账号的扩展信息 |
|
|
|
|
|
Map<String, String> hxhqInfo = new HashMap<>(); |
|
|
|
|
|
hxhqInfo.put("displayName", "测试用户"); |
|
|
|
|
|
hxhqInfo.put("mail", "hxhq@glpcd.com"); |
|
|
|
|
|
DEV_TEST_USER_INFO.put("hxhq", hxhqInfo); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -60,7 +69,15 @@ public class JdkADAuthUtil { |
|
|
String trimUser = username.trim(); |
|
|
String trimUser = username.trim(); |
|
|
if (DEV_TEST_ACCOUNTS.containsKey(trimUser) && DEV_TEST_ACCOUNTS.get(trimUser).equals(password.trim())) { |
|
|
if (DEV_TEST_ACCOUNTS.containsKey(trimUser) && DEV_TEST_ACCOUNTS.get(trimUser).equals(password.trim())) { |
|
|
log.debug("DEV环境测试账号鉴权成功:{}", username); |
|
|
log.debug("DEV环境测试账号鉴权成功:{}", username); |
|
|
return AjaxResult.success("账号[" + username + "]鉴权成功"); |
|
|
|
|
|
|
|
|
Map<String, Object> result = new HashMap<>(); |
|
|
|
|
|
result.put("username", trimUser); |
|
|
|
|
|
result.put("displayName", DEV_TEST_USER_INFO.get(trimUser).get("displayName")); |
|
|
|
|
|
result.put("mail", DEV_TEST_USER_INFO.get(trimUser).get("mail")); |
|
|
|
|
|
result.put("message", "账号[" + username + "]鉴权成功"); |
|
|
|
|
|
return AjaxResult.success(result); |
|
|
|
|
|
}else { |
|
|
|
|
|
log.debug("DEV环境测试账号鉴权失败:{}", username); |
|
|
|
|
|
return AjaxResult.error("账号[" + username + "]鉴权失败"); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -69,9 +86,15 @@ public class JdkADAuthUtil { |
|
|
Hashtable<String, String> env = buildLDAPEnv(username, password); |
|
|
Hashtable<String, String> env = buildLDAPEnv(username, password); |
|
|
log.debug("开始鉴权,用户主体名:{}", env.get(Context.SECURITY_PRINCIPAL)); |
|
|
log.debug("开始鉴权,用户主体名:{}", env.get(Context.SECURITY_PRINCIPAL)); |
|
|
|
|
|
|
|
|
|
|
|
// 1. 先验证账号密码 |
|
|
ldapContext = new InitialLdapContext(env, null); |
|
|
ldapContext = new InitialLdapContext(env, null); |
|
|
log.debug("账号[{}]鉴权成功", username); |
|
|
|
|
|
return AjaxResult.success("账号[" + username + "]鉴权成功"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 密码验证成功后,查询用户详细信息 |
|
|
|
|
|
Map<String, Object> userInfo = getUserInfo(username); |
|
|
|
|
|
userInfo.put("message", "账号[" + username + "]鉴权成功"); |
|
|
|
|
|
|
|
|
|
|
|
log.debug("账号[{}]鉴权成功,用户信息:{}", username, userInfo); |
|
|
|
|
|
return AjaxResult.success(userInfo); |
|
|
|
|
|
|
|
|
} catch (NamingException e) { |
|
|
} catch (NamingException e) { |
|
|
String errorMsg = e.getMessage().toLowerCase(); |
|
|
String errorMsg = e.getMessage().toLowerCase(); |
|
|
@ -108,8 +131,17 @@ public class JdkADAuthUtil { |
|
|
String pureUser = trimUser.contains("@") ? trimUser.split("@")[0] : trimUser; |
|
|
String pureUser = trimUser.contains("@") ? trimUser.split("@")[0] : trimUser; |
|
|
if (DEV_TEST_ACCOUNTS.containsKey(pureUser)) { |
|
|
if (DEV_TEST_ACCOUNTS.containsKey(pureUser)) { |
|
|
log.debug("DEV环境测试账号存在:{}", username); |
|
|
log.debug("DEV环境测试账号存在:{}", username); |
|
|
return AjaxResult.success("账号[" + username + "]在AD域中存在"); |
|
|
|
|
|
|
|
|
Map<String, Object> result = new HashMap<>(); |
|
|
|
|
|
result.put("username", pureUser); |
|
|
|
|
|
result.put("displayName", DEV_TEST_USER_INFO.get(pureUser).get("displayName")); |
|
|
|
|
|
result.put("mail", DEV_TEST_USER_INFO.get(pureUser).get("mail")); |
|
|
|
|
|
result.put("message", "账号[" + username + "]在AD域中存在"); |
|
|
|
|
|
return AjaxResult.success(result); |
|
|
|
|
|
}else { |
|
|
|
|
|
return AjaxResult.error("账号[" + username + "]在AD域中不存在"); |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
LdapContext ldapContext = null; |
|
|
LdapContext ldapContext = null; |
|
|
@ -120,7 +152,10 @@ public class JdkADAuthUtil { |
|
|
|
|
|
|
|
|
SearchControls searchControls = new SearchControls(); |
|
|
SearchControls searchControls = new SearchControls(); |
|
|
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); |
|
|
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); |
|
|
searchControls.setReturningAttributes(new String[]{"sAMAccountName", "userPrincipalName"}); |
|
|
|
|
|
|
|
|
// 扩展返回属性:增加cn(中文名)、department(部门)、displayName(显示名) |
|
|
|
|
|
searchControls.setReturningAttributes(new String[]{ |
|
|
|
|
|
"sAMAccountName", "userPrincipalName", "cn", "department", "displayName", "mail" |
|
|
|
|
|
}); |
|
|
searchControls.setTimeLimit(3000); |
|
|
searchControls.setTimeLimit(3000); |
|
|
|
|
|
|
|
|
String filter = trimUser.contains("@") |
|
|
String filter = trimUser.contains("@") |
|
|
@ -128,10 +163,20 @@ public class JdkADAuthUtil { |
|
|
: String.format("(sAMAccountName=%s)", escapeLDAPFilter(trimUser)); |
|
|
: String.format("(sAMAccountName=%s)", escapeLDAPFilter(trimUser)); |
|
|
|
|
|
|
|
|
results = ldapContext.search(adProperties.getBaseDn(), filter, searchControls); |
|
|
results = ldapContext.search(adProperties.getBaseDn(), filter, searchControls); |
|
|
boolean exists = results != null && results.hasMoreElements(); |
|
|
|
|
|
|
|
|
|
|
|
if (exists) { |
|
|
|
|
|
return AjaxResult.success("账号[" + username + "]在AD域中存在"); |
|
|
|
|
|
|
|
|
if (results != null && results.hasMoreElements()) { |
|
|
|
|
|
SearchResult result = results.nextElement(); |
|
|
|
|
|
Attributes attrs = result.getAttributes(); |
|
|
|
|
|
|
|
|
|
|
|
// 封装用户信息 |
|
|
|
|
|
Map<String, Object> userInfo = new HashMap<>(); |
|
|
|
|
|
userInfo.put("username", trimUser); |
|
|
|
|
|
userInfo.put("displayName", getAttributeValue(attrs, "displayName")); |
|
|
|
|
|
userInfo.put("mail", getAttributeValue(attrs, "mail")); |
|
|
|
|
|
|
|
|
|
|
|
userInfo.put("message", "账号[" + username + "]在AD域中存在"); |
|
|
|
|
|
|
|
|
|
|
|
return AjaxResult.success(userInfo); |
|
|
} else { |
|
|
} else { |
|
|
return AjaxResult.error("账号[" + username + "]在AD域中不存在"); |
|
|
return AjaxResult.error("账号[" + username + "]在AD域中不存在"); |
|
|
} |
|
|
} |
|
|
@ -150,6 +195,69 @@ public class JdkADAuthUtil { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 获取用户详细信息(鉴权成功后调用) |
|
|
|
|
|
*/ |
|
|
|
|
|
private Map<String, Object> getUserInfo(String username) throws NamingException { |
|
|
|
|
|
LdapContext ldapContext = null; |
|
|
|
|
|
NamingEnumeration<SearchResult> results = null; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 使用只读账号查询用户信息 |
|
|
|
|
|
Hashtable<String, String> env = buildLDAPEnvForQuery(); |
|
|
|
|
|
ldapContext = new InitialLdapContext(env, null); |
|
|
|
|
|
|
|
|
|
|
|
SearchControls searchControls = new SearchControls(); |
|
|
|
|
|
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); |
|
|
|
|
|
searchControls.setReturningAttributes(new String[]{ |
|
|
|
|
|
"sAMAccountName", "cn", "department", "displayName", "mail", |
|
|
|
|
|
}); |
|
|
|
|
|
searchControls.setTimeLimit(3000); |
|
|
|
|
|
|
|
|
|
|
|
String filter = username.contains("@") |
|
|
|
|
|
? String.format("(userPrincipalName=%s)", escapeLDAPFilter(username)) |
|
|
|
|
|
: String.format("(sAMAccountName=%s)", escapeLDAPFilter(username)); |
|
|
|
|
|
|
|
|
|
|
|
results = ldapContext.search(adProperties.getBaseDn(), filter, searchControls); |
|
|
|
|
|
|
|
|
|
|
|
Map<String, Object> userInfo = new HashMap<>(); |
|
|
|
|
|
userInfo.put("username", username); |
|
|
|
|
|
|
|
|
|
|
|
if (results != null && results.hasMoreElements()) { |
|
|
|
|
|
SearchResult result = results.nextElement(); |
|
|
|
|
|
Attributes attrs = result.getAttributes(); |
|
|
|
|
|
userInfo.put("displayName", getAttributeValue(attrs, "displayName")); |
|
|
|
|
|
userInfo.put("mail", getAttributeValue(attrs, "mail")); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return userInfo; |
|
|
|
|
|
} finally { |
|
|
|
|
|
closeNamingEnumeration(results); |
|
|
|
|
|
closeLdapContext(ldapContext); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 安全获取AD属性值(处理空值) |
|
|
|
|
|
*/ |
|
|
|
|
|
private String getAttributeValue(Attributes attrs, String attrName) { |
|
|
|
|
|
if (attrs == null) { |
|
|
|
|
|
return ""; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Attribute attr = attrs.get(attrName); |
|
|
|
|
|
if (attr == null) { |
|
|
|
|
|
return ""; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
return attr.get() != null ? attr.get().toString() : ""; |
|
|
|
|
|
} catch (NamingException e) { |
|
|
|
|
|
log.warn("获取属性[{}]失败", attrName, e); |
|
|
|
|
|
return ""; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// ====================== 以下是原有工具方法,完全不变 ====================== |
|
|
// ====================== 以下是原有工具方法,完全不变 ====================== |
|
|
private Hashtable<String, String> buildLDAPEnv(String username, String password) { |
|
|
private Hashtable<String, String> buildLDAPEnv(String username, String password) { |
|
|
Hashtable<String, String> env = new Hashtable<>(); |
|
|
Hashtable<String, String> env = new Hashtable<>(); |
|
|
@ -216,25 +324,94 @@ public class JdkADAuthUtil { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 查询AD用户【所有属性】,返回完整Map,用于查看该用户到底有哪些字段 |
|
|
|
|
|
*/ |
|
|
|
|
|
public AjaxResult getUserAllAttributes(String username) { |
|
|
|
|
|
if (username == null || username.trim().isEmpty()) { |
|
|
|
|
|
return AjaxResult.error("用户名不能为空"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String trimUser = username.trim(); |
|
|
|
|
|
|
|
|
|
|
|
LdapContext ldapContext = null; |
|
|
|
|
|
NamingEnumeration<SearchResult> results = null; |
|
|
|
|
|
Map<String, Object> allAttrMap = new HashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
Hashtable<String, String> env = buildLDAPEnvForQuery(); |
|
|
|
|
|
ldapContext = new InitialLdapContext(env, null); |
|
|
|
|
|
|
|
|
|
|
|
SearchControls searchControls = new SearchControls(); |
|
|
|
|
|
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); |
|
|
|
|
|
// 【关键】返回 *所有* 属性,不指定字段 |
|
|
|
|
|
searchControls.setReturningAttributes(null); |
|
|
|
|
|
searchControls.setTimeLimit(5000); |
|
|
|
|
|
|
|
|
|
|
|
String filter = trimUser.contains("@") |
|
|
|
|
|
? String.format("(userPrincipalName=%s)", escapeLDAPFilter(trimUser)) |
|
|
|
|
|
: String.format("(sAMAccountName=%s)", escapeLDAPFilter(trimUser)); |
|
|
|
|
|
|
|
|
|
|
|
results = ldapContext.search(adProperties.getBaseDn(), filter, searchControls); |
|
|
|
|
|
|
|
|
|
|
|
if (results != null && results.hasMoreElements()) { |
|
|
|
|
|
SearchResult searchResult = results.next(); |
|
|
|
|
|
Attributes attributes = searchResult.getAttributes(); |
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有属性 |
|
|
|
|
|
NamingEnumeration<String> ids = attributes.getIDs(); |
|
|
|
|
|
while (ids.hasMoreElements()) { |
|
|
|
|
|
String id = ids.next(); |
|
|
|
|
|
Attribute attr = attributes.get(id); |
|
|
|
|
|
if (attr == null) { |
|
|
|
|
|
allAttrMap.put(id, null); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
// 单个值 |
|
|
|
|
|
Object value = attr.get(); |
|
|
|
|
|
allAttrMap.put(id, value != null ? value.toString() : null); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
// 多值属性(如memberOf) |
|
|
|
|
|
try { |
|
|
|
|
|
NamingEnumeration<?> ne = attr.getAll(); |
|
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
|
|
|
while (ne.hasMoreElements()) { |
|
|
|
|
|
sb.append(ne.next()).append("; "); |
|
|
|
|
|
} |
|
|
|
|
|
allAttrMap.put(id, sb.toString()); |
|
|
|
|
|
} catch (Exception ex) { |
|
|
|
|
|
allAttrMap.put(id, "无法解析"); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
log.info("AD用户[{}]所有属性:{}", username, allAttrMap); |
|
|
|
|
|
return AjaxResult.success("获取成功", allAttrMap); |
|
|
|
|
|
} else { |
|
|
|
|
|
return AjaxResult.error("用户不存在"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("获取用户所有属性异常", e); |
|
|
|
|
|
return AjaxResult.error("获取失败:" + e.getMessage()); |
|
|
|
|
|
} finally { |
|
|
|
|
|
closeNamingEnumeration(results); |
|
|
|
|
|
closeLdapContext(ldapContext); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) { |
|
|
public static void main(String[] args) { |
|
|
// |
|
|
|
|
|
// // 1. 配置AD基础信息(替换为你的实际配置) |
|
|
|
|
|
// ADProperties adConfig = new ADProperties(); |
|
|
|
|
|
// adConfig.setServerHost("172.21.10.1"); |
|
|
|
|
|
// adConfig.setServerPort(389); |
|
|
|
|
|
// adConfig.setBaseDn("DC=glpcd,DC=com"); |
|
|
|
|
|
// adConfig.setUseSsl(false); |
|
|
|
|
|
// adConfig.setConnectTimeout(5000); |
|
|
|
|
|
// adConfig.getReadOnly().setUsername("adcon"); |
|
|
|
|
|
// adConfig.getReadOnly().setPassword("JYL@it323"); |
|
|
|
|
|
// |
|
|
|
|
|
// // 2. 初始化工具类(★核心★:传入只读账号,替换为你的实际只读账号/密码) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // 2. 初始化工具类(★核心★:传入只读账号,替换为你的实际只读账号/密码) |
|
|
// JdkADAuthUtil adAuthUtil = new JdkADAuthUtil(adConfig); |
|
|
// JdkADAuthUtil adAuthUtil = new JdkADAuthUtil(adConfig); |
|
|
// |
|
|
// |
|
|
// // 3. 测试账号 |
|
|
// // 3. 测试账号 |
|
|
// String existUsername = "ELNtest01"; // 存在的账号 |
|
|
// String existUsername = "ELNtest01"; // 存在的账号 |
|
|
// String nonExistUsername = "test_not_exists"; // 不存在的账号 |
|
|
// String nonExistUsername = "test_not_exists"; // 不存在的账号 |
|
|
// String testPassword = "GLPcd_28"; // 测试账号密码 |
|
|
|
|
|
|
|
|
// String testPassword = "Abcd1234"; // 测试账号密码 |
|
|
// |
|
|
// |
|
|
// // 4. 测试接口2:验证账号是否存在(无需用户密码) |
|
|
// // 4. 测试接口2:验证账号是否存在(无需用户密码) |
|
|
// AjaxResult exists = adAuthUtil.checkAccountExists(existUsername); |
|
|
// AjaxResult exists = adAuthUtil.checkAccountExists(existUsername); |
|
|
@ -249,8 +426,12 @@ public class JdkADAuthUtil { |
|
|
// // 5. 测试接口1:账号密码鉴权 |
|
|
// // 5. 测试接口1:账号密码鉴权 |
|
|
// AjaxResult authSuccess = adAuthUtil.validateAccount(existUsername, testPassword); |
|
|
// AjaxResult authSuccess = adAuthUtil.validateAccount(existUsername, testPassword); |
|
|
// System.out.println("===== 账号[" + existUsername + "]鉴权结果:" + (authSuccess.isSuccess() ? "成功" : "失败") + " ====="); |
|
|
// System.out.println("===== 账号[" + existUsername + "]鉴权结果:" + (authSuccess.isSuccess() ? "成功" : "失败") + " ====="); |
|
|
|
|
|
// AjaxResult authSuccess1 = adAuthUtil.validateAccount(existUsername+"@glpcd.com", testPassword); |
|
|
|
|
|
// System.out.println("===== 账号[" + existUsername + "]鉴权结果:" + (authSuccess1.isSuccess() ? "成功" : "失败") + " ====="); |
|
|
|
|
|
// |
|
|
// |
|
|
// |
|
|
// AjaxResult authFail = adAuthUtil.validateAccount(existUsername, "wrong_password"); |
|
|
// AjaxResult authFail = adAuthUtil.validateAccount(existUsername, "wrong_password"); |
|
|
// System.out.println("===== 账号[" + existUsername + "]错误密码鉴权结果:" + (authFail.isSuccess() ? "成功" : "失败") + " ====="); |
|
|
// System.out.println("===== 账号[" + existUsername + "]错误密码鉴权结果:" + (authFail.isSuccess() ? "成功" : "失败") + " ====="); |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |