Skip to content

JNDI 详解

1. 概述

1.1 什么是JNDI

JNDI(Java Naming and Directory Interface,Java命名和目录接口)是Java平台提供的API,用于访问各种命名和目录服务。它提供了一种统一的方式来查找和访问分布式系统中的各种资源,如数据库连接、消息队列、EJB等。

1.2 JNDI的主要用途

  • 资源查找与获取:通过名称查找分布式系统中的资源
  • 服务注册与发现:允许组件注册自己并被其他组件发现
  • 配置外部化:将系统配置从代码中分离,提高灵活性
  • 分布式协作:促进分布式系统中不同组件的协作

1.3 JNDI与其他技术的关系

  • JNDI与JDBC:JNDI可用于管理数据库连接池
  • JNDI与EJB:EJB容器使用JNDI注册和查找企业Bean
  • JNDI与Spring:Spring可以通过JNDI查找外部资源

2. JNDI核心概念

2.1 命名服务(Naming Service)

命名服务维护名称和对象之间的映射关系,允许客户端通过名称查找对象。常见的命名服务包括DNS、文件系统等。

2.2 目录服务(Directory Service)

目录服务是命名服务的扩展,不仅提供名称到对象的映射,还允许对象拥有属性,并且可以通过属性进行搜索。常见的目录服务包括LDAP。

2.3 绑定(Binding)

绑定是名称与对象之间的关联关系。在JNDI中,我们可以将对象绑定到某个名称,然后通过该名称查找该对象。

2.4 上下文(Context)

上下文是一组名称绑定的集合,类似于文件系统中的目录。每个上下文都提供了查找、绑定、解绑和重命名对象的方法。

2.5 引用(Reference)

引用是一种特殊对象,包含了如何构造实际对象的信息。当通过JNDI查找引用时,JNDI服务提供者会使用引用中的信息构造并返回实际对象。

3. JNDI API详解

3.1 核心接口

Context接口

Context是JNDI API中最基本的接口,代表命名上下文,提供了基本的命名操作:

java
public interface Context {
    // 查找对象
    Object lookup(String name) throws NamingException;
    
    // 绑定对象
    void bind(String name, Object obj) throws NamingException;
    
    // 解绑对象
    void unbind(String name) throws NamingException;
    
    // 重命名对象
    void rename(String oldName, String newName) throws NamingException;
    
    // 列出上下文中的名称
    NamingEnumeration<NameClassPair> list(String name) throws NamingException;
    
    // 关闭上下文
    void close() throws NamingException;
    
    // 其他方法...
}

DirContext接口

DirContext扩展了Context接口,提供了目录服务的功能:

java
public interface DirContext extends Context {
    // 通过属性搜索对象
    NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes) throws NamingException;
    
    // 获取对象的属性
    Attributes getAttributes(String name) throws NamingException;
    
    // 修改对象的属性
    void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException;
    
    // 其他方法...
}

3.2 常用类

InitialContext

InitialContext是JNDI的入口点,代表初始命名上下文:

java
public class InitialContext implements Context {
    // 构造方法
    public InitialContext() throws NamingException {}
    public InitialContext(Hashtable<?,?> environment) throws NamingException {}
    
    // 实现Context接口的方法...
}

NamingException

NamingException是JNDI操作中可能抛出的异常的基类:

java
public class NamingException extends Exception {
    // 构造方法
    public NamingException() {}
    public NamingException(String explanation) {}
    
    // 获取根异常
    public Throwable getRootCause() {}
    
    // 设置根异常
    public void setRootCause(Throwable e) {}
    
    // 其他方法...
}

4. JNDI的使用方法

4.1 基本操作示例

创建InitialContext

java
// 创建初始上下文
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:/tmp/jndi");

try (Context ctx = new InitialContext(env)) {
    // 执行JNDI操作
    // ...
} catch (NamingException e) {
    e.printStackTrace();
}

绑定和查找对象

java
// 绑定对象
ctx.bind("java:comp/env/myObject", myObject);

// 查找对象
Object retrievedObject = ctx.lookup("java:comp/env/myObject");

4.2 访问数据库连接池

