Browse Source

fix:[域控对接]配置测试账号

master
yuyantian 1 month ago
parent
commit
8c1a47cecd
5 changed files with 132 additions and 99 deletions
  1. +39
    -0
      hxhq-modules/hxhq-integration/src/main/java/com/hxhq/HxhqIntegrationApplication.java
  2. +51
    -93
      hxhq-modules/hxhq-integration/src/main/java/com/hxhq/common/ad/JdkADAuthUtil.java
  3. +18
    -0
      hxhq-modules/hxhq-integration/src/main/resources/bootstrap-dev.yml
  4. +18
    -0
      hxhq-modules/hxhq-integration/src/main/resources/bootstrap-prod.yml
  5. +6
    -6
      hxhq-modules/hxhq-integration/src/main/resources/bootstrap.yml

+ 39
- 0
hxhq-modules/hxhq-integration/src/main/java/com/hxhq/HxhqIntegrationApplication.java View File

@ -26,5 +26,44 @@ public class HxhqIntegrationApplication
{ {
SpringApplication.run(HxhqIntegrationApplication.class, args); SpringApplication.run(HxhqIntegrationApplication.class, args);
System.out.println("数据对接模块启动成功"); System.out.println("数据对接模块启动成功");
// 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. 初始化工具类核心传入只读账号替换为你的实际只读账号/密码
JdkADAuthUtil adAuthUtil = new JdkADAuthUtil(adConfig);
// 3. 测试账号
String existUsername = "ELNtest01"; // 存在的账号
String nonExistUsername = "test_not_exists"; // 不存在的账号
String testPassword = "Abcd1234"; // 测试账号密码
// 4. 测试接口2验证账号是否存在无需用户密码
AjaxResult exists = adAuthUtil.checkAccountExists(existUsername);
System.out.println("===== 验证账号[" + existUsername + "]是否存在:" + (exists.isSuccess() ? "存在" : "不存在") + " =====");
AjaxResult exists1 = adAuthUtil.checkAccountExists("ELNtest01@glpcd.com");
System.out.println("===== 验证账号[" + existUsername + "]是否存在:" + (exists1.isSuccess()? "存在" : "不存在") + " =====");
AjaxResult nonExists = adAuthUtil.checkAccountExists(nonExistUsername);
System.out.println("===== 验证账号[" + nonExistUsername + "]是否存在:" + (nonExists.isSuccess() ? "存在" : "不存在") + " =====");
// 5. 测试接口1账号密码鉴权
AjaxResult authSuccess = adAuthUtil.validateAccount(existUsername, testPassword);
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");
System.out.println("===== 账号[" + existUsername + "]错误密码鉴权结果:" + (authFail.isSuccess() ? "成功" : "失败") + " =====");
} }
} }

+ 51
- 93
hxhq-modules/hxhq-integration/src/main/java/com/hxhq/common/ad/JdkADAuthUtil.java View File

