feat(auth): 企业 LDAP 登录与身份绑定 - #437
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces LDAP authentication as a fallback mechanism when local authentication fails. Key changes include the addition of LDAP configuration properties, a new LdapAuthService for directory integration, and updates to LocalAuthService to handle the fallback logic. Feedback focuses on critical issues such as potential account duplication when the email attribute is missing, resource leaks due to unclosed LDAP contexts, and the recommendation to use LdapTemplate instead of manual JNDI operations. Additionally, a compilation error in the unit tests regarding the PlatformPrincipal constructor was identified.
| if (email != null && !email.isEmpty()) { | ||
| user = userAccountRepository.findByEmailIgnoreCase(email.toLowerCase()).orElse(null); | ||
| } | ||
|
|
||
| // If not found, create a new user | ||
| if (user == null) { |
There was a problem hiding this comment.
✅ 已修复
修复方案:使用 ldap:{username}@internal 作为唯一标识符,确保同一 LDAP 用户始终映射到同一个本地账号,防止重复账号创建和用户数据丢失。
提交: d8b507b
| PlatformPrincipal ldapPrincipal = new PlatformPrincipal( | ||
| "usr_ldap", | ||
| "ldapuser", | ||
| "ldapuser@example.com", | ||
| Set.of("USER"), | ||
| Set.of(), | ||
| Set.of() | ||
| ); |
There was a problem hiding this comment.
此处使用的 PlatformPrincipal 构造函数与 LocalAuthService 和 LdapAuthService 中的实际调用不匹配。服务代码期望的参数列表为 (String id, String displayName, String email, String avatarUrl, String provider, Set<String> roles),但此测试传递的是 (String, String, String, Set, Set, Set)。这将导致编译错误。
| PlatformPrincipal ldapPrincipal = new PlatformPrincipal( | |
| "usr_ldap", | |
| "ldapuser", | |
| "ldapuser@example.com", | |
| Set.of("USER"), | |
| Set.of(), | |
| Set.of() | |
| ); | |
| PlatformPrincipal ldapPrincipal = new PlatformPrincipal( | |
| "usr_ldap", | |
| "ldapuser", | |
| "ldapuser@example.com", | |
| null, | |
| "ldap", | |
| Set.of("USER") | |
| ); |
There was a problem hiding this comment.
✅ 已修复
修复方案:更正 PlatformPrincipal 构造函数参数顺序为:
(userId, displayName, email, avatarUrl, oauthProvider, roles)
与 LocalAuthService 和 LdapAuthService 中的调用保持一致。
提交: d8b507b
| private Attributes getUserAttributes(String userDn) { | ||
| try { | ||
| Hashtable<String, String> env = new Hashtable<>(); | ||
| env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); | ||
| env.put(Context.PROVIDER_URL, ldapProperties.getUrl()); | ||
| env.put(Context.SECURITY_AUTHENTICATION, "simple"); | ||
|
|
||
| // Use bind DN if configured, otherwise anonymous bind | ||
| if (ldapProperties.getUsername() != null && !ldapProperties.getUsername().isEmpty()) { | ||
| env.put(Context.SECURITY_PRINCIPAL, ldapProperties.getUsername()); | ||
| env.put(Context.SECURITY_CREDENTIALS, ldapProperties.getPassword()); | ||
| } | ||
|
|
||
| DirContext ctx = new InitialDirContext(env); | ||
| Attributes attrs = ctx.getAttributes(new LdapName(userDn)); | ||
| ctx.close(); | ||
| return attrs; | ||
| } catch (Exception e) { |
There was a problem hiding this comment.
✅ 已修复
修复方案:在 finally 块中显式关闭 NamingEnumeration 和 DirContext,防止资源泄露。
finally {
if (results != null) {
try {
results.close();
} catch (Exception e) {
log.warn("Failed to close LDAP search results", e);
}
}
closeContext(ctx);
}提交: d8b507b
| private final LdapTemplate ldapTemplate; | ||
| private final UserAccountRepository userAccountRepository; | ||
| private final UserRoleBindingRepository userRoleBindingRepository; | ||
| private final GlobalNamespaceMembershipService globalNamespaceMembershipService; | ||
|
|
||
| public LdapAuthService(LdapProperties ldapProperties, | ||
| LdapTemplate ldapTemplate, | ||
| UserAccountRepository userAccountRepository, | ||
| UserRoleBindingRepository userRoleBindingRepository, | ||
| GlobalNamespaceMembershipService globalNamespaceMembershipService) { | ||
| this.ldapProperties = ldapProperties; | ||
| this.ldapTemplate = ldapTemplate; | ||
| this.userAccountRepository = userAccountRepository; | ||
| this.userRoleBindingRepository = userRoleBindingRepository; | ||
| this.globalNamespaceMembershipService = globalNamespaceMembershipService; | ||
| } |
There was a problem hiding this comment.
ℹ️️ 不采纳
原因:
- 保持代码一致性:原 PR 新增支持LDAP登录https://github.com/iflytek/skillhub/issues/260 #283 使用手动 JNDI 操作,为保持一致性,我们沿用了该模式
- 资源管理已修复:通过显式关闭
NamingEnumeration和DirContext,资源泄露问题已解决 - 最小化变更原则:作为 bug 修复 PR,应尽量减少架构性变更
- 功能完整性:手动 JNDI 操作提供了更细粒度的控制,便于调试和错误处理
未来改进建议:如果进行大规模重构,可以考虑迁移到 LdapTemplate。
提交: d8b507b
基于 PR iflytek#283 的代码审查反馈,修复了多个关键问题: ## 🔴 Critical 修复 ### 1. 修复 LDAP 账号重复创建问题 **问题**: 如果 LDAP 用户的 mail 属性缺失或为空,每次登录都会创建新的 UserAccount,导致: - 同一用户产生多个账号 - 用户数据在不同会话间丢失 - 数据库中出现大量重复账号 **修复**: - 当 mail 属性为空时,使用 "ldap:{username}@internal" 作为唯一标识符 - 确保同一 LDAP 用户始终映射到同一个本地账号 - 保持用户数据的连续性 ```java // 修复前:email 为 null 时会创建新账号 String normalizedEmail = email != null ? email.toLowerCase() : null; // 修复后:使用稳定的唯一标识符 String normalizedEmail = email != null ? email.toLowerCase() : "ldap:" + username + "@internal"; ``` ## 🟠 High 优先级修复 ### 2. 修复测试代码中的 PlatformPrincipal 构造函数错误 **问题**: 测试中使用了错误的构造函数参数,导致编译错误 **修复**: 更正为正确的参数顺序: ```java // 修复前 new PlatformPrincipal(userId, displayName, email, Set.of(), Set.of(), Set.of()) // 修复后 new PlatformPrincipal(userId, displayName, email, null, "ldap", Set.of("USER")) ``` ### 3. 修复资源泄露问题 **问题**: `NamingEnumeration<SearchResult>` 没有正确关闭,导致 LDAP 连接泄露 **修复**: 在 finally 块中显式关闭 NamingEnumeration ```java finally { if (results != null) { try { results.close(); } catch (Exception e) { log.warn("Failed to close LDAP search results", e); } } closeContext(ctx); } ``` ## 📝 测试覆盖 - 添加了 3 个新测试用例验证 LDAP 回退功能 - 所有测试用例使用正确的构造函数 - 确保原有测试不受 LDAP 功能影响 ## 🔗 相关链接 - 原始 PR: iflytek#283 - 修复 PR: iflytek#437 - Issue: iflytek#260 这些修复确保了 LDAP 认证功能的稳定性和正确性。
基于 PR iflytek#283 的代码审查反馈,修复了多个关键问题: ## 🔴 Critical 修复 ### 1. 修复 LDAP 账号重复创建问题 **问题**: 如果 LDAP 用户的 mail 属性缺失或为空,每次登录都会创建新的 UserAccount,导致: - 同一用户产生多个账号 - 用户数据在不同会话间丢失 - 数据库中出现大量重复账号 **修复**: - 当 mail 属性为空时,使用 "ldap:{username}@internal" 作为唯一标识符 - 确保同一 LDAP 用户始终映射到同一个本地账号 - 保持用户数据的连续性 ```java // 修复前:email 为 null 时会创建新账号 String normalizedEmail = email != null ? email.toLowerCase() : null; // 修复后:使用稳定的唯一标识符 String normalizedEmail = email != null ? email.toLowerCase() : "ldap:" + username + "@internal"; ``` ## 🟠 High 优先级修复 ### 2. 修复测试代码中的 PlatformPrincipal 构造函数错误 **问题**: 测试中使用了错误的构造函数参数,导致编译错误 **修复**: 更正为正确的参数顺序: ```java // 修复前 new PlatformPrincipal(userId, displayName, email, Set.of(), Set.of(), Set.of()) // 修复后 new PlatformPrincipal(userId, displayName, email, null, "ldap", Set.of("USER")) ``` ### 3. 修复资源泄露问题 **问题**: `NamingEnumeration<SearchResult>` 没有正确关闭,导致 LDAP 连接泄露 **修复**: 在 finally 块中显式关闭 NamingEnumeration ```java finally { if (results != null) { try { results.close(); } catch (Exception e) { log.warn("Failed to close LDAP search results", e); } } closeContext(ctx); } ``` ## 📝 测试覆盖 - 添加了 3 个新测试用例验证 LDAP 回退功能 - 所有测试用例使用正确的构造函数 - 确保原有测试不受 LDAP 功能影响 ## 🔗 相关链接 - 原始 PR: iflytek#283 - 修复 PR: iflytek#437 - Issue: iflytek#260 这些修复确保了 LDAP 认证功能的稳定性和正确性。
|
您好!这个 PR 修复了 PR #283 的测试问题,包括:
当前有 2 个 workflows 需要您的批准:
烦请批准运行,谢谢! |
好的,已批准 |
|
@jangrui 辛苦了,接力推进 LDAP 这个 PR 不容易,几个关键修复都很到位 👍 我从外部看了一下 #283 和 #437 的对比,整理一些想法供参考。 相比 #283 已经修好的几处都很关键:
一些可以再讨论的点:
关于合并路径建议: #437 和 #283 是 100% 重叠的,建议 maintainer 考虑直接合并 #437,同时关闭 #283 并在关闭说明里注明"由 #437 替代,感谢 @cw1427 的原始贡献",保留对原作者的归属感谢。合并前如果能顺手处理一下上面的 #1(AutoConfiguration 启动副作用)和 #3(日志泄露)就更稳了。 不管最终怎么定,你这次的接力工作都很有价值,给 LDAP 功能补上了真实可用的实现 🙌 |
|
感谢 Review,已按建议修复:
请再次 Review。 |
Signed-off-by: jangrui <admin@jangrui.com>
问题分析:
- 原有测试未显式设置 ldapProperties.isEnabled() 的返回值
- 虽然 Mockito 默认返回 false,但为了测试稳定性应显式配置
- 缺少对 LDAP 回退功能的测试覆盖
修复内容:
1. 在 setUp() 中显式设置 ldapProperties.isEnabled() 返回 false
- 确保所有原有测试不受 LDAP 功能影响
- 提高测试的可读性和维护性
2. 添加三个新的测试用例:
- login_withUnknownUsername_fallsBackToLdap_whenEnabled
验证 LDAP 启用时,本地用户不存在会回退到 LDAP 认证
- login_withUnknownUsername_fails_whenLdapAuthenticationFails
验证 LDAP 认证失败时正确抛出异常
- login_withUnknownUsername_fails_whenLdapDisabled
验证 LDAP 禁用时不会调用 LDAP 服务
这些修改确保了:
- 原有测试的稳定性和可预测性
- LDAP 回退功能的正确性
- 测试覆盖的完整性
Refs: iflytek#283
Signed-off-by: jangrui <admin@jangrui.com>
基于 PR iflytek#283 的代码审查反馈,修复了多个关键问题: ## 🔴 Critical 修复 ### 1. 修复 LDAP 账号重复创建问题 **问题**: 如果 LDAP 用户的 mail 属性缺失或为空,每次登录都会创建新的 UserAccount,导致: - 同一用户产生多个账号 - 用户数据在不同会话间丢失 - 数据库中出现大量重复账号 **修复**: - 当 mail 属性为空时,使用 "ldap:{username}@internal" 作为唯一标识符 - 确保同一 LDAP 用户始终映射到同一个本地账号 - 保持用户数据的连续性 ```java // 修复前:email 为 null 时会创建新账号 String normalizedEmail = email != null ? email.toLowerCase() : null; // 修复后:使用稳定的唯一标识符 String normalizedEmail = email != null ? email.toLowerCase() : "ldap:" + username + "@internal"; ``` ## 🟠 High 优先级修复 ### 2. 修复测试代码中的 PlatformPrincipal 构造函数错误 **问题**: 测试中使用了错误的构造函数参数,导致编译错误 **修复**: 更正为正确的参数顺序: ```java // 修复前 new PlatformPrincipal(userId, displayName, email, Set.of(), Set.of(), Set.of()) // 修复后 new PlatformPrincipal(userId, displayName, email, null, "ldap", Set.of("USER")) ``` ### 3. 修复资源泄露问题 **问题**: `NamingEnumeration<SearchResult>` 没有正确关闭,导致 LDAP 连接泄露 **修复**: 在 finally 块中显式关闭 NamingEnumeration ```java finally { if (results != null) { try { results.close(); } catch (Exception e) { log.warn("Failed to close LDAP search results", e); } } closeContext(ctx); } ``` ## 📝 测试覆盖 - 添加了 3 个新测试用例验证 LDAP 回退功能 - 所有测试用例使用正确的构造函数 - 确保原有测试不受 LDAP 功能影响 ## 🔗 相关链接 - 原始 PR: iflytek#283 - 修复 PR: iflytek#437 - Issue: iflytek#260 这些修复确保了 LDAP 认证功能的稳定性和正确性。 Signed-off-by: jangrui <admin@jangrui.com>
- application.yml: 合并重复的 management.health 节点,解决 DuplicateKeyException - LocalAuthServiceTest.java: 补充 PlatformPrincipal/Set import 和类闭合括号 Signed-off-by: jangrui <admin@jangrui.com>
将 ldapProperties.isEnabled() 的默认 stub 从 setUp() 移到 login_withUnknownUsername_stillPerformsDummyPasswordCheck 中, 消除 7 个测试方法的 UnnecessaryStubbingException。 Signed-off-by: jangrui <admin@jangrui.com>
- Prevent spring-boot-starter-data-ldap AutoConfiguration side effects by setting spring.ldap.urls="" to avoid connection attempts when disabled - Add conditional LdapAutoConfiguration that only creates LdapTemplate when skillhub.ldap.enabled=true - Fix resource leaks in LdapAuthService (DirContext, NamingEnumeration) - Add safeLogHost() to prevent credential exposure in logs - Add connection timeouts (5s connect, 10s read) to prevent hangs - Add LDAP injection prevention via isValidUsername() validation Signed-off-by: jangrui <admin@jangrui.com>
XiaoSeS
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
This is not safe to merge yet. The current CI and my Java 21 merge-tree build prove that the code compiles, but they do not cover the LDAP identity and provisioning behavior introduced by this PR.
-
Blocking security issue: an LDAP login can take over an existing account by email collision. In
LdapAuthService.findOrCreateLdapUser, a successful LDAP bind followed by a matchingmailvalue directly reuses any existingUserAccount. There is no LDAP identity binding or explicit account-link/merge check. An LDAP entry with the same email as a local/OAuth account therefore receives that account and its roles. LDAP users must be keyed by a stable provider subject such asentryUUID,objectGUID, or another configured immutable identifier, using the existing identity-binding/account-merge model. Email alone must not silently link accounts. -
The requested provisioning/sync contract is incomplete. Issue #260 asks for configurable synchronized attributes. This implementation hard-codes
mail,displayName, andcn, and does not update attributes on later logins. The no-mail fallbackldap:{username}@internalis also not a stable directory identity: usernames can change and the same username can exist in different directory branches. -
There is no functional LDAP or LDAPS verification. The added tests mock
LdapAuthService;LdapAuthServiceitself has no unit or integration coverage. Before merge, please verify at least OpenLDAP login, LDAPS trust/certificate handling, first-login provisioning, repeat login and attribute synchronization, duplicate-email behavior, invalid credentials, directory unavailable, and LDAP-disabled startup. -
Directory failures are hidden and user-facing message keys are missing. Search, bind, attribute lookup, and attribute-read exceptions are broadly converted to
null/false, so bad configuration or an unavailable LDAP server is reported like “user not found” or “invalid credentials”. Also, theerror.auth.ldap.*keys used here do not exist in the message bundles. Please preserve safe diagnostic logging/exception classification and add localized messages. -
LdapTemplateis configured and injected but never used; all operations duplicate connection setup through raw JNDI. Please either use the configured client consistently or remove the dead dependency/configuration and test the chosen lifecycle.
Validation performed against current main plus PR head 9e178711:
- Java 21 full reactor compile/package: passed.
- LDAP-disabled Spring context via
LocalAuthControllerTest: 13/13 passed. - Existing
LocalAuthServiceTestLDAP fallback cases: passed, but they only exercise a mocked LDAP service.
Because this is a large authentication feature rather than a simple test fix, I recommend updating the PR title/description and addressing the identity model plus real-directory tests before another review.
维护状态说明:当前 HEAD 在此期间维护者不会改写作者提交;如作者补交修改,将基于新 HEAD 继续 Review。若届时仍无回复,维护者可通过额外的、带 DCO sign-off 的提交接管修复,并按 当前结论仍是阻塞,尚未进入 |
补充一份可执行的改进方案,便于后续修复和重新验证:
完成上述修改后,建议先合入 |
按 PR iflytek#437 review (CHANGES_REQUESTED + 改进方案) 系统修复 LDAP 登录安全与稳定性问题: 身份模型重构: - 锚定稳定目录标识 (entryUUID/objectGUID) 而非输入侧 username,复用 IdentityBinding 绑定,首次登录持久化、重复登录按 subject 命中,避免重复建号 - 禁止邮箱静默合并/继承:邮箱已被任意账号占用即抛 409 (跨 provider 与同 issuer 双 subject 均拒绝);ldap:{username}@internal 降级为纯占位,不承担身份键 - 规范化 AD objectGUID 二进制属性为稳定 GUID 字符串 (混合字节序),并补回归单测 操作属性获取: - getUserAttributes 显式请求 "*"+"+" 属性集,OpenLDAP entryUUID / AD objectGUID 等操作属性可正常返回,默认 subject 配置不再导致登录 503;目录拒绝该语法时回退默认属性集 安全与错误分类: - 属性名白名单校验防 JNDI 注入,isValidUsername 改预编译 Pattern - 区分 TLS/证书错误与目录不可用 (isTlsFailure 遍历 cause 链),新增 error.auth.ldap.tlsError 中英文消息键 - bind 成功后的目录错误由 500 折叠 401 改为透传 503;LocalAuthService 透传 403/503/409,不再误导为密码错误 - displayName fallback 与超时参数可配置化 (默认 cn / 5s+10s);修正 application.yml LDAP 配置段缩进 架构清理: - LdapAuthService 改用 JNDI 直连,移除 LdapTemplate 死依赖与失效配置;LdapAuthService 条件化、LocalAuthService 改 ObjectProvider 可选注入 测试补齐 (单测): - LdapAuthServiceTest 行为单测:邮箱碰撞拒绝、重复登录按 subject 命中不重复建号、属性更新刷新、禁用账号拒绝、subject 缺失 503、cn fallback - LdapSubjectGuidTest:objectGUID 二进制规范化回归 - LdapAuthServiceTest isTlsFailure 分类单测;LocalAuthServiceTest 错误传播单测 Signed-off-by: jangrui <admin@jangrui.com>
- OpenLDAP(Testcontainers)全链路:默认 entryUUID 首登建号绑定、重复登录不重复建号、无邮箱占位与 cn 回退、邮箱碰撞 409、错误密码 401、属性变更同步刷新、LDAPS TLS 错误分类 - 目录不可用:真实 JNDI 连接关闭端口,断言 503 directoryUnavailable;不依赖 Docker,任意环境可运行 - LDAP disabled 启动:完整上下文启动,断言 LdapAuthService Bean 缺席且本地登录降级 401 - 统一 Testcontainers 版本:junit-jupiter 移除显式 1.20.3,由 Spring Boot BOM 统一管理为 1.19.5 Signed-off-by: jangrui <admin@jangrui.com>
- 并发首登同一 LDAP subject 时,唯一约束冲突经 REQUIRES_NEW 子事务回滚后按 subject 重查命中既有账号,双方登录均成功且不重复建号 - null/空密码在 authenticateLdap 显式归类 401,避免 Hashtable NPE 导致 500 - 已绑定用户 email 变更增加碰撞检查(排除自身账号),与首登 409 规则一致 - LDAPS 支持自定义 truststore 配置(tls-trust-store*),企业自签 CA 可用 - 属性名白名单补 displayNameFallbackAttribute;LDAP 配置启动期校验(url/base/超时) - LDAP 登录成功与开始日志降为 debug - 新增并发首登集成测试(真实 OpenLDAP)、空密码 401 用例、email 刷新碰撞单测、truststore env 单测、LdapProperties 校验单测 Signed-off-by: jangrui <admin@jangrui.com>
- 显式绑定端点 POST /api/v1/auth/ldap/bind:登录用户以 LDAP 凭据证明目录身份所有权后绑定到当前账号,解决 409 邮箱冲突后无自助入口的问题;前端设置页新增 LDAP 绑定卡片 - LDAPS 自定义 truststore 真实生效:JDK 21 JNDI 不读取 javax.net.ssl.trustStore* 环境项且无 factory.socket 注入点(反编译验证),改为 EnvironmentPostProcessor 在启动早期将自定义 CA 合并进 JVM 信任库;集成测试覆盖真实 LDAPS 登录成功(证书链含 BasicConstraints + 有效期修复) - 连接层改用 Spring LDAP LdapContextSource(无 base 双重拼接、异常转换保持 JNDI 分类语义),移除自建 JNDI env 与空壳配置类 - email 并发碰撞:首登 check-and-insert 按 email 条带锁串行化,不同 subject 同 email 并发首登只建一个账号(集成测试覆盖) - 孤儿 identity_binding 自愈:绑定指向已删除账号时删除残留绑定并按首登重建,不再永久 500;loginName 随登录名变更刷新 - isTlsFailure 识别 CertPathValidatorException 链;属性配置错误使用独立消息键;'*' '+' 属性请求回退仅限协议级错误 - LdapIntegrationTest 独立 surefire fork,避免全量测试中 JSSE 默认 SSLContext 被先行缓存导致 LDAPS 用例失效 - 集成测试 ensureMember 真实化(seed global namespace),移除 MockBean Signed-off-by: jangrui <admin@jangrui.com>
- 403 与 5xx 分支优先展示后端本地化业务消息(账号禁用、目录不可用、TLS 配置错误),无消息时回退通用文案 - .gitignore 增加 .pnpm-store/ Signed-off-by: jangrui <admin@jangrui.com>
账号被删除时其身份绑定随之外键级联清理,配合 LdapAuthService 的孤儿绑定自愈,防止残留绑定阻塞目录身份重新建号。 Signed-off-by: jangrui <admin@jangrui.com>
LocalAuthService.login 移除方法级事务,LDAP 目录网络调用(最长 connect+read 超时)在无事务上下文中执行,本地凭据路径的数据库操作由 TransactionTemplate 显式包裹,避免目录慢速/不可达时放大连接池占用。 Signed-off-by: jangrui <admin@jangrui.com>
upstream main 已存在 V42__audit_log_created_at_timestamptz.sql,本 PR 的 identity_binding 级联删除迁移改用 V44(main 当前最高版本为 V43),消除 FlywayMigrationGuardrailTest 检测到的版本号冲突。 Signed-off-by: jangrui <admin@jangrui.com>
|
Heads-up on a collision: #672 (@XiaoSeS, opened 2026-07-31) also implements enterprise LDAP/AD login, so this PR and that one overlap and would conflict if both landed.
Both do OpenLDAP A maintainer should pick one lane before either merges. @dongjiang1989 — standalone (#437) or folded into the federation core (#672)? The explicit-bind endpoint here ( Flagging so these converge rather than race — not voting on the direction. |
|
维护者复核结论(保留 PR,不关闭): #437 的 LDAP 功能与已在 #672 采用的统一身份核心存在架构重叠。#437 直接在 因此当前不建议把 #437 直接合入 #437 中仍有可移植的设计/测试价值,建议保留为参考:
如果继续维护本 PR,建议不要再增加第二套账号创建逻辑,而是把上述场景改写为统一 provider adapter / identity-link contract 的测试或设计补充。当前维持 Changes Requested,等待后续统一身份方案收敛。 |
|
Thanks @XiaoSeS for the clear call — this resolves the collision I flagged. Agreed on the direction: LDAP folded into the unified identity core (#672 on @jangrui — the useful parts of this PR aren't wasted. Per XiaoSeS's note, the explicit LDAP bind/link endpoint and the edge-case tests here (directory-unavailable, TLS/cert, LDAP-disabled startup, multi-OU same-name accounts, attribute sync, error classification) are exactly the kind of coverage the unified adapter will want. The most valuable next step would be to re-target those scenarios as tests / design input against the #672 identity-link contract, rather than maintaining a second |
基于 PR #283 的 LDAP 目录登录实现,按 #437 review 要求完整实现企业级 LDAP 认证。
认证与身份:
安全与配置:
前端:
测试: