Browse Source

fix:域控对接代码提交

master
yuyantian 1 month ago
parent
commit
c7414c7c1e
5 changed files with 470 additions and 1 deletions
  1. +5
    -0
      hxhq-modules/hxhq-integration/src/main/java/com/hxhq/HxhqIntegrationApplication.java
  2. +102
    -0
      hxhq-modules/hxhq-integration/src/main/java/com/hxhq/common/ad/ADProperties.java
  3. +298
    -0
      hxhq-modules/hxhq-integration/src/main/java/com/hxhq/common/ad/JdkADAuthUtil.java
  4. +44
    -0
      hxhq-modules/hxhq-integration/src/main/java/com/hxhq/controller/ADAuthController.java
  5. +21
    -1
      hxhq-modules/hxhq-integration/src/main/resources/bootstrap.yml

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

@ -1,9 +1,14 @@
package com.hxhq;
import com.hxhq.common.ad.ADProperties;
import com.hxhq.common.ad.JdkADAuthUtil;
import com.hxhq.common.core.web.domain.AjaxResult;
import com.hxhq.common.security.annotation.EnableCustomConfig;
import com.hxhq.common.security.annotation.EnableRyFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
/**

+ 102
- 0
hxhq-modules/hxhq-integration/src/main/java/com/hxhq/common/ad/ADProperties.java View File

@ -0,0 +1,102 @@
package com.hxhq.common.ad;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author yuyantian
* @date 2026/2/11
* @desc
*/
/**
* AD域控配置类绑定application.yml中的hxhq.ad配置
*/
@Component
@ConfigurationProperties(prefix = "hxhq.ad")
public class ADProperties {
// 服务器地址
private String serverHost;
// 端口
private int serverPort = 389;
// 基础DN
private String baseDn;
// 是否启用SSL
private boolean useSsl = false;
// 连接超时
private int connectTimeout = 5000;
// 只读账号子配置
private ReadOnly readOnly = new ReadOnly();
// 内部类只读账号配置
public static class ReadOnly {
private String username;
private String password;
// Getter & Setter
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
// Getter & Setter
public String getServerHost() {
return serverHost;
}
public void setServerHost(String serverHost) {
this.serverHost = serverHost;
}
public int getServerPort() {
return serverPort;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
public String getBaseDn() {
return baseDn;
}
public void setBaseDn(String baseDn) {
this.baseDn = baseDn;
}
public boolean isUseSsl() {
return useSsl;
}
public void setUseSsl(boolean useSsl) {
this.useSsl = useSsl;
}
public int getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public ReadOnly getReadOnly() {
return readOnly;
}
public void setReadOnly(ReadOnly readOnly) {
this.readOnly = readOnly;
}
}

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

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

+ 44
- 0
hxhq-modules/hxhq-integration/src/main/java/com/hxhq/controller/ADAuthController.java View File

@ -0,0 +1,44 @@
package com.hxhq.controller;
import com.hxhq.common.ad.JdkADAuthUtil;
import com.hxhq.common.core.web.domain.AjaxResult;
import io.swagger.v3.oas.models.responses.ApiResponse;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* @author yuyantian
* @date 2026/2/11
* @desc
*/
@RestController
@RequestMapping("/ad") // 接口统一前缀
public class ADAuthController {
// 注入AD工具类Spring Bean
@Resource
private JdkADAuthUtil jdkADAuthUtil;
/**
* 接口1AD账号密码鉴权
* 请求示例POST /ad/validate?username=ELNtest01&password=GLPcd_28
*/
@PostMapping("/validate")
public AjaxResult validateAccount(
@RequestParam String username,
@RequestParam String password
) {
return jdkADAuthUtil.validateAccount(username, password);
}
/**
* 接口2验证AD账号是否存在
* 请求示例GET /ad/exists?username=ELNtest01
*/
@GetMapping("/exists")
public AjaxResult checkAccountExists(@RequestParam String username) {
return jdkADAuthUtil.checkAccountExists(username);
}
}

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

@ -35,4 +35,24 @@ token:
logging:
level:
com.alibaba.cloud.nacos: DEBUG
com.alibaba.cloud.nacos: DEBUG
# 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 # 只读账号密码

Loading…
Cancel
Save