Tomca教程
Tomcat Manager
Tomcat Realm 配置
Tomcat 安全管理
Tomcat JNDI 资源
Tomcat JDBC 数据源
Tomcat 类加载机制
Tomcat JSPs
Tomcat SSL/TLS配置
Tomcat SSI
Tomcat CGI
Tomcat 代理支持
Tomcat MBean 描述符
Tomcat 默认 Servlet
Tomcat 集群
Tomcat 连接器
Tomcat监控与管理
Tomcat 日志机制
Tomcat 基于 APR 的原生库
Tomcat 虚拟主机
Tomcat 高级 IO 机制
Tomcat 附加组件
Tomcat 安全性注意事项
Tomcat Windows 服务
Tomcat Windows 认证
Tomcat 的 JDBC 连接池
Tomcat WebSocket 支持
Tomcat 重写机制

JNDIRealm

JNDIRealm 是 Tomcat Realm 接口的一种实现,通过一个 JNDI 提供者1 LDAP 目录服务器中查找用户。realm 支持大量的方法来使用认证目录。

通常是可以使用 JNDI API 类的标准 LDAP 提供者。

 1.连接目录

realm 与目录服务器的连接是通过 connectionURL 配置属性来定义的。这个 URL 的格式是通过 JNDI 提供者来定义的。它通常是个 LDAP URL,指定了所要连接的目录服务器的域名,另外还(可选择)指定了所需的根命名上下文的端口和唯一名称(DN)。

如果有多个提供者,则可以配置 alternateURL。如果一个套接字连接无法传递给提供者 connectionURL,则会换用 alternateURL

当通过创建连接来搜索目录,获取用户及角色信息时,realm 会利用 connectionName 和 connectionPassword 这两个属性所指定的用户名和密码在目录上进行自我认证。如果未指定这两个属性,则创立的连接是匿名连接,这种连接适用于大多数情况。

2.选择用户目录项

在目录中,每个可被认证的用户都必须表示为独立的项,这种独立项对应着由属性 connectionURL 定义的初始 DirContext 中的元素。这种用户项必须有一个包含认证所需用户名的属性。

每个用户项的唯一性名称(DN)通常含有用于认证的用户名。在这种情况下,userPattern 属性可以用来指定 DN,其中的 {0} 代表用户名应该被替换的位置。

realm 必须搜索目录来寻找一个包含用户名的唯一项,可用下列属性来配置搜索:

  • userBase 用户子树的基准项。如果未指定,则搜索基准为顶级元素。
  • userSubtree 用户子树,也就是搜索范围。如果希望搜索以userBase 项为基准的整个子树,则使用 true;默认值为 false,只对顶级元素进行搜索。
  • userSearch 指定替代用户名之后所使用的 LDAP 搜索过滤器的模式。
3. 用户认证
  • 绑定模式

默认情况下,realm 会利用用户项的 DN 与用户所提供的密码,将用户绑定到目录上。如果成功执行了这种简单的绑定,那么就可以认为用户认证成功。

出于安全考虑,目录可能保存的是用户的摘要式密码,而非明文密码(参看摘要式密码以获知详情)。在这种情况下,在绑定过程中,目录会自动将用户所提供的明文密码加密为正确的摘要式密码,以便后续和存储的摘要式密码进行比对。然而在绑定过程中,realm 并不参与处理摘要式密码。不会用到 digest 属性,如果设置了该属性,也会被自动忽略。

  • 对比模式

另外一种方法是,realm 从目录中获取存储的密码,然后将其与用户所提供的值进行比对。配置方法是,在包含密码的用户项中,将 userPassword 属性设为目录属性名。

对比模式的缺点在于:首先,connectionName 和 connectionPassword 属性必须配置成允许 realm 读取目录中的用户密码。出于安全考虑,这是一种不可取的做法。事实上,很多目录实现甚至都不允许目录管理器读取密码。其次,realm 必须自己处理摘要式密码,包括要设置所使用的具体算法、在目录中表示密码散列值的方式。但是,realm 可能有时又要访问存储的密码,比如为了支持 HTTP 摘要式访问认证(HTTP Digest Access Authentication,RFC 2069)。(注意,HTTP 摘要式访问认证不同于之前讨论过的在库中存储密码摘要的方式。)

