From f09cca32fd1f90d30c153cd6ba813185480ef6c6 Mon Sep 17 00:00:00 2001 From: panxiao81 Date: Fri, 25 Sep 2026 21:15:05 +0000 Subject: [PATCH] =?UTF-8?q?=E5=BF=BD=E7=95=A5=20Samba=20=E5=88=86=E5=8C=BA?= =?UTF-8?q?=20referral=EF=BC=8C=E9=81=BF=E5=85=8D=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E5=90=8E=E7=9A=84=E7=94=A8=E6=88=B7=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ad-login.md | 2 ++ .../ddupan/iam/login/ad/AdPasswordVerifier.java | 4 +++- .../ddupan/iam/login/ad/AdIntegrationTests.java | 14 +++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/ad-login.md b/docs/ad-login.md index e8ae5c2..079bbcd 100644 --- a/docs/ad-login.md +++ b/docs/ad-login.md @@ -68,6 +68,8 @@ java -Djavax.net.ssl.trustStore=/run/iam/truststore \ 这里 truststore 仅含公开信任锚,口令不是目录密码。保留所需公共根证书;不得禁用 LDAP endpoint identification 或用信任所有证书的 socket factory。连接/读取超时为 3/5 秒。 +AD 根范围查询可能返回 DomainDnsZones/ForestDnsZones 等分区 referral;配置为 ignore, +由 Spring AD provider 忽略 partial result,不使用 throw 打断用户查询,也不 follow 转发用户凭据。 浏览器入口只接受 HTTPS;明文请求返回 426。默认不信任转发头。若以后由代理终结 TLS, 必须配合仅受信代理可达的后端网络和转发头配置,不能公开一个信任任意 forwarded header diff --git a/src/main/java/top/ddupan/iam/login/ad/AdPasswordVerifier.java b/src/main/java/top/ddupan/iam/login/ad/AdPasswordVerifier.java index debad04..235ab76 100644 --- a/src/main/java/top/ddupan/iam/login/ad/AdPasswordVerifier.java +++ b/src/main/java/top/ddupan/iam/login/ad/AdPasswordVerifier.java @@ -48,11 +48,13 @@ public class AdPasswordVerifier { provider.setConvertSubErrorCodesToExceptions(true); provider.setUseAuthenticationRequestCredentials(false); provider.setSearchFilter("(&(objectClass=user)(!(objectClass=computer))(userPrincipalName={0}))"); + // AD domain searches include other naming-context references. Ignore them (never follow + // with user credentials); Spring's AD provider already ignores partial-result exceptions. provider.setContextEnvironmentProperties(Map.of( "com.sun.jndi.ldap.connect.timeout", "3000", "com.sun.jndi.ldap.read.timeout", "5000", "java.naming.ldap.attributes.binary", "objectGUID", - "java.naming.referral", "throw")); + "java.naming.referral", "ignore")); // Directory groups are mapped independently; do not confuse FACTOR_PASSWORD with a group. provider.setAuthoritiesPopulator((entry, username) -> List.of()); provider.setUserDetailsContextMapper(new IdentityMapper()); diff --git a/src/test/java/top/ddupan/iam/login/ad/AdIntegrationTests.java b/src/test/java/top/ddupan/iam/login/ad/AdIntegrationTests.java index 948bbe3..419b63f 100644 --- a/src/test/java/top/ddupan/iam/login/ad/AdIntegrationTests.java +++ b/src/test/java/top/ddupan/iam/login/ad/AdIntegrationTests.java @@ -4,6 +4,8 @@ import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSimpleBindRequest; +import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; +import com.unboundid.ldap.sdk.SearchResultReference; import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; @@ -74,6 +76,16 @@ class AdIntegrationTests { config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("ldaps", InetAddress.getByName("127.0.0.1"), 0, ssl.getServerSocketFactory(), ssl.getSocketFactory())); config.addInMemoryOperationInterceptor(new InMemoryOperationInterceptor() { + @Override + public void processSearchResult(InMemoryInterceptedSearchResult result) { + try { + // Like Samba AD's DomainDnsZones/ForestDnsZones continuation references. + // A client following this reference would fail instead of returning the user. + result.sendSearchReference(new SearchResultReference( + new String[]{"ldap://127.0.0.1:1/DC=other,DC=test"}, null)); + } catch (LDAPException ex) { throw new IllegalStateException(ex); } + } + @Override public void processSimpleBindRequest(InMemoryInterceptedSimpleBindRequest request) throws LDAPException { String name = request.getRequest().getBindDN(); @@ -123,7 +135,7 @@ class AdIntegrationTests { @Autowired MockMvc mvc; @Test - void passwordReadsGuidAndExactGroupsOverTls() { + void passwordReadsGuidAndExactGroupsOverTlsDespitePartitionReferrals() { var identity = verifier.verify("alice", "fixture-password"); assertThat(identity.objectGuid()).isEqualTo("00112233-4455-6677-8899-aabbccddeeff"); assertThat(identity.groups()).containsExactly("MixedCase", "gitea-admins");