JNDIRealm

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

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

a. 连接目录

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

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

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

b. 选择用户目录项

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

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

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

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

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

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

  • 对比模式

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

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

d. 赋予用户角色

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

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

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

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

    将角色名称保存为用户目录项中的一个属性值。使用 userRoleName 来指定该属性名称。

当然,也可以综合使用这两种方法来表示角色。

快速入门

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

  1. 确保目录服务器配置中的模式符合上文所列出的要求。
  2. 必要时可以为 Tomcat 配置一个用户名和密码,对上文所提到的信息用于只读访问权限(Tomcat 永远不会修改该信息。)
  3. 按照下文的方法,在 $CATALINA_BASE/conf/server.xml 文件中设置一个 <Realm> 元素。
  4. 如果 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 运行在同一台机器上。

接下来,假定利用如下所示的元素(以 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)登录应用,匿名连接已经足够可以搜索目录并获取角色信息了:

<Realm   className="org.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=com
objectClass: inetOrgPerson
uid: jjones
sn: jones
cn: janet jones
mail: j.jones@mycompany.com
memberOf: role2
memberOf: role3
userPassword: 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 没有提供任何内置功能来维护这两种表。
联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2022

备案号:京ICP备15003423号-3