【问题标题】:Spring's SimpleNamingContextBuilder and LDAPSpring 的 SimpleNamingContextBuilder 和 LDAP
【发布时间】:2019-01-16 00:03:32
【问题描述】:

我目前正在尝试为我们现有的 Web 应用程序开发一个新模块。我正在尝试添加 LDAP 功能并且在初始化 LDAP 上下文时遇到问题,因为 SimpleNamingContextBuilder 注册了一个不与LdapTemplate 一起工作的上下文。

在我们的 spring applicationContext.xml 中,我们有几个 JNDI 查找,因此在运行测试用例之前,我必须在测试用例构造函数中使用 SimpleNamingContextBuilder 创建模拟 JNDI-Resources。

SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("someJNDIname",someObject); //e.g. for some datasource
builder.activate();

在我们的 Spring application-context-test.xml 中,我们有以下 ldapConfiguration:

<bean id="ldapContextSource" class="org.springframework.ldap.core.support.LdapContextSource">
    <property name="url" value="ldap://ourserver:389" />
    <property name="base" value="CN=Groups,CN=ourcompany,DC=com" />
    <property name="userDn" value="CN=binduser" />
    <property name="password" value="password" />
</bean>

<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
    <constructor-arg ref="ldapContextSource" />
</bean>

我们运行测试用例:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application-context-test.xml"})
public class TestClass {
    public TestClass(){
       .. //init the SimpleNamingContextBuilder
    }

    @Autowired
    private LdapTemplate template;

    @Test
    public void someTestcase(){
        ldapTemplate.search("", "(objectclass=user)" ,new LdapUserMapper());
    }
 }

由于 SimpleNamingContextBuilder 已经注册了一个简单的 InitialContext,我收到以下错误:

org.springframework.ldap.NotContextException: DirContext object is required.; nested exception is javax.naming.NotContextException: DirContext object is required.
  at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:198)
  at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:319)
  at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:259)
  at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:571)
  at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:556)
  at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:411)
  at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:431)
  at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:451)
  at  com.somecompany.TestClass.someTestcase(TestClass.java:30)
      [...]
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: javax.naming.NotContextException: DirContext object is required.
  at javax.naming.directory.InitialDirContext.castToDirContext(InitialDirContext.java:106)
  at javax.naming.directory.InitialDirContext.getURLOrDefaultInitDirCtx(InitialDirContext.java:112)
  at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:245)
  at org.springframework.ldap.core.LdapTemplate$4.executeSearch(LdapTemplate.java:253)
  at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:293)
  ... 35 more

错误告诉我 LDAP 需要 DirContext。如何让 SimpleNamingContextBuilder 创建和使用这样的DirContext

如果我不注册 SimpleNamingContextBuilder,那么创建 LDAPTemplate 将起作用。但是我会遇到其他问题,因为应用程序的其他部分需要 JNDI 查找。

