接入真实 AD 密码与直接所属组,停在待 MFA 状态 #4
@@ -68,6 +68,8 @@ java -Djavax.net.ssl.trustStore=/run/iam/truststore \
|
|||||||
|
|
||||||
这里 truststore 仅含公开信任锚,口令不是目录密码。保留所需公共根证书;不得禁用
|
这里 truststore 仅含公开信任锚,口令不是目录密码。保留所需公共根证书;不得禁用
|
||||||
LDAP endpoint identification 或用信任所有证书的 socket factory。连接/读取超时为 3/5 秒。
|
LDAP endpoint identification 或用信任所有证书的 socket factory。连接/读取超时为 3/5 秒。
|
||||||
|
AD 根范围查询可能返回 DomainDnsZones/ForestDnsZones 等分区 referral;配置为 ignore,
|
||||||
|
由 Spring AD provider 忽略 partial result,不使用 throw 打断用户查询,也不 follow 转发用户凭据。
|
||||||
|
|
||||||
浏览器入口只接受 HTTPS;明文请求返回 426。默认不信任转发头。若以后由代理终结 TLS,
|
浏览器入口只接受 HTTPS;明文请求返回 426。默认不信任转发头。若以后由代理终结 TLS,
|
||||||
必须配合仅受信代理可达的后端网络和转发头配置,不能公开一个信任任意 forwarded header
|
必须配合仅受信代理可达的后端网络和转发头配置,不能公开一个信任任意 forwarded header
|
||||||
|
|||||||
@@ -48,11 +48,13 @@ public class AdPasswordVerifier {
|
|||||||
provider.setConvertSubErrorCodesToExceptions(true);
|
provider.setConvertSubErrorCodesToExceptions(true);
|
||||||
provider.setUseAuthenticationRequestCredentials(false);
|
provider.setUseAuthenticationRequestCredentials(false);
|
||||||
provider.setSearchFilter("(&(objectClass=user)(!(objectClass=computer))(userPrincipalName={0}))");
|
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(
|
provider.setContextEnvironmentProperties(Map.of(
|
||||||
"com.sun.jndi.ldap.connect.timeout", "3000",
|
"com.sun.jndi.ldap.connect.timeout", "3000",
|
||||||
"com.sun.jndi.ldap.read.timeout", "5000",
|
"com.sun.jndi.ldap.read.timeout", "5000",
|
||||||
"java.naming.ldap.attributes.binary", "objectGUID",
|
"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.
|
// Directory groups are mapped independently; do not confuse FACTOR_PASSWORD with a group.
|
||||||
provider.setAuthoritiesPopulator((entry, username) -> List.of());
|
provider.setAuthoritiesPopulator((entry, username) -> List.of());
|
||||||
provider.setUserDetailsContextMapper(new IdentityMapper());
|
provider.setUserDetailsContextMapper(new IdentityMapper());
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import com.unboundid.ldap.listener.InMemoryDirectoryServer;
|
|||||||
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
|
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
|
||||||
import com.unboundid.ldap.listener.InMemoryListenerConfig;
|
import com.unboundid.ldap.listener.InMemoryListenerConfig;
|
||||||
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSimpleBindRequest;
|
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.listener.interceptor.InMemoryOperationInterceptor;
|
||||||
import com.unboundid.ldap.sdk.Entry;
|
import com.unboundid.ldap.sdk.Entry;
|
||||||
import com.unboundid.ldap.sdk.LDAPException;
|
import com.unboundid.ldap.sdk.LDAPException;
|
||||||
@@ -74,6 +76,16 @@ class AdIntegrationTests {
|
|||||||
config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("ldaps",
|
config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("ldaps",
|
||||||
InetAddress.getByName("127.0.0.1"), 0, ssl.getServerSocketFactory(), ssl.getSocketFactory()));
|
InetAddress.getByName("127.0.0.1"), 0, ssl.getServerSocketFactory(), ssl.getSocketFactory()));
|
||||||
config.addInMemoryOperationInterceptor(new InMemoryOperationInterceptor() {
|
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
|
@Override
|
||||||
public void processSimpleBindRequest(InMemoryInterceptedSimpleBindRequest request) throws LDAPException {
|
public void processSimpleBindRequest(InMemoryInterceptedSimpleBindRequest request) throws LDAPException {
|
||||||
String name = request.getRequest().getBindDN();
|
String name = request.getRequest().getBindDN();
|
||||||
@@ -123,7 +135,7 @@ class AdIntegrationTests {
|
|||||||
@Autowired MockMvc mvc;
|
@Autowired MockMvc mvc;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void passwordReadsGuidAndExactGroupsOverTls() {
|
void passwordReadsGuidAndExactGroupsOverTlsDespitePartitionReferrals() {
|
||||||
var identity = verifier.verify("alice", "fixture-password");
|
var identity = verifier.verify("alice", "fixture-password");
|
||||||
assertThat(identity.objectGuid()).isEqualTo("00112233-4455-6677-8899-aabbccddeeff");
|
assertThat(identity.objectGuid()).isEqualTo("00112233-4455-6677-8899-aabbccddeeff");
|
||||||
assertThat(identity.groups()).containsExactly("MixedCase", "gitea-admins");
|
assertThat(identity.groups()).containsExactly("MixedCase", "gitea-admins");
|
||||||
|
|||||||
Reference in New Issue
Block a user