@ -1,10 +1,9 @@
package com.hxhq.common.ad; package com.hxhq.common.ad;
import com.hxhq.common.core.web.domain.AjaxResult; import com.hxhq.common.core.web.domain.AjaxResult;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.naming.Context; import javax.naming.Context;
@ -15,21 +14,33 @@ 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.Hashtable;
import java.util.HashMap;
import java.util.Map;
/** /**
* 纯JDK实现的AD域控核心接口Spring Bean化 * 纯JDK实现的AD域控核心接口Spring Bean化
* 核心功能1. 账号密码鉴权 2. 验证账号是否存在基于只读账号 * 核心功能1. 账号密码鉴权 2. 验证账号是否存在基于只读账号
*/ */
@Component // 标记为Spring组件自动注入
@Component
public class JdkADAuthUtil { public class JdkADAuthUtil {
private static final Logger log = LoggerFactory.getLogger(JdkADAuthUtil.class); private static final Logger log = LoggerFactory.getLogger(JdkADAuthUtil.class);
private final ADProperties adProperties; private final ADProperties adProperties;
// 只读账号从配置文件注入
private final String readOnlyUsername; private final String readOnlyUsername;
private final String readOnlyPassword; private final String readOnlyPassword;
@Value("${spring.profiles.active:dev}")
private String activeProfile;
// ====================== dev 环境多测试账号配置 ======================
private static final Map<String, String> DEV_TEST_ACCOUNTS = new HashMap<>();
static {
DEV_TEST_ACCOUNTS.put("hxhq", "hxhq123");
}
/** /**
* 构造器注入AD配置Spring推荐方式
* 构造器注入AD配置
*/ */
public JdkADAuthUtil(ADProperties adProperties) { public JdkADAuthUtil(ADProperties adProperties) {
this.adProperties = adProperties; this.adProperties = adProperties;
@ -38,141 +49,118 @@ public class JdkADAuthUtil {
} }
// ====================== 核心接口1AD账号密码鉴权 ====================== // ====================== 核心接口1AD账号密码鉴权 ======================
/**
* 验证AD账号密码是否正确登录鉴权
*
* @param username 纯用户名ELNtest01
* @param password 密码
* @return true=鉴权成功false=鉴权失败
*/
public AjaxResult validateAccount(String username, String password) { public AjaxResult validateAccount(String username, String password) {
// 入参校验
if (username == null || username.trim().isEmpty() || password == null || password.trim().isEmpty()) { if (username == null || username.trim().isEmpty() || password == null || password.trim().isEmpty()) {
log.debug("用户名或密码为空,鉴权失败"); log.debug("用户名或密码为空,鉴权失败");
return AjaxResult.error("用户名或密码为空,鉴权失败"); return AjaxResult.error("用户名或密码为空,鉴权失败");
} }
// ====== dev 多账号校验 ======
if ("dev".equals(activeProfile)) {
String trimUser = username.trim();
if (DEV_TEST_ACCOUNTS.containsKey(trimUser) && DEV_TEST_ACCOUNTS.get(trimUser).equals(password.trim())) {
log.debug("DEV环境测试账号鉴权成功:{}", username);
return AjaxResult.success("账号[" + username + "]鉴权成功");
}
}
LdapContext ldapContext = null; LdapContext ldapContext = null;
try { try {
// 构建LDAP连接环境绑定用户账号密码
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));
// 尝试绑定绑定成功=账号密码正确
ldapContext = new InitialLdapContext(env, null); ldapContext = new InitialLdapContext(env, null);
log.debug("账号[{}]鉴权成功", username); log.debug("账号[{}]鉴权成功", username);
return AjaxResult.error("账号[{}]鉴权成功", username);
return AjaxResult.success("账号[" + username + "]鉴权成功");
} catch (NamingException e) { } catch (NamingException e) {
String errorMsg = e.getMessage().toLowerCase(); String errorMsg = e.getMessage().toLowerCase();
log.error("账号[{}]鉴权失败:{}", username, errorMsg); log.error("账号[{}]鉴权失败:{}", username, errorMsg);
// 解析常见失败原因
if (errorMsg.contains("data 525")) { if (errorMsg.contains("data 525")) {
log.error("鉴权失败原因:用户不存在");
return AjaxResult.error("鉴权失败原因:用户不存在"); return AjaxResult.error("鉴权失败原因:用户不存在");
} else if (errorMsg.contains("data 52e")) { } else if (errorMsg.contains("data 52e")) {
log.error("鉴权失败原因:密码错误");
return AjaxResult.error("鉴权失败原因:密码错误"); return AjaxResult.error("鉴权失败原因:密码错误");
} else if (errorMsg.contains("data 532")) { } else if (errorMsg.contains("data 532")) {
log.error("鉴权失败原因:密码过期");
return AjaxResult.error("鉴权失败原因:密码过期"); return AjaxResult.error("鉴权失败原因:密码过期");
} else if (errorMsg.contains("data 533")) { } else if (errorMsg.contains("data 533")) {
log.error("鉴权失败原因:账号禁用");
return AjaxResult.error("鉴权失败原因:账号禁用"); return AjaxResult.error("鉴权失败原因:账号禁用");
} else if (errorMsg.contains("data 775")) { } else if (errorMsg.contains("data 775")) {
log.error("鉴权失败原因:账号锁定");
return AjaxResult.error("鉴权失败原因:账号锁定"); return AjaxResult.error("鉴权失败原因:账号锁定");
} }
return AjaxResult.error("账号[" + username + "]鉴权失败");
} finally { } finally {
closeLdapContext(ldapContext); closeLdapContext(ldapContext);
} }
return AjaxResult.success("账号[{}]鉴权成功", username);
} }
// ====================== 核心接口2验证AD账号是否存在 ====================== // ====================== 核心接口2验证AD账号是否存在 ======================
/**
* 验证账号是否在AD域中存在基于只读账号无需用户密码
*
* @param username 纯用户名ELNtest01支持带@后缀的用户名自动处理
* @return true=账号存在false=账号不存在/查询失败
*/
public AjaxResult checkAccountExists(String username) { public AjaxResult checkAccountExists(String username) {
// 入参校验
if (username == null || username.trim().isEmpty()) { if (username == null || username.trim().isEmpty()) {
log.debug("用户名不能为空,查询失败");
return AjaxResult.error("用户名不能为空,查询失败"); return AjaxResult.error("用户名不能为空,查询失败");
} }
// 处理用户名如果带@则直接用否则用sAMAccountName查询核心规则
String queryUsername = username;
if (username.contains("@")) {
// 带后缀用userPrincipalName查询
queryUsername = username;
} else {
// 纯用户名用sAMAccountName查询符合你的规则
queryUsername = username;
String trimUser = username.trim();
// ====== dev 多账号存在校验 ======
if ("dev".equals(activeProfile)) {
// 支持纯账号 hxhq@xxx.com
String pureUser = trimUser.contains("@") ? trimUser.split("@")[0] : trimUser;
if (DEV_TEST_ACCOUNTS.containsKey(pureUser)) {
log.debug("DEV环境测试账号存在:{}", username);
return AjaxResult.success("账号[" + username + "]在AD域中存在");
}
} }
LdapContext ldapContext = null; LdapContext ldapContext = null;
NamingEnumeration<SearchResult> results = null; NamingEnumeration<SearchResult> results = null;
try { try {
// 构建LDAP连接环境强制使用只读账号
Hashtable<String, String> env = buildLDAPEnvForQuery(); Hashtable<String, String> env = buildLDAPEnvForQuery();
ldapContext = new InitialLdapContext(env, null); ldapContext = new InitialLdapContext(env, null);
// 构建查询条件根据是否带后缀选择查询字段
SearchControls searchControls = new SearchControls(); SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[]{"sAMAccountName", "userPrincipalName"}); searchControls.setReturningAttributes(new String[]{"sAMAccountName", "userPrincipalName"});
searchControls.setTimeLimit(3000); searchControls.setTimeLimit(3000);
String filter = username.contains("@")
? String.format("(userPrincipalName=%s)", escapeLDAPFilter(queryUsername))
: String.format("(sAMAccountName=%s)", escapeLDAPFilter(queryUsername));
// 执行查询
String filter = trimUser.contains("@")
? String.format("(userPrincipalName=%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(); boolean exists = results != null && results.hasMoreElements();
if (exists) { if (exists) {
log.debug("账号[{}]在AD域中存在", username);
return AjaxResult.success("账号[{}]在AD域中存在", username);
return AjaxResult.success("账号[" + username + "]在AD域中存在");
} else { } else {
log.error("账号[{}]在AD域中不存在", username);
return AjaxResult.error("账号[{}]在AD域中不存在", username);
return AjaxResult.error("账号[" + username + "]在AD域中不存在");
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.error("查询失败:{}", e.getMessage()); log.error("查询失败:{}", e.getMessage());
return AjaxResult.error("查询失败:{}", e.getMessage());
return AjaxResult.error("查询失败:" + e.getMessage());
} catch (NamingException e) { } catch (NamingException e) {
log.error("查询账号[{}]是否存在失败:{}", username, e.getMessage()); log.error("查询账号[{}]是否存在失败:{}", username, e.getMessage());
// 精准提示只读账号绑定失败
if (e.getMessage().toLowerCase().contains("error code 49")) { if (e.getMessage().toLowerCase().contains("error code 49")) {
log.error("查询失败原因:只读账号绑定失败(账号/密码错误或权限不足)");
return AjaxResult.error("查询失败原因:只读账号绑定失败(账号/密码错误或权限不足)");
return AjaxResult.error("查询失败原因:只读账号绑定失败");
} }
return AjaxResult.error("查询账号[" + username + "]是否存在失败");
} finally { } finally {
closeNamingEnumeration(results); closeNamingEnumeration(results);
closeLdapContext(ldapContext); closeLdapContext(ldapContext);
} }
return AjaxResult.error("查询失败:查询账号[{}]是否存在失败", username);
} }
// ====================== 私有辅助方法 ======================
/**
* 构建鉴权用的LDAP连接环境
*/
// ====================== 以下是原有工具方法完全不变 ======================
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<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// 拼接LDAP连接地址
String ldapUrl = adProperties.isUseSsl() String ldapUrl = adProperties.isUseSsl()
? "ldaps://" + adProperties.getServerHost() + ":" + adProperties.getServerPort() ? "ldaps://" + adProperties.getServerHost() + ":" + adProperties.getServerPort()
: "ldap://" + adProperties.getServerHost() + ":" + adProperties.getServerPort(); : "ldap://" + adProperties.getServerHost() + ":" + adProperties.getServerPort();
env.put(Context.PROVIDER_URL, ldapUrl); env.put(Context.PROVIDER_URL, ldapUrl);
env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_AUTHENTICATION, "simple");
// 处理用户名@直接用否则拼接UPN
String userPrincipal = username; String userPrincipal = username;
if (!username.contains("@")) { if (!username.contains("@")) {
String domainSuffix = adProperties.getBaseDn().replaceAll("DC=", "").replace(",", "."); String domainSuffix = adProperties.getBaseDn().replaceAll("DC=", "").replace(",", ".");
@ -180,51 +168,35 @@ public class JdkADAuthUtil {
} }
env.put(Context.SECURITY_PRINCIPAL, userPrincipal); env.put(Context.SECURITY_PRINCIPAL, userPrincipal);
env.put(Context.SECURITY_CREDENTIALS, password); env.put(Context.SECURITY_CREDENTIALS, password);
// 连接超时
env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(adProperties.getConnectTimeout())); env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(adProperties.getConnectTimeout()));
return env; return env;
} }
/**
* 构建查询用的LDAP连接环境只读账号
*/
private Hashtable<String, String> buildLDAPEnvForQuery() { private Hashtable<String, String> buildLDAPEnvForQuery() {
Hashtable<String, String> env = new Hashtable<>(); Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// 拼接LDAP连接地址
String ldapUrl = adProperties.isUseSsl() String ldapUrl = adProperties.isUseSsl()
? "ldaps://" + adProperties.getServerHost() + ":" + adProperties.getServerPort() ? "ldaps://" + adProperties.getServerHost() + ":" + adProperties.getServerPort()
: "ldap://" + adProperties.getServerHost() + ":" + adProperties.getServerPort(); : "ldap://" + adProperties.getServerHost() + ":" + adProperties.getServerPort();
env.put(Context.PROVIDER_URL, ldapUrl); env.put(Context.PROVIDER_URL, ldapUrl);
env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_AUTHENTICATION, "simple");
// 校验只读账号配置
if (readOnlyUsername == null || readOnlyUsername.trim().isEmpty() if (readOnlyUsername == null || readOnlyUsername.trim().isEmpty()
|| readOnlyPassword == null || readOnlyPassword.trim().isEmpty()) { || readOnlyPassword == null || readOnlyPassword.trim().isEmpty()) {
throw new IllegalArgumentException("只读账号未配置,无法执行AD账号查询!"); throw new IllegalArgumentException("只读账号未配置,无法执行AD账号查询!");
} }
// 拼接只读账号UPN
String domainSuffix = adProperties.getBaseDn().replaceAll("DC=", "").replace(",", "."); String domainSuffix = adProperties.getBaseDn().replaceAll("DC=", "").replace(",", ".");
String readOnlyUpn = readOnlyUsername + "@" + domainSuffix; String readOnlyUpn = readOnlyUsername + "@" + domainSuffix;
env.put(Context.SECURITY_PRINCIPAL, readOnlyUpn); env.put(Context.SECURITY_PRINCIPAL, readOnlyUpn);
env.put(Context.SECURITY_CREDENTIALS, readOnlyPassword); env.put(Context.SECURITY_CREDENTIALS, readOnlyPassword);
log.debug("使用只读账号构建查询环境:{}", readOnlyUpn);
// 连接超时
env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(adProperties.getConnectTimeout())); env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(adProperties.getConnectTimeout()));
return env; return env;
} }
/**
* 转义LDAP过滤器特殊字符
*/
private String escapeLDAPFilter(String input) { private String escapeLDAPFilter(String input) {
if (input == null || input.isEmpty()) {
return input;
}
if (input == null || input.isEmpty()) return input;
return input.replace("\\", "\\5c") return input.replace("\\", "\\5c")
.replace("(", "\\28") .replace("(", "\\28")
.replace(")", "\\29") .replace(")", "\\29")
@ -232,29 +204,15 @@ public class JdkADAuthUtil {
.replace("\0", "\\00"); .replace("\0", "\\00");
} }
/**
* 关闭LDAP连接
*/
private void closeLdapContext(LdapContext ctx) { private void closeLdapContext(LdapContext ctx) {
if (ctx != null) { if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
log.warn("关闭LDAP连接失败:{}", e.getMessage());
}
try { ctx.close(); } catch (NamingException e) { log.warn("关闭LDAP连接失败"); }
} }
} }
/**
* 关闭结果集
*/
private void closeNamingEnumeration(NamingEnumeration<?> ne) { private void closeNamingEnumeration(NamingEnumeration<?> ne) {
if (ne != null) { if (ne != null) {
try {
ne.close();
} catch (NamingException e) {
log.warn("关闭LDAP结果集失败:{}", e.getMessage());
}
try { ne.close(); } catch (NamingException e) { log.warn("关闭LDAP结果集失败"); }
} }
} }