4. 赋予用户角色

Realm 支持两种方法来表示目录中的角色:

  • 将角色显式表示为目录项

通过明确的目录项来表示角色。角色项通常是一个 LDAP 分组项,该分组项的一个属性包含角色名称,另一属性值则是拥有该角色的用户的 DN 名或用户名。下列属性配置了一个目录搜索来寻找与认证用户相关的角色名。

  • roleBase 角色搜索的基准项。如未指定,则基准项为顶级目录上下文。
  • roleSubtree 搜索范围。如果希望搜索以 roleBase 为基准项的整个子树,则设为 true。默认值为 false,请求一个只包含顶级元素的单一搜索。
  • roleSearch 用于选择角色项的 LDAP 搜索过滤器。它还可以(可选择)包含用于唯一名称的模式替换 {0}、用户名的模式替换 {1},以及用户目录项属性的模式替换 {2}。使用 userRoleAttribute 来指定提供 {2} 值的属性名。
  • roleName 包含角色名称的角色项属性。
  • roleNested 启用内嵌角色。如果希望在角色中内嵌角色,则设为 true。如果配置了该属性,每一个新近找到的 roleName 和 DN 都将用于递归式的新角色搜索。默认值为 false
  • 将角色表示为用户项属性

将角色名称保存为用户目录项中的一个属性值。使用 userRoleName 来指定该属性名称。当然,也可以综合使用这两种方法来表示角色。

快速入门

为了配置 Tomcat 使用 JNDIRealm,需要下列步骤:

  • 确保目录服务器配置中的模式符合上文所列出的要求。
  • 必要时可以为 Tomcat 配置一个用户名和密码,对上文所提到的信息用于只读访问权限(Tomcat 永远不会修改该信息。)
  • 按照下文的方法,在 $CATALINA_BASE/conf/server.xml 文件中设置一个  元素。
  • 如果 Tomcat 已经运行,则重启它。

Realm 元素属性

如上所述,为了配置 JDBCRealm,需要创建一个 Realm 元素,并把它放在 $CATALINA_BASE/conf/server.xml 文件中。JDBCRealm 的属性都定义在 Realm 配置文档中。

范例