【问题讨论】:

    标签: java spring junit ldap spring-ldap


    【解决方案1】:

    我没有设法让 SimpleNamingContextBuilder 创建 DirContext 的实例,但使用自定义 DirContextBuilder 是解决此限制的解决方案。

    模拟的 SimpleNamingContext 主要是通过例如提供绑定对象

    InitialContext.doLookup(String name)
    

    方法 - 让这些方法工作并为例如提供适当的支持。 LDAP DirContext 实例,只需省略对“已激活”上下文的检查 - 您将引导您的代码以应用 activate() 无论如何,所以这没问题 - 至少对于给定的 JNDI + LDAP 测试用例来说不是。

    如果缺少此检查,代码会查找“java.naming.factory.initial”环境键以及环境是否为空(InitialContext.doLookup(String name) 就是这种情况)您将使用绑定的对象获得模拟的 SimpleNamingContext。

    如果使用 LdapTemplate,环境不为空,并且键“java.naming.factory.initial”设置为“com.sun.jndi.ldap.LdapCtxFactory" 或类似的东西,至少(希望)是一个 DirContext。

    在这种情况下,您会从 createInitialContextFactory 调用返回一个正常工作的 DirContext 实例,并且 LdapTemplate 查找成功。

    所以只需创建一个类 DirContextBuilder - 从 SimpleNamingContextBuilder 中获取代码 - 像这样:

    public class DirContextBuilder implements InitialContextFactoryBuilder {
    
    ...
    
    public InitialContextFactory createInitialContextFactory(Hashtable<?,?> environment) {
        if (environment != null) {
    ...
    }
    

    省略对 activated == null 的检查,您就可以测试绑定的 JNDI 对象并拥有一个工作的 LdapTemplate。

    【讨论】:

    • 这并不能解决混合问题。我想在同一上下文中为数据库初始化 JNDI 数据源。
    • 如果您需要在相同的上下文中,这也应该可以使用自定义 DirContextBuilder - 只需创建您自己的 DirContext 实现,或使用例如JBoss 之一(org.jnp.interfaces.NamingContextFactory)。
    • 我需要在同一个上下文中,你能告诉我如何做到这一点吗?真的很感激!谢谢
    【解决方案2】:

    我遇到了同样的问题。但是用下面的技巧克服它

    @BeforeClass
    public static void setUp(){
        OracleDataSource ods = null;
        try {
            ods= new OracleDataSource();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        ods.setURL("jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=....;
        ods.setUser(..);
        ods.setPassword(..);        
    
        SimpleNamingContextBuilder builder = null;
        try {
            builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
            builder.bind("some/name", ods);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
    
    @Before
    public void beforeTest(){
          SimpleNamingContextBuilder.getCurrentContextBuilder().deactivate();
    }
    
    @Test
    public void yourTest(){
        .....
    }
    

    这会将您的数据库与 some/name 绑定,并让您正确连接到 ldap。

    【讨论】:

    • @ContextConfiguration 配置需要jdbc 数据源的jndi 的spring 上下文之前,我使用@BeforeClass 注册jndi。但是我需要添加builder.activate() 才能使其正常工作。感谢这个技巧 +1 :)
    【解决方案3】:

    我也遇到了同样的问题。我研究了JavaSpringLdap 的原因和内部行为,为什么会发生这种情况。我做出了以下决定。

    为了解决这个问题,我自定义了 ContextSource bean 创建。这种方法是拐杖,需要修改配置以检查测试模式。但它有效。 下面我介绍一个简单的项目来演示它。对于嵌入式 LDAP 服务器,我使用了 Apache Directory Server

    CommonConfig.java 就是这个拐杖:

    package com.stackoverflow.question8325740.config;
    
    
    import com.stackoverflow.question8325740.JndiExplorer;
    import com.stackoverflow.question8325740.LdapSettings;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.ldap.core.ContextSource;
    import org.springframework.ldap.core.LdapOperations;
    import org.springframework.ldap.core.LdapTemplate;
    import org.springframework.ldap.core.support.AbstractContextSource;
    import org.springframework.ldap.core.support.LdapContextSource;
    
    import javax.naming.Context;
    import javax.naming.NamingException;
    import javax.naming.NoInitialContextException;
    import javax.naming.directory.DirContext;
    import javax.naming.ldap.Control;
    import javax.naming.ldap.InitialLdapContext;
    import javax.naming.spi.InitialContextFactory;
    import javax.naming.spi.NamingManager;
    import java.util.HashMap;
    import java.util.Hashtable;
    import java.util.Map;
    
    @Configuration
    public class CommonConfig {
    
        private static class CustomLdapContextSource extends AbstractContextSource {
            @Override
            protected DirContext getDirContextInstance(Hashtable<String, Object> environment) throws NamingException {
                return new CustomLdapContext(environment, null);
            }
        }
    
        private static class CustomLdapContext extends InitialLdapContext {
            public CustomLdapContext() throws NamingException {
            }
    
            public CustomLdapContext(Hashtable<?, ?> environment, Control[] connCtls) throws NamingException {
                super(environment, connCtls);
            }
    
            @Override
            protected Context getDefaultInitCtx() throws NamingException {
                String className = "com.sun.jndi.ldap.LdapCtxFactory";
                InitialContextFactory factory;
                try {
                    factory = (InitialContextFactory) Class.forName(className).newInstance();
                } catch (Exception e) {
                    NoInitialContextException ne =
                            new NoInitialContextException(
                                    "Cannot instantiate class: " + className);
                    ne.setRootCause(e);
                    throw ne;
                }
                return factory.getInitialContext(myProps);
            }
    
    
        }
    
        private static boolean checkTestMode() {
            //checking test mode using reflection in order to not collapse in real execution
            try {
                Class clazz = Class.forName("org.springframework.mock.jndi.SimpleNamingContextBuilder");
                Object result = clazz.getMethod("getCurrentContextBuilder").invoke(null);
                return NamingManager.hasInitialContextFactoryBuilder() && result != null;
            } catch (Exception e) {
                return false;
            }
    
    
        }
    
        @Bean
        @Autowired
        public ContextSource ldapContextSource(LdapSettings ldapSettings) {
            AbstractContextSource contextSource;
            if (checkTestMode()) {
                contextSource = new CustomLdapContextSource();
            } else {
                contextSource = new LdapContextSource();
            }
            contextSource.setUrl(ldapSettings.getUrl());
            contextSource.setUserDn(ldapSettings.getLogin());
            contextSource.setPassword(ldapSettings.getPassword());
            contextSource.setPooled(true);
            contextSource.setAnonymousReadOnly(false);
    
            Map<String, Object> baseEnvironmentProperties = new HashMap<String, Object>();
            baseEnvironmentProperties.put(Context.SECURITY_AUTHENTICATION, "simple");
            baseEnvironmentProperties.put(Context.REFERRAL, "follow");
            contextSource.setBaseEnvironmentProperties(baseEnvironmentProperties);
            return contextSource;
        }
    
        @Bean
        @Autowired
        public LdapOperations ldapTemplate(ContextSource ldapContextSource) {
            return new LdapTemplate(ldapContextSource);
        }
    
        @Bean
        public JndiExplorer jndiExplorer() {
            return new JndiExplorer();
        }
    
    }
    

    MainTest.java 使用JNDILdapOperations

    package com.stackoverflow.question8325740;
    
    import com.stackoverflow.question8325740.config.CommonConfig;
    import org.junit.jupiter.api.AfterAll;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.BeforeAll;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.ldap.core.AttributesMapper;
    import org.springframework.ldap.core.LdapOperations;
    import org.springframework.ldap.query.LdapQueryBuilder;
    import org.springframework.ldap.test.LdapTestUtils;
    import org.springframework.mock.jndi.SimpleNamingContextBuilder;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit.jupiter.SpringExtension;
    
    import javax.naming.NamingException;
    import javax.naming.directory.Attributes;
    import javax.naming.directory.BasicAttributes;
    import java.util.List;
    
    
    @ExtendWith(SpringExtension.class)
    @ContextConfiguration(classes = {ApacheDsEmbededConfiguration.class, CommonConfig.class})
    public class MainTest {
        public static final String TEST_VALUE = "testValue";
    
    
        private static SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
    
        @BeforeAll
        public static void setUp() throws Exception {
            builder.bind(JndiExplorer.JNDI_TEST, TEST_VALUE);
            builder.activate();
            LdapTestUtils.startEmbeddedServer(ApacheDsEmbededConfiguration.PORT, ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX, "test");
            Thread.sleep(1000);
        }
    
        @AfterAll
        public static void shutdown() throws Exception {
            LdapTestUtils.shutdownEmbeddedServer();
            builder.deactivate();
        }
    
    
        @Autowired
        private JndiExplorer jndiExplorer;
    
        @Autowired
        private LdapOperations ldapOperations;
    
        @Test
        public void testLdapTemplateWithSimpleJndi() {
            Assertions.assertEquals(TEST_VALUE, jndiExplorer.getValue());
            String cn = "testCN";
            String sn = "testSN";
            Attributes attrs = new BasicAttributes();
            attrs.put("objectClass", "inetOrgPerson");
            attrs.put("cn", cn);
            attrs.put("sn", sn);
            ldapOperations.bind("cn=" + cn + "," + ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX, null, attrs);
    
            AttributesMapper<String> mapper = new AttributesMapper<String>() {
                @Override
                public String mapFromAttributes(Attributes attributes) throws NamingException {
                    return (String) attributes.get("sn").get();
                }
            };
            List<String> sns = ldapOperations.search(LdapQueryBuilder.query().base(ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX).attributes("*").where("sn").is(sn), mapper);
    
            Assertions.assertEquals(1, sns.size());
            String resultSn = sns.get(0);
            Assertions.assertEquals(sn, resultSn);
    
        }
    
    }
    

    ApacheDsEmbededConfiguration.java:

    package com.stackoverflow.question8325740;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ApacheDsEmbededConfiguration {
    
        //default password
        static final String PASSWORD = "secret";
        //default admin DN
        static final String PRINCIPAL = "uid=admin,ou=system";
    
        static final String DEFAULT_PARTITION_SUFFIX = "dc=stackoverflow,dc=com";
        static final int PORT = 1888;
    
        @Bean
        public LdapSettings ldapSettings() {
            LdapSettings settings = new LdapSettings();
            settings.setUrl("ldap://localhost:" + PORT);
            settings.setLogin(PRINCIPAL);
            settings.setPassword(PASSWORD);
            return settings;
    
        }
    
    }
    

    波乔LdapSettings.java:

    package com.stackoverflow.question8325740;
    
    
    public class LdapSettings {
        private String url;
        private String login;
        private String password;
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getLogin() {
            return login;
        }
    
        public void setLogin(String login) {
            this.login = login;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    

    使用 JNDI 变量 JndiExplorer.java 的 Bean:

    package com.stackoverflow.question8325740;
    
    import javax.annotation.Resource;
    
    public class JndiExplorer {
        public static final String JNDI_TEST = "com/anything";
    
        @Resource(mappedName = JNDI_TEST)
        private String value;
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    }
    

    还有pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.stackoverflow</groupId>
        <artifactId>question-8325740</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <junit.version>5.3.2</junit.version>
            <spring.version>5.1.4.RELEASE</spring.version>
            <spring.ldap.version>2.3.2.RELEASE</spring.ldap.version>
            <apacheDirectoryService.version>1.5.5</apacheDirectoryService.version>
            <apacheDirectoryService.shared.version>0.9.15</apacheDirectoryService.shared.version>
        </properties>
        <dependencies>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.ldap</groupId>
                <artifactId>spring-ldap-core</artifactId>
                <version>${spring.ldap.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>${spring.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-engine</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.ldap</groupId>
                <artifactId>spring-ldap-test</artifactId>
                <version>${spring.ldap.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.directory.server</groupId>
                <artifactId>apacheds-core</artifactId>
                <version>${apacheDirectoryService.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.directory.server</groupId>
                <artifactId>apacheds-core-entry</artifactId>
                <version>${apacheDirectoryService.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.directory.server</groupId>
                <artifactId>apacheds-protocol-shared</artifactId>
                <version>${apacheDirectoryService.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.directory.server</groupId>
                <artifactId>apacheds-protocol-ldap</artifactId>
                <version>${apacheDirectoryService.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.directory.server</groupId>
                <artifactId>apacheds-server-jndi</artifactId>
                <version>${apacheDirectoryService.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.directory.shared</groupId>
                <artifactId>shared-ldap</artifactId>
                <version>${apacheDirectoryService.shared.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
       <build>
          <plugins>
            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <version>3.0.0-M3</version>
             </plugin>
          </plugins>
        </build>
    </project>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-10-20
      • 2018-11-03
      • 1970-01-01
      • 2011-09-22
      • 2015-10-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多