+ 18
- 0
hxhq-modules/hxhq-integration/src/main/resources/bootstrap-dev.yml View File

@ -0,0 +1,18 @@
# AD域控配置(核心)
hxhq:
ad:
# AD服务器地址(IP/域名)
server-host: 172.21.10.1
# LDAP端口(389=非SSL,636=SSL)
server-port: 389
# 基础DN(域根)
base-dn: DC=glpcd,DC=com
# 是否启用SSL(ldaps协议)
use-ssl: false
# 连接超时时间(毫秒)
connect-timeout: 5000
# 只读账号配置(查询账号是否存在用)
read-only:
username: adcon # 只读账号(纯用户名)
password: JYL@it323 # 只读账号密码

+ 18
- 0
hxhq-modules/hxhq-integration/src/main/resources/bootstrap-prod.yml View File

@ -0,0 +1,18 @@
# AD域控配置(核心)
hxhq:
ad:
# AD服务器地址(IP/域名)
server-host: 172.21.10.1
# LDAP端口(389=非SSL,636=SSL)
server-port: 389
# 基础DN(域根)
base-dn: DC=glpcd,DC=com
# 是否启用SSL(ldaps协议)
use-ssl: false
# 连接超时时间(毫秒)
connect-timeout: 5000
# 只读账号配置(查询账号是否存在用)
read-only:
username: adcon # 只读账号(纯用户名)
password: JYL@it323 # 只读账号密码

