Spring的SimpleNamingContextBuilder和LDAP

时间:2011-11-30 12:09:49

标签: java spring junit ldap spring-ldap

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

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

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查找。

3 个答案:

答案 0 :(得分:3)

我没有设法让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

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

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

public class DirContextBuilder implements InitialContextFactoryBuilder {

...

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

省略检查已激活== null ,您已准备好测试绑定的JNDI对象并拥有可用的LdapTemplate。

答案 1 :(得分:1)

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

@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。

答案 2 :(得分:0)

我也面临同样的问题。我研究了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;

    }

}

Pojo 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>