From 2bc448e913491c5b6d6f0debb19671a7a5d9f4d7 Mon Sep 17 00:00:00 2001 From: caiyuchao Date: Fri, 22 Aug 2025 19:07:04 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=AF=E6=8C=81LDAP=E7=99=BB?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agt-module-system-server/pom.xml | 5 + .../admin/user/vo/user/LdapUser.java | 31 ++++++ .../service/auth/AdminAuthServiceImpl.java | 70 +++++++++++- .../service/user/AdminUserServiceImpl.java | 104 +++++++++++++++--- .../system/util/ldap/SHA512CryptVerifier.java | 58 ++++++++++ .../src/main/resources/application-local.yaml | 9 +- 6 files changed, 258 insertions(+), 19 deletions(-) create mode 100644 agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/controller/admin/user/vo/user/LdapUser.java create mode 100644 agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/util/ldap/SHA512CryptVerifier.java diff --git a/agt-module-system/agt-module-system-server/pom.xml b/agt-module-system/agt-module-system-server/pom.xml index f374849..aeff863 100644 --- a/agt-module-system/agt-module-system-server/pom.xml +++ b/agt-module-system/agt-module-system-server/pom.xml @@ -163,6 +163,11 @@ hutool-extra + + org.springframework.boot + spring-boot-starter-data-ldap + + diff --git a/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/controller/admin/user/vo/user/LdapUser.java b/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/controller/admin/user/vo/user/LdapUser.java new file mode 100644 index 0000000..b0d42a1 --- /dev/null +++ b/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/controller/admin/user/vo/user/LdapUser.java @@ -0,0 +1,31 @@ +package org.agt.module.system.controller.admin.user.vo.user; + +import lombok.Data; +import org.springframework.ldap.odm.annotations.Attribute; +import org.springframework.ldap.odm.annotations.Entry; +import org.springframework.ldap.odm.annotations.Id; + +import javax.naming.Name; + +@Data +@Entry(base = "ou=users", objectClasses = {"inetOrgPerson", "organizationalPerson", "person", "top"}) +public class LdapUser { + + @Id + private Name dn; + + @Attribute(name = "cn") + private String username; + + @Attribute(name = "sn") + private String surname; + + @Attribute(name = "userPassword") + private String password; + + @Attribute(name = "mail") + private String email; + + @Attribute(name = "gecos") + private String gecos; +} \ No newline at end of file diff --git a/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/service/auth/AdminAuthServiceImpl.java b/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/service/auth/AdminAuthServiceImpl.java index 32e6126..bf97736 100644 --- a/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/service/auth/AdminAuthServiceImpl.java +++ b/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/service/auth/AdminAuthServiceImpl.java @@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j; import org.agt.framework.common.enums.CommonStatusEnum; import org.agt.framework.common.enums.UserTypeEnum; import org.agt.framework.common.util.monitor.TracerUtils; +import org.agt.framework.common.util.object.BeanUtils; import org.agt.framework.common.util.servlet.ServletUtils; import org.agt.framework.common.util.validation.ValidationUtils; import org.agt.module.system.api.logger.dto.LoginLogCreateReqDTO; @@ -19,7 +20,17 @@ import org.agt.module.system.api.sms.SmsCodeApi; import org.agt.module.system.api.sms.dto.code.SmsCodeUseReqDTO; import org.agt.module.system.api.social.dto.SocialUserBindReqDTO; import org.agt.module.system.api.social.dto.SocialUserRespDTO; -import org.agt.module.system.controller.admin.auth.vo.*; +import org.agt.module.system.controller.admin.auth.vo.AuthLoginReqVO; +import org.agt.module.system.controller.admin.auth.vo.AuthLoginRespVO; +import org.agt.module.system.controller.admin.auth.vo.AuthRegisterReqVO; +import org.agt.module.system.controller.admin.auth.vo.AuthResetPasswordByNameReqVO; +import org.agt.module.system.controller.admin.auth.vo.AuthResetPasswordReqVO; +import org.agt.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO; +import org.agt.module.system.controller.admin.auth.vo.AuthSmsSendReqVO; +import org.agt.module.system.controller.admin.auth.vo.AuthSocialLoginReqVO; +import org.agt.module.system.controller.admin.auth.vo.CaptchaVerificationReqVO; +import org.agt.module.system.controller.admin.user.vo.user.LdapUser; +import org.agt.module.system.controller.admin.user.vo.user.UserSaveReqVO; import org.agt.module.system.convert.auth.AuthConvert; import org.agt.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import org.agt.module.system.dal.dataobject.user.AdminUserDO; @@ -30,17 +41,29 @@ import org.agt.module.system.enums.sms.SmsSceneEnum; import org.agt.module.system.service.logger.LoginLogService; import org.agt.module.system.service.member.MemberService; import org.agt.module.system.service.oauth2.OAuth2TokenService; +import org.agt.module.system.service.permission.PermissionService; import org.agt.module.system.service.social.SocialUserService; import org.agt.module.system.service.user.AdminUserService; import org.springframework.beans.factory.annotation.Value; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.ldap.query.LdapQueryBuilder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Objects; +import java.util.Set; import static org.agt.framework.common.exception.util.ServiceExceptionUtil.exception; import static org.agt.framework.common.util.servlet.ServletUtils.getClientIP; -import static org.agt.module.system.enums.ErrorCodeConstants.*; +import static org.agt.module.system.enums.ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS; +import static org.agt.module.system.enums.ErrorCodeConstants.AUTH_LOGIN_CAPTCHA_CODE_ERROR; +import static org.agt.module.system.enums.ErrorCodeConstants.AUTH_LOGIN_USER_DISABLED; +import static org.agt.module.system.enums.ErrorCodeConstants.AUTH_MOBILE_NOT_EXISTS; +import static org.agt.module.system.enums.ErrorCodeConstants.AUTH_REGISTER_CAPTCHA_CODE_ERROR; +import static org.agt.module.system.enums.ErrorCodeConstants.AUTH_THIRD_LOGIN_NOT_BIND; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_MOBILE_NOT_EXISTS; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; /** * Auth Service 实现类 @@ -67,6 +90,10 @@ public class AdminAuthServiceImpl implements AdminAuthService { private CaptchaService captchaService; @Resource private SmsCodeApi smsCodeApi; + @Resource + private LdapTemplate ldapTemplate; + @Resource + private PermissionService permissionService; /** * 验证码的开关,默认为 true @@ -96,13 +123,50 @@ public class AdminAuthServiceImpl implements AdminAuthService { return user; } + public AdminUserDO authenticateByLdap(String username, String password) { + + final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; + LdapUser ldapUsers; + try { + LdapQuery query = LdapQueryBuilder.query() + .where("uid").is(username).or("mail").is(username); + ldapUsers = ldapTemplate.findOne(query, LdapUser.class); + ldapTemplate.authenticate(query, password); + } catch (Exception e) { + log.error("登录失败:{}", e.getMessage()); + createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + } + if (ldapUsers == null) { + createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + } + + // 校验账号是否存在 + AdminUserDO user = userService.getUserByUsername(username); + if (user == null) { + UserSaveReqVO userSaveReqVO = new UserSaveReqVO(); + userSaveReqVO.setUsername(username); + userSaveReqVO.setPassword(password); + userSaveReqVO.setNickname(ldapUsers.getGecos()); + userSaveReqVO.setEmail(ldapUsers.getEmail()); + Long userId = userService.createUser(userSaveReqVO); + permissionService.assignUserRole(userId, Set.of(3L)); + user = BeanUtils.toBean(userSaveReqVO, AdminUserDO.class); + user.setId(userId); + } + + return user; + } + @Override public AuthLoginRespVO login(AuthLoginReqVO reqVO) { // 校验验证码 validateCaptcha(reqVO); // 使用账号密码,进行登录 - AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword()); +// AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword()); + AdminUserDO user = authenticateByLdap(reqVO.getUsername(), reqVO.getPassword()); // 首次登录不返回token if (user.getLoginDate() == null) { diff --git a/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/service/user/AdminUserServiceImpl.java b/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/service/user/AdminUserServiceImpl.java index 3f53ee1..9739d15 100644 --- a/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/service/user/AdminUserServiceImpl.java +++ b/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/service/user/AdminUserServiceImpl.java @@ -4,6 +4,13 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; +import com.google.common.annotations.VisibleForTesting; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; import org.agt.framework.common.enums.CommonStatusEnum; import org.agt.framework.common.exception.ServiceException; import org.agt.framework.common.pojo.PageResult; @@ -15,6 +22,7 @@ import org.agt.module.infra.api.config.ConfigApi; import org.agt.module.system.controller.admin.auth.vo.AuthRegisterReqVO; import org.agt.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; import org.agt.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; +import org.agt.module.system.controller.admin.user.vo.user.LdapUser; import org.agt.module.system.controller.admin.user.vo.user.UserImportExcelVO; import org.agt.module.system.controller.admin.user.vo.user.UserImportRespVO; import org.agt.module.system.controller.admin.user.vo.user.UserPageReqVO; @@ -31,26 +39,52 @@ import org.agt.module.system.service.dept.DeptService; import org.agt.module.system.service.dept.PostService; import org.agt.module.system.service.permission.PermissionService; import org.agt.module.system.service.tenant.TenantService; -import com.google.common.annotations.VisibleForTesting; -import com.mzt.logapi.context.LogRecordContext; -import com.mzt.logapi.service.impl.DiffParseFunction; -import com.mzt.logapi.starter.annotation.LogRecord; -import jakarta.annotation.Resource; -import jakarta.validation.ConstraintViolationException; -import lombok.extern.slf4j.Slf4j; +import org.agt.module.system.util.ldap.SHA512CryptVerifier; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.ldap.query.LdapQueryBuilder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.agt.framework.common.exception.util.ServiceExceptionUtil.exception; -import static org.agt.framework.common.util.collection.CollectionUtils.*; -import static org.agt.module.system.enums.ErrorCodeConstants.*; -import static org.agt.module.system.enums.LogRecordConstants.*; +import static org.agt.framework.common.util.collection.CollectionUtils.convertList; +import static org.agt.framework.common.util.collection.CollectionUtils.convertSet; +import static org.agt.framework.common.util.collection.CollectionUtils.singleton; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_COUNT_MAX; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_EMAIL_EXISTS; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_IMPORT_INIT_PASSWORD; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_IMPORT_LIST_IS_EMPTY; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_IS_DISABLE; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_MOBILE_EXISTS; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_PASSWORD_FAILED; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_REGISTER_DISABLED; +import static org.agt.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_CREATE_SUB_TYPE; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_CREATE_SUCCESS; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_DELETE_SUB_TYPE; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_DELETE_SUCCESS; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_TYPE; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_UPDATE_PASSWORD_SUCCESS; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_UPDATE_SUB_TYPE; +import static org.agt.module.system.enums.LogRecordConstants.SYSTEM_USER_UPDATE_SUCCESS; /** * 后台用户 Service 实现类 @@ -87,6 +121,8 @@ public class AdminUserServiceImpl implements AdminUserService { private ConfigApi configApi; @Autowired private RoleMapper roleMapper; + @Resource + private LdapTemplate ldapTemplate; @Override @Transactional(rollbackFor = Exception.class) @@ -199,11 +235,13 @@ public class AdminUserServiceImpl implements AdminUserService { @Override public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) { // 校验旧密码密码 - validateOldPassword(id, reqVO.getOldPassword()); + AdminUserDO user = validateOldPassword(id, reqVO.getOldPassword()); // 执行更新 AdminUserDO updateObj = new AdminUserDO().setId(id); updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码 userMapper.updateById(updateObj); + + updateLdapPassword(user.getUsername(), reqVO.getNewPassword()); } @Override @@ -219,6 +257,8 @@ public class AdminUserServiceImpl implements AdminUserService { updateObj.setPassword(encodePassword(password)); // 加密密码 userMapper.updateById(updateObj); + updateLdapPassword(user.getUsername(), password); + // 3. 记录操作日志上下文 LogRecordContext.putVariable("user", user); LogRecordContext.putVariable("newPassword", updateObj.getPassword()); @@ -235,6 +275,28 @@ public class AdminUserServiceImpl implements AdminUserService { updateObj.setPassword(encodePassword(password)); // 加密密码 updateObj.setLoginDate(LocalDateTime.now()); userMapper.updateById(updateObj); + + updateLdapPassword(user.getUsername(), password); + } + + private void updateLdapPassword(String username, String password) { + try { + LdapQuery query = LdapQueryBuilder.query() + .where("uid").is(username).or("mail").is(username); + LdapUser ldapUsers = ldapTemplate.findOne(query, LdapUser.class); + + password = SHA512CryptVerifier.generateHash(password, "OY7N5bGk"); + // 创建修改操作 + ModificationItem[] modificationItems = new ModificationItem[1]; + modificationItems[0] = new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, + new BasicAttribute("userPassword", password) + ); + ldapTemplate.modifyAttributes(ldapUsers.getDn(), modificationItems); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw exception(USER_PASSWORD_FAILED, e.getMessage()); + } } @Override @@ -375,7 +437,7 @@ public class AdminUserServiceImpl implements AdminUserService { } private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email, - Long deptId, Set postIds) { + Long deptId, Set postIds) { // 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确 return DataPermissionUtils.executeIgnore(() -> { // 校验用户存在 @@ -462,11 +524,12 @@ public class AdminUserServiceImpl implements AdminUserService { /** * 校验旧密码 + * * @param id 用户 id * @param oldPassword 旧密码 */ @VisibleForTesting - void validateOldPassword(Long id, String oldPassword) { + AdminUserDO validateOldPassword(Long id, String oldPassword) { AdminUserDO user = userMapper.selectById(id); if (user == null) { throw exception(USER_NOT_EXISTS); @@ -474,6 +537,17 @@ public class AdminUserServiceImpl implements AdminUserService { if (!isPasswordMatch(oldPassword, user.getPassword())) { throw exception(USER_PASSWORD_FAILED); } + + try { + String username = user.getUsername(); + LdapQuery query = LdapQueryBuilder.query() + .where("uid").is(username).or("mail").is(username); + ldapTemplate.authenticate(query, oldPassword); + } catch (Exception e) { + log.error("密码校验失败:{}", e.getMessage()); + throw exception(USER_PASSWORD_FAILED); + } + return user; } @Override @@ -496,7 +570,7 @@ public class AdminUserServiceImpl implements AdminUserService { // 2.1.1 校验字段是否符合要求 try { ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword)); - } catch (ConstraintViolationException ex){ + } catch (ConstraintViolationException ex) { respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); return; } diff --git a/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/util/ldap/SHA512CryptVerifier.java b/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/util/ldap/SHA512CryptVerifier.java new file mode 100644 index 0000000..61c851f --- /dev/null +++ b/agt-module-system/agt-module-system-server/src/main/java/org/agt/module/system/util/ldap/SHA512CryptVerifier.java @@ -0,0 +1,58 @@ +package org.agt.module.system.util.ldap; + +import org.apache.commons.codec.digest.Sha2Crypt; + +public class SHA512CryptVerifier { + + /** + * 验证密码是否匹配SHA-512 Crypt哈希 + * @param password 明文密码 + * @param hash 完整的哈希字符串(包括{CRYPT}前缀) + * @return 匹配返回true,否则返回false + */ + public static boolean verifyPassword(String password, String hash) { + // 去除{CRYPT}前缀(如果存在) + if (hash.startsWith("{CRYPT}")) { + hash = hash.substring(7); + } + + try { + // 使用Apache Commons Codec的Sha2Crypt进行验证 + String computedHash = Sha2Crypt.sha512Crypt(password.getBytes(), hash); + return computedHash.equals(hash); + } catch (Exception e) { + System.err.println("验证失败: " + e.getMessage()); + return false; + } + } + + /** + * 生成SHA-512 Crypt哈希 + * @param password 明文密码 + * @param salt 盐值(可选,如果为null则自动生成) + * @return 哈希值(包含{CRYPT}前缀) + */ + public static String generateHash(String password, String salt) { + String hash; + if (salt != null) { + hash = Sha2Crypt.sha512Crypt(password.getBytes(), "$6$" + salt); + } else { + hash = Sha2Crypt.sha512Crypt(password.getBytes()); + } + return "{CRYPT}" + hash; + } + + public static void main(String[] args) { + String storedHash = "{CRYPT}$6$OY7N5bGk$WF6pEJOYU1SEySOuvLxVBNo2MRMsNAX4PX9JnzgUTYfDzevl1/fruztCTM0mIuiAUb3eJ//DEYxzFABmOlIzm/"; + String password = "test"; // 要验证的密码 + + // 验证密码 + boolean isValid = verifyPassword(password, storedHash); + System.out.println("密码验证结果: " + isValid); + + // 生成新哈希(使用相同盐值) + String newHash = generateHash(password, "OY7N5bGk"); + System.out.println("生成的哈希: " + newHash); + System.out.println("哈希匹配: " + newHash.equals(storedHash)); + } +} \ No newline at end of file diff --git a/agt-server/src/main/resources/application-local.yaml b/agt-server/src/main/resources/application-local.yaml index ca78ac6..9d8c812 100644 --- a/agt-server/src/main/resources/application-local.yaml +++ b/agt-server/src/main/resources/application-local.yaml @@ -250,4 +250,11 @@ justauth: --- #################### iot相关配置 TODO 芋艿【IOT】:再瞅瞅 #################### pf4j: # pluginsDir: /tmp/ - pluginsDir: ../plugins \ No newline at end of file + pluginsDir: ../plugins + +spring: + ldap: + urls: ldap://192.168.88.205 + base: dc=agrandtech,dc=com + username: uid=root,cn=users,dc=agrandtech,dc=com + password: Tian7989! \ No newline at end of file