在Java EE环境中,通常使用JNDI来获取数据库连接:

java
// 查找数据源
DataSource dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/myDB");

// 获取数据库连接
try (Connection conn = dataSource.getConnection()) {
    // 使用连接执行SQL操作
    // ...
} catch (SQLException e) {
    e.printStackTrace();
}

4.3 在Web容器中配置JNDI资源

Tomcat配置示例

在Tomcat的context.xmlserver.xml中配置JNDI资源:

xml
<Resource name="jdbc/myDB"
          auth="Container"
          type="javax.sql.DataSource"
          username="dbuser"
          password="dbpass"
          driverClassName="com.mysql.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/mydb"
          maxActive="100"
          maxIdle="30"
          maxWait="10000" />

然后在Web应用的web.xml中引用该资源:

xml
<resource-ref>
    <description>My Database Connection Pool</description>
    <res-ref-name>jdbc/myDB</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>

5. JNDI在Spring中的应用

5.1 Spring中查找JNDI资源

在Spring应用中,可以使用JndiObjectFactoryBeanjee:jndi-lookup来查找JNDI资源:

XML配置方式

xml
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/myDB" />
    <property name="resourceRef" value="true" />
</bean>

<!-- 或者使用jee命名空间 -->
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/myDB" />

Java配置方式

java
@Configuration
public class AppConfig {
    
    @Bean
    public DataSource dataSource() throws NamingException {
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("java:comp/env/jdbc/myDB");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.afterPropertiesSet();
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
}

5.2 Spring Boot中的JNDI支持

在Spring Boot中,可以通过以下方式配置JNDI:

application.properties配置

properties
# 启用JNDI
spring.datasource.jndi-name=java:comp/env/jdbc/myDB

Java配置

java
@Configuration
public class JndiConfig {
    
    @Bean
    public DataSource dataSource() throws NamingException {
        return (DataSource) new JndiTemplate().lookup("java:comp/env/jdbc/myDB");
    }
}

6. JNDI安全考虑

6.1 JNDI注入漏洞

JNDI注入是一种严重的安全漏洞,攻击者可以通过操纵JNDI查找过程来执行恶意代码。主要风险点包括:

  • 远程类加载:通过引用指向恶意类
  • 命名引用注入:在查找过程中注入恶意引用

6.2 防护措施

为了防止JNDI注入攻击,可以采取以下措施:

  • 在Java 8u121+、Java 7u131+、Java 6u141+中,可以设置系统属性禁用远程类加载:
    -Dcom.sun.jndi.rmi.object.trustURLCodebase=false
    -Dcom.sun.jndi.cosnaming.object.trustURLCodebase=false
  • 限制JNDI提供者只能访问受信任的服务器
  • 对用户输入进行严格验证和过滤
  • 使用最新版本的JDK,及时应用安全补丁

7. 常见问题与解决方案

7.1 找不到InitialContextFactory

问题:抛出NoInitialContextException: Cannot instantiate class: com.sun.jndi.fscontext.RefFSContextFactory

解决方案

  • 确保类路径中包含了相应的JNDI提供者JAR文件
  • 检查InitialContextFactory的类名是否正确

7.2 连接超时或无法连接到服务

问题:抛出CommunicationException或连接超时异常

解决方案

  • 检查网络连接和防火墙设置
  • 验证JNDI服务的URL和端口是否正确
  • 确认JNDI服务是否正在运行

7.3 权限不足

问题:抛出AuthenticationException或权限相关异常

解决方案

  • 检查认证凭据是否正确
  • 确认当前用户是否有足够的权限执行操作

8. 总结

JNDI是Java平台中访问命名和目录服务的标准接口,它提供了一种统一的方式来查找和访问分布式系统中的各种资源。通过JNDI,我们可以实现资源的集中管理、配置的外部化以及组件之间的松耦合。在使用JNDI时,我们需要注意安全问题,特别是JNDI注入漏洞,采取适当的防护措施。在现代Java应用中,虽然有些场景下JNDI的使用有所减少,但在企业级应用和Java EE环境中,JNDI仍然是一个重要的技术组件。

基于 MIT 许可发布