bigywater.tech
Java · 322

JNDI 资源查找:名字空间与对象绑定

JNDI 是 Java 里「按名字找资源」的统一接口,但它自己什么都不做,全靠提供者。这篇不用任何应用服务器:自己写一个提供者,用 JDK 自带的 RMI 与 LDAP 提供者连真实的服务,逐项看 bind / lookup / search 和各种异常。

3 种
本文验证的 JNDI 提供者:自写内存提供者、RMI Registry、LDAP
32 / 68 / 49
LDAP 错误码对应的 JNDI 异常:NameNotFound / NameAlreadyBound / Authentication
null
com.sun.jndi.ldap.object.trustURLCodebase 的默认读取结果(按 false 处理)

没有提供者

实测 · 本机执行输出
1 没有任何配置就 new InitialContext().lookup("java:comp/env/jdbc/x"):javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or in an application resource file: java.naming.factory.initial
  lookup("foo"):javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or in an application resource file: java.naming.factory.initial
  JNDI 相关系统属性的默认值:java.naming.factory.initial=null;java.naming.provider.url=null;java.naming.factory.url.pkgs=null
  安全相关默认值:com.sun.jndi.ldap.object.trustURLCodebase=null(未设置时按 false 处理);com.sun.jndi.rmi.object.trustURLCodebase=null;jdk.jndi.object.factoriesFilter=null;jdk.jndi.ldap.object.factoriesFilter=null

自写提供者

实测 · 本机执行输出
2 自己的 InitialContextFactory:lookup(jdbc/main) = Cfg[url=jdbc:h2:mem:x, size=5];lookup(mail/host) = smtp.example.invalid
  list():[jdbc/main→Cfg, mail/host→java.lang.String]
  重复 bind:javax.naming.NameAlreadyBoundException: mail/host
  lookup 不存在的名字:javax.naming.NameNotFoundException: nope not bound
  unbind(代理里没实现):javax.naming.OperationNotSupportedException: unbind
  Context.INITIAL_CONTEXT_FACTORY 的取值来源:显式传 env 优先于系统属性与 jndi.properties;当前 env={java.naming.factory.initial=Main$MemFactory}

RMI Registry 提供者

实测 · 本机执行输出
3 JDK 自带的 RMI Registry 提供者:javax.naming.InitialContext
  bind 一个只是 Serializable 的记录:java.lang.IllegalArgumentException: RegistryContext: object to bind must be Remote, Reference, or Referenceable
  bind 一个 javax.naming.Reference,lookup 得到:javax.naming.Reference,地址 host=db1.example.invalid(没有 ObjectFactory,所以返回的仍是 Reference)
  bind 一个导出的 Remote 对象,lookup 得到 动态代理 stub,调用 echo 返回 echo:x
  listBindings():[ref→Reference, echo→Proxy];registry.list() 直接看:[ref, echo]
  重复 bind:javax.naming.NameAlreadyBoundException: echo ← java.rmi.AlreadyBoundException: echo
  lookup 不存在:javax.naming.NameNotFoundException: none
  rename 之后 list:[echo2, ref]
  unbind(ref) 之后 registry.list():[echo2]

LDAP 提供者

实测 · 本机执行输出
  ldap ready
4 JDK 的 LDAP 提供者 com.sun.jndi.ldap.LdapCtxFactory 连接内存 LDAP 服务器(UnboundID):javax.naming.directory.InitialDirContext
  getAttributes(uid=alice,ou=people):{cn=Alice, createTimestamp=20261003162735.795Z, creatorsName=cn=Internal Root User, entryDN=uid=alice,ou=people,dc=example,dc=com, entryUUID=527503a9-d07e-4d6b-b419-ea8154fbea8e, mail=alice@example.com, modifiersName=cn=Internal Root User, modifyTimestamp=20261003162735.795Z, objectClass=inetOrgPerson, sn=A, subschemaSubentry=cn=schema, uid=alice}
  search (&(objectClass=inetOrgPerson)(cn=*a*)):[uid=alice alice@example.com]
  createSubcontext + modifyAttributes 之后 carol 的 mail:carol@new.example.com
  lookup 不存在的条目:javax.naming.NameNotFoundException: [LDAP: error code 32 - Unable to perform the search because base entry 'uid=nobody,ou=people,dc=example,dc=com' does not exist in the server.]
  重复创建:javax.naming.NameAlreadyBoundException: [LDAP: error code 68 - An entry with DN 'uid=carol,ou=people,dc=example,dc=com' already exists in the server.]
  错误密码:javax.naming.AuthenticationException: [LDAP: error code 49 - Unable to bind as user 'cn=admin,dc=example,dc=com' because the provided password was incorrect.]

分步演示

JNDI
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;LDAP 服务器用 UnboundID LDAP SDK 7.0.1 的内存目录服务器(127.0.0.1:19389)
    • 没有验证:java:comp/env 在 Tomcat / Jakarta EE 容器里的实际解析、JNDI 的 DNS 提供者(没有网络)、URL 上下文工厂(java.naming.factory.url.pkgs)的完整机制、LDAP 连接池与超时属性、JNDI 注入相关的远程类加载(只读取了默认属性,没有构造任何攻击载荷)、jdk.jndi.object.factoriesFilter 设置后的行为
    京ICP备2026067502号-1京公网安备11010502063487号