在目录服务器上创建适合的模式超出了本文档的讲解范围,因为这是跟每个目录服务器的实现密切相关的。在下面的实例中,我们将假定使用的是 OpenLDAP 目录服务器的一个分发版(2.0.11 版或更新版本,可从http://www.openldap.org处下载)。假设 slapd.conf 文件包含下列设置(除了其他设置之外)。

database ldbm
suffix dc="mycompany",dc="com"
rootdn "cn=Manager,dc=mycompany,dc=com"
rootpw secret

我们还假定 connectionURL,使目录服务器与 Tomcat 运行在同一台机器上。要想了解如何配置及使用 JNDI LDAP 提供者的详细信息,请参看 http://docs.oracle.com/javase/7/docs/technotes/guides/jndi/index.html

接下来,假定利用如下所示的元素(以 LDIF 格式)来填充目录服务器。

# Define top-level entry
dn: dc=mycompany,dc=com
objectClass: dcObject
dc:mycompany
# Define an entry to contain people# searches for users are based on this entry
dn: ou=people,dc=mycompany,dc=com
objectClass: organizationalUnit
ou: people
# Define a user entry for Janet Jones
dn: uid=jjones,ou=people,dc=mycompany,dc=com
objectClass: inetOrgPerson
uid: jjones
sn: jones
cn: janet jones
mail: j.jones@mycompany.com
userPassword: janet
# Define a user entry for Fred Bloggs
dn: uid=fbloggs,ou=people,dc=mycompany,dc=com
objectClass: inetOrgPerson
uid: fbloggs
sn: bloggs
cn: fred bloggs
mail: f.bloggs@mycompany.com
userPassword: fred
# Define an entry to contain LDAP groups# searches for roles are based on this entry
dn: ou=groups,dc=mycompany,dc=com
objectClass: organizationalUnit
ou: groups
# Define an entry for the "tomcat" role
dn: cn=tomcat,ou=groups,dc=mycompany,dc=com
objectClass: groupOfUniqueNames
cn: tomcat
uniqueMember: uid=jjones,ou=people,dc=mycompany,dc=com
uniqueMember: uid=fbloggs,ou=people,dc=mycompany,dc=com
# Define an entry for the "role1" role
dn: cn=role1,ou=groups,dc=mycompany,dc=com
objectClass: groupOfUniqueNames
cn: role1
uniqueMember: uid=fbloggs,ou=people,dc=mycompany,dc=com

OpenLDAP 服务器。假定用户使用他们的 uid(比如说 jjones)登录应用,匿名连接已经足够可以搜索目录并获取角色信息了:

.apache.catalina.realm.JNDIRealm"
     connectionURL="ldap://localhost:389"
       userPattern="uid={0},ou=people,dc=mycompany,dc=com"
          roleBase="ou=groups,dc=mycompany,dc=com"
          roleName="cn"
        roleSearch="(uniqueMember={0})"
/>

利用这种配置,通过在 userPattern 替换用户名,realm 能够确定用户的 DN,然后利用这个 DN 和取自用户的密码将用户绑定到目录中,从而验证用户身份,然后搜索整个目录服务器来找寻用户角色。

现在假定希望用户输入电子邮件地址(而不是用户 id)。在这种情况下,realm 必须搜索目录找到用户项。当用户项被保存在多个子树中,而这些子树可能分别对应不同的组织单位或企业位置时,可能必须执行一个搜索。

另外,假设除了分组项之外,你还想用用户项的属性来保存角色,那么在这种情况下,Janet Jones 对应的项可能如下所示:

dn: uid=jjones,ou=people,dc=mycompany,dc=comobjectClass: inetOrgPersonuid: jjonessn: jonescn: janet jonesmail: j.jones@mycompany.commemberOf: role2memberOf: role3userPassword: janet

这个 realm 配置必须满足以下新要求:

<Realm   className="org.apache.catalina.realm.JNDIRealm"
     connectionURL="ldap://localhost:389"
          userBase="ou=people,dc=mycompany,dc=com"
        userSearch="(mail={0})"
      userRoleName="memberOf"
          roleBase="ou=groups,dc=mycompany,dc=com"
          roleName="cn"
        roleSearch="(uniqueMember={0})"
/>

Janet Jones 用她的电子邮件 j.jones@mycompany.com 登录时,realm 会搜索目录,寻找带有该电邮值的唯一项,并尝试利用给定密码来绑定到目录:uid=jjones,ou=people,dc=mycompany,dc=com。如果验证成功,该用户将被赋予以下三个角色:"role2" 与 "role3",她的目录项中的 memberOf 属性值;"tomcat",她作为成员存在的唯一分组项中的 cn 属性值。

最后,为了验证用户,我们必须从目录中获取密码并在 realm 中执行本地比对,将 realm 按照如下方式来配置:

<Realm   className="org.apache.catalina.realm.JNDIRealm"
    connectionName="cn=Manager,dc=mycompany,dc=com"connectionPassword="secret"
     connectionURL="ldap://localhost:389"
      userPassword="userPassword"
       userPattern="uid={0},ou=people,dc=mycompany,dc=com"
          roleBase="ou=groups,dc=mycompany,dc=com"
          roleName="cn"
        roleSearch="(uniqueMember={0})"
/>

但是,正如之前所讨论的那样,往往应该优先考虑默认的绑定模式。

特别注意事项

使用 JNDIRealm 需要遵循以下规则:

  • 当用户首次访问一个受保护资源时,Tomcat 会调用这一 Realm 的 authenticate() 方法,从而使任何对数据库的即时修改(新用户、密码或角色改变,等等)都能立即生效。
  • 一旦用户认证成功,在登录后,该用户(及其相应角色)就将缓存在 Tomcat 中。(对于以表单形式的认证,这意味着直到会话超时或者无效才会过期;对于基本形式的验证,意味着直到用户关闭浏览器才会过期。)在会话序列化期间不会保存或重置缓存的用户。对已认证用户的数据库信息进行的任何改动都不会生效,直到该用户下次登录。
  • 应用负责管理users(用户表)和user roles(用户角色表)中的信息。Tomcat 没有提供任何内置功能来维护这两种表。