+ 6
- 6
hxhq-modules/hxhq-integration/src/main/resources/bootstrap.yml View File

@ -11,11 +11,11 @@ spring:
name: hxhq-integration name: hxhq-integration
profiles: profiles:
# 环境配置 # 环境配置
active: dev
active: prod
cloud: cloud:
nacos: nacos:
username: nacos username: nacos
password: nacosHxhq
password: nacos
discovery: discovery:
# 服务注册地址 # 服务注册地址
server-addr: 127.0.0.1:8848 server-addr: 127.0.0.1:8848
@ -24,10 +24,10 @@ spring:
server-addr: 127.0.0.1:8848 server-addr: 127.0.0.1:8848
# 配置文件格式 # 配置文件格式
file-extension: yml file-extension: yml
# 共享配置
shared-configs:
- application- $ {spring.profiles.active}. $ {spring.cloud.nacos.config.file-extension}
- hxhq-integration- $ {spring.profiles.active}. $ {spring.cloud.nacos.config.file-extension}
# # 共享配置
# shared-configs:
# - application- $ {spring.profiles.active}. $ {spring.cloud.nacos.config.file-extension}
# - hxhq-integration- $ {spring.profiles.active}. $ {spring.cloud.nacos.config.file-extension}
# token配置 # token配置
token: token:
# 是否允许账户多终端同时登录(true允许 false不允许) # 是否允许账户多终端同时登录(true允许 false不允许)

Loading…
Cancel
Save