领域配置指南
目录
快速入门
本文档介绍如何配置 Tomcat 以支持容器管理的安全,方法是连接到现有的用户名、密码和用户角色“数据库”。只有在使用包含一个或多个<security-constraint>
元素和定义用户如何进行身份验证的<login-config>
元素的 Web 应用程序时,才需要关心这一点。如果您没有使用这些功能,可以安全地跳过本文档。
有关容器管理安全的背景信息,请参阅Servlet 规范(版本 2.4),第 12 节。
有关使用 Tomcat 的单点登录功能(允许用户在与虚拟主机关联的所有 Web 应用程序中仅进行一次身份验证)的信息,请参阅此处。
概述
什么是领域?
领域是用户名和密码的“数据库”,这些用户名和密码标识 Web 应用程序(或一组 Web 应用程序)的有效用户,以及与每个有效用户关联的角色列表。您可以将角色视为类似于类 Unix 操作系统中的组,因为对特定 Web 应用程序资源的访问权限授予拥有特定角色的所有用户(而不是列出关联的用户名)。特定用户可以与其用户名关联任意数量的角色。
虽然 Servlet 规范描述了应用程序声明其安全需求的便携机制(在 web.xml
部署描述符中),但没有便携式 API 定义 Servlet 容器与关联的用户和角色信息之间的接口。然而,在许多情况下,将 Servlet 容器“连接”到生产环境中已有的身份验证数据库或机制是可取的。因此,Tomcat 定义了一个 Java 接口(org.apache.catalina.Realm
),可以通过“插件”组件实现该接口来建立这种连接。提供了六个标准插件,支持连接到各种身份验证信息源。
- DataSourceRealm - 访问存储在关系数据库中的身份验证信息,通过命名的 JNDI JDBC 数据源访问。
- JNDIRealm - 访问存储在基于 LDAP 的目录服务器中的身份验证信息,通过 JNDI 提供程序访问。
- UserDatabaseRealm - 访问存储在 UserDatabase JNDI 资源中的身份验证信息,该资源通常由 XML 文档(
conf/tomcat-users.xml
)支持。 - MemoryRealm - 访问存储在内存中对象集合中的身份验证信息,该集合从 XML 文档(
conf/tomcat-users.xml
)初始化。 - JAASRealm - 通过 Java 身份验证和授权服务 (JAAS) 框架访问身份验证信息。
也可以编写自己的 Realm
实现,并将其与 Tomcat 集成。为此,您需要
- 实现
org.apache.catalina.Realm
, - 将编译后的 realm 放置在 $CATALINA_HOME/lib 中,
- 按照下面“配置 Realm”部分的描述声明您的 realm,
- 将您的 realm 声明到 MBeans 描述符 中。
配置领域
在深入了解标准 Realm 实现的细节之前,重要的是要大体了解 Realm 的配置方式。通常,您将在 conf/server.xml
配置文件中添加一个 XML 元素,如下所示
<Realm className="... class name for this implementation"
... other attributes for this implementation .../>
<Realm>
元素可以嵌套在以下任何一个 Container
元素中。Realm 元素的位置直接影响该 Realm 的“范围”(即哪些 Web 应用程序将共享相同的身份验证信息)
- 在 <Engine> 元素中 - 此 Realm 将在所有虚拟主机上的所有 Web 应用程序中共享,除非它被嵌套在从属
<Host>
或<Context>
元素中的 Realm 元素覆盖。 - 在 <Host> 元素中 - 此 Realm 将在该虚拟主机上的所有 Web 应用程序中共享,除非它被嵌套在从属
<Context>
元素中的 Realm 元素覆盖。 - 在 <Context> 元素内 - 此 Realm 将仅用于此 Web 应用程序。
常见功能
摘要密码
对于每个标准的 Realm
实现,用户的密码(默认情况下)以明文形式存储。在许多环境中,这是不可取的,因为对身份验证数据的随意观察者可以收集到足够的信息来成功登录并冒充其他用户。为了避免此问题,标准实现支持摘要用户密码的概念。这允许存储的密码版本被编码(以一种不容易逆转的形式),但 Realm
实现仍然可以利用它进行身份验证。
当标准 realm 通过检索存储的密码并将其与用户提供的密码进行比较来进行身份验证时,您可以通过在 <Realm>
元素内放置一个 CredentialHandler
元素来选择摘要密码。支持 SSHA、SHA 或 MD5 等算法的一种简单选择是使用 MessageDigestCredentialHandler
。此元素必须配置为 java.security.MessageDigest
类支持的摘要算法之一(SSHA、SHA 或 MD5)。当您选择此选项时,存储在 Realm
中的密码内容必须是使用指定算法摘要后的密码的明文版本。
当调用 Realm 的 authenticate()
方法时,用户指定的(明文)密码将使用相同的算法进行摘要,并将结果与 Realm
返回的值进行比较。匹配表示原始密码的明文版本与用户提供的密码相同,因此应授权此用户。
为了计算明文密码的摘要值,支持两种便捷技术。
- 如果您正在编写需要动态计算摘要密码的应用程序,请调用
org.apache.catalina.realm.RealmBase
类的静态Digest()
方法,并将明文密码、摘要算法名称和编码作为参数传递。此方法将返回摘要后的密码。 - 如果您想执行命令行实用程序来计算摘要密码,只需执行
此明文密码的摘要版本将返回到标准输出。
CATALINA_HOME/bin/digest.[bat|sh] -a {algorithm} {cleartext-password}
如果将摘要密码与 DIGEST 身份验证一起使用,用于生成摘要的明文不同,并且摘要必须使用 MD5 算法的一次迭代,且不带盐。在上面的示例中,{cleartext-password}
必须替换为 {username}:{realm}:{cleartext-password}
。例如,在开发环境中,这可能采用 testUser:Authentication required:testPassword
的形式。{realm}
的值取自 Web 应用程序的 <login-config>
中的 <realm-name>
元素。如果未在 web.xml 中指定,则使用默认值 Authentication required
。
使用除平台默认编码之外的编码的用户名和/或密码可以使用
CATALINA_HOME/bin/digest.[bat|sh] -a {algorithm} -e {encoding} {input}
但需要小心确保输入正确传递给摘要器。摘要器返回 {input}:{digest}
。如果输入在返回中显示为损坏,则摘要将无效。
摘要的输出格式为 {salt}${iterations}${digest}
。如果盐长度为零且迭代次数为一,则输出将简化为 {digest}
。
CATALINA_HOME/bin/digest.[bat|sh]
的完整语法为
CATALINA_HOME/bin/digest.[bat|sh] [-a <algorithm>] [-e <encoding>]
[-i <iterations>] [-s <salt-length>] [-k <key-length>]
[-h <handler-class-name>] [-f <password-file> | <credentials>]
- -a - 用于生成存储的凭据的算法。如果未指定,将使用处理程序的默认值。如果未指定处理程序和算法,则将使用默认值
SHA-512
- -e - 用于任何可能需要的字节到/从字符转换的编码。如果未指定,将使用系统编码 (
Charset#defaultCharset()
)。 - -i - 生成存储的凭据时使用的迭代次数。如果未指定,将使用 CredentialHandler 的默认值。
- -s - 要生成并存储为凭据一部分的盐的长度(以字节为单位)。如果未指定,将使用 CredentialHandler 的默认值。
- -k - 生成凭据时创建的密钥(如果有)的长度(以位为单位)。如果未指定,将使用 CredentialHandler 的默认值。
- -h - 要使用的 CredentialHandler 的完全限定类名。如果未指定,将依次测试内置处理程序(MessageDigestCredentialHandler 然后是 SecretKeyCredentialHandler),第一个接受指定算法的处理程序将被使用。
- -f - 包含要编码的密码的文件的名称。文件中的每一行都应该只包含一个密码。使用此选项会忽略其他密码输入。
示例应用程序
与 Tomcat 一起提供的示例应用程序包含一个受安全约束保护的区域,它利用基于表单的登录。要访问它,请将您的浏览器指向 https://127.0.0.1:8080/examples/jsp/security/protected/ 并使用为默认 UserDatabaseRealm 描述的用户名和密码之一登录。
管理器应用程序
如果您希望使用 Manager Application 在运行的 Tomcat 安装中部署和取消部署应用程序,您必须在所选 Realm 实现中至少为一个用户名添加 "manager-gui" 角色。这是因为 manager web 应用程序本身使用一个安全约束,该约束要求 "manager-gui" 角色才能访问该应用程序 HTML 接口中的任何请求 URI。
出于安全原因,默认 Realm 中的任何用户名(即使用 conf/tomcat-users.xml
)都不会被分配 "manager-gui" 角色。因此,在 Tomcat 管理员专门将此角色分配给一个或多个用户之前,没有人能够使用此应用程序的功能。
标准领域实现
DataSourceRealm
介绍
DataSourceRealm 是 Tomcat Realm
接口的实现,它通过 JNDI 命名的 JDBC DataSource 在关系数据库中查找用户。存在大量的配置灵活性,允许您适应现有的表和列名,只要您的数据库结构符合以下要求
- 必须有一个表(在下面称为 users 表),其中包含此
Realm
应该识别的每个有效用户的行。 - 用户 表必须包含至少两列(如果您的现有应用程序需要,它可以包含更多列)。
- 用户登录时 Tomcat 识别的用户名。
- 用户登录时 Tomcat 识别的密码。此值可以是明文或摘要 - 有关更多信息,请参见下文。
- 必须有一个表(在下文中称为 用户角色 表),该表为分配给特定用户的每个有效角色包含一行。用户可以具有零个、一个或多个有效角色。
- 用户角色 表必须包含至少两列(如果您的现有应用程序需要,它可以包含更多列)。
- Tomcat 识别的用户名(与 用户 表中指定的相同值)。
- 与该用户关联的有效角色的名称。
快速入门
要将 Tomcat 设置为使用 DataSourceRealm,您需要执行以下步骤。
- 如果您尚未这样做,请在您的数据库中创建符合上述要求的表和列。
- 配置一个数据库用户名和密码供 Tomcat 使用,该用户名和密码至少对上述表具有只读访问权限。(Tomcat 永远不会尝试写入这些表。)
- 为您的数据库配置一个名为 JNDI 的 JDBC DataSource。有关如何配置名为 JNDI 的 JDBC DataSource 的信息,请参阅 JNDI DataSource 示例操作指南。请务必根据 JNDI DataSource 的定义位置,适当地设置
Realm
的localDataSource
属性。 - 在您的
$CATALINA_BASE/conf/server.xml
文件中,设置一个<Realm>
元素,如下所述。 - 如果 Tomcat 正在运行,请重新启动它。
Realm 元素属性
要配置 DataSourceRealm,您将创建一个 <Realm>
元素,并将其嵌套在您的 $CATALINA_BASE/conf/server.xml
文件中,如 上文 所述。DataSourceRealm 的属性在 Realm 配置文档中定义。
示例
创建所需表的示例 SQL 脚本可能如下所示(根据您的特定数据库调整语法)。
create table users (
user_name varchar(15) not null primary key,
user_pass varchar(15) not null
);
create table user_roles (
user_name varchar(15) not null,
role_name varchar(15) not null,
primary key (user_name, role_name)
);
以下是如何使用名为“authority”的 MySQL 数据库的示例,该数据库使用上述表进行配置,并使用名为“java:/comp/env/jdbc/authority”的 JNDI JDBC DataSource 进行访问。
<Realm className="org.apache.catalina.realm.DataSourceRealm"
dataSourceName="jdbc/authority"
userTable="users" userNameCol="user_name" userCredCol="user_pass"
userRoleTable="user_roles" roleNameCol="role_name"/>
补充说明
DataSourceRealm 按照以下规则运作
- 当用户首次尝试访问受保护资源时,Tomcat 将调用此 Realm 的
authenticate()
方法。因此,您对数据库进行的任何直接更改(新用户、更改密码或角色等)都会立即反映出来。 - 用户经过身份验证后,用户(及其关联的角色)将在 Tomcat 中缓存,直到用户登录会话结束。(对于基于 FORM 的身份验证,这意味着直到会话超时或失效;对于 BASIC 身份验证,这意味着直到用户关闭浏览器)。缓存的用户 **不会** 在会话序列化过程中保存和恢复。对已验证用户的数据库信息进行的任何更改 **不会** 立即反映,直到该用户下次登录。
- 管理 users 和 user roles 表中的信息是您应用程序的责任。Tomcat 不提供任何内置功能来维护用户和角色。
JNDIRealm
介绍
**JNDIRealm** 是 Tomcat Realm
接口的实现,它在由 JNDI 提供程序访问的 LDAP 目录服务器中查找用户(通常是 JNDI API 类中提供的标准 LDAP 提供程序)。该 Realm 支持多种使用目录进行身份验证的方法。
连接到目录
Realm 与目录的连接由 **connectionURL** 配置属性定义。这是一个 URL,其格式由 JNDI 提供程序定义。它通常是一个 LDAP URL,指定要连接到的目录服务器的域名,以及可选的端口号和所需根命名上下文的区分名称 (DN)。
如果您有多个提供程序,您可以配置 **alternateURL**。如果无法建立到 **connectionURL** 提供程序的套接字连接,将尝试使用 **alternateURL**。
在建立连接以搜索目录并检索用户和角色信息时,领域会使用 **connectionName** 和 **connectionPassword** 属性中指定的用户名和密码向目录进行身份验证。如果未指定这些属性,则连接将是匿名的。在许多情况下,这已经足够了。
选择用户的目录条目
每个可以进行身份验证的用户都必须在目录中由一个单独的条目表示,该条目对应于 **connectionURL** 属性定义的初始 DirContext
中的一个元素。此用户条目必须包含一个包含用于身份验证的用户名属性。
通常,用户条目的识别名称包含用于身份验证的用户名,但在其他方面对于所有用户都是相同的。在这种情况下,可以使用 **userPattern** 属性来指定 DN,其中 "{0}" 标记了用户名应该被替换的位置。
否则,领域必须搜索目录以找到包含用户名的唯一条目。以下属性配置此搜索:
- **userBase** - 包含用户的子树的根条目。如果未指定,则搜索基础是顶级上下文。
- **userSubtree** - 搜索范围。如果希望搜索以 **userBase** 条目为根的整个子树,则将其设置为
true
。默认值为false
,请求仅包括顶层的单级搜索。 - **userSearch** - 指定在替换用户名后使用的 LDAP 搜索过滤器的模式。
验证用户
-
绑定模式
默认情况下,领域通过使用该用户的条目 DN 和用户提供的密码绑定到目录来验证用户。如果此简单绑定成功,则认为用户已通过身份验证。
出于安全原因,目录可能会存储用户密码的摘要,而不是明文版本(有关更多信息,请参阅 摘要密码)。在这种情况下,作为简单绑定操作的一部分,目录会在验证用户提供的明文密码与存储的值之前,自动计算出该明文密码的正确摘要。因此,在绑定模式下,领域不参与摘要处理。**digest** 属性不会被使用,如果设置了该属性,它将被忽略。
-
比较模式
或者,领域可以从目录中检索存储的密码,并将其与用户提供的密码进行显式比较。通过将 **userPassword** 属性设置为用户条目中包含密码的目录属性的名称来配置此模式。
比较模式有一些缺点。首先,必须配置**connectionName**和**connectionPassword**属性,以允许领域读取目录中用户的密码。出于安全原因,这通常是不希望的;事实上,许多目录实现甚至不允许目录管理员读取这些密码。此外,领域必须自己处理密码摘要,包括所用算法的变体以及在目录中表示密码哈希的方式。但是,领域有时可能需要访问存储的密码,例如为了支持 HTTP 摘要访问身份验证 (RFC 2069)。(请注意,HTTP 摘要身份验证不同于如上所述存储在存储库中的用于用户信息的密码摘要。)
将角色分配给用户
目录领域支持两种方法来表示目录中的角色
-
角色作为显式目录条目
角色可以通过显式目录条目来表示。角色条目通常是 LDAP 组条目,其中一个属性包含角色的名称,另一个属性的值是该角色中用户的可分辨名称或用户名。以下属性配置目录搜索以查找与已验证用户关联的角色名称
- **roleBase** - 角色搜索的基准条目。如果未指定,则搜索基准为顶级目录上下文。
- **roleSubtree** - 搜索范围。如果要搜索以**roleBase**条目为根的整个子树,则设置为
true
。默认值为false
,请求仅包括顶层的单级搜索。 - **roleSearch** - 用于选择角色条目的 LDAP 搜索过滤器。它可以选择包含已验证用户的可分辨名称和/或用户名和/或用户目录条目中的属性的模式替换“{0}”,“{1}”和/或“{2}”。使用**userRoleAttribute**指定提供“{2}”值的属性的名称。
- **roleName** - 角色条目中包含该角色名称的属性。
- **roleNested** - 启用嵌套角色。如果要将角色嵌套在角色中,则设置为
true
。如果已配置,则将递归地尝试每个新找到的 roleName 和可分辨名称以进行新的角色搜索。默认值为false
。
-
角色作为用户条目的属性
角色名称也可以作为用户目录条目中属性的值。使用 **userRoleName** 指定此属性的名称。
可以使用这两种角色表示方法的组合。
快速入门
要设置 Tomcat 使用 JNDIRealm,您需要按照以下步骤操作
- 确保您的目录服务器配置了与上面列出的要求匹配的模式。
- 如果需要,配置一个供 Tomcat 使用的用户名和密码,该用户名和密码对上述信息具有只读访问权限。(Tomcat 永远不会尝试修改此信息。)
- 在您的
$CATALINA_BASE/conf/server.xml
文件中,设置一个<Realm>
元素,如下所述。 - 如果 Tomcat 正在运行,请重新启动它。
Realm 元素属性
要配置 JNDIRealm,您需要创建一个 <Realm>
元素,并将其嵌套在您的 $CATALINA_BASE/conf/server.xml
文件中,如 上面 所述。JNDIRealm 的属性在 Realm 配置文档中定义。
示例
在您的目录服务器中创建适当的模式超出了本文档的范围,因为它对每个目录服务器实现都是唯一的。在下面的示例中,我们将假设您使用的是 OpenLDAP 目录服务器(版本 2.0.11 或更高版本)的某个发行版,可以从 https://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: [email protected]
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: [email protected]
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 目录服务器,一个示例 Realm
元素可能如下所示,假设用户使用他们的 uid(例如 jjones)登录应用程序,并且匿名连接足以搜索目录并检索角色信息
<Realm className="org.apache.catalina.realm.JNDIRealm"
connectionURL="ldap://127.0.0.1:389"
userPattern="uid={0},ou=people,dc=mycompany,dc=com"
roleBase="ou=groups,dc=mycompany,dc=com"
roleName="cn"
roleSearch="(uniqueMember={0})"
/>
使用此配置,领域将通过将用户名替换到 userPattern
中来确定用户的识别名称,通过使用此 DN 和从用户接收的密码绑定到目录来进行身份验证,并搜索目录以查找用户的角色。
现在假设用户需要输入他们的电子邮件地址而不是他们的用户 ID 来登录。在这种情况下,领域必须在目录中搜索用户的条目。(当用户条目保存在对应于不同组织单位或公司位置的多个子树中时,也需要进行搜索)。
此外,假设除了组条目之外,您还想使用用户条目中的属性来保存角色。现在,Janet Jones 的条目可能如下所示
dn: uid=jjones,ou=people,dc=mycompany,dc=com
objectClass: inetOrgPerson
uid: jjones
sn: jones
cn: janet jones
mail: [email protected]
memberOf: role2
memberOf: role3
userPassword: janet
此领域配置将满足新的要求
<Realm className="org.apache.catalina.realm.JNDIRealm"
connectionURL="ldap://127.0.0.1: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 以 "[email protected]" 登录时,领域会在目录中搜索具有该值作为其邮件属性的唯一条目,并尝试以 uid=jjones,ou=people,dc=mycompany,dc=com
和给定密码绑定到目录。如果身份验证成功,他们将被分配三个角色:"role2" 和 "role3",它们是其目录条目中 "memberOf" 属性的值,以及 "tomcat",它是其所属的唯一组条目中 "cn" 属性的值。
最后,为了通过从目录中检索密码并在领域中进行本地比较来验证用户,您可以使用类似于此的领域配置
<Realm className="org.apache.catalina.realm.JNDIRealm"
connectionName="cn=Manager,dc=mycompany,dc=com"
connectionPassword="secret"
connectionURL="ldap://127.0.0.1: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 中缓存,直到用户登录会话结束。(对于基于 FORM 的身份验证,这意味着直到会话超时或失效;对于 BASIC 身份验证,这意味着直到用户关闭浏览器)。缓存的用户 **不会** 在会话序列化期间保存和恢复。对已验证用户的目录信息的任何更改 **不会** 反映出来,直到该用户下次登录。
- 管理目录服务器中的信息是您自己的应用程序的责任。Tomcat 不提供任何内置功能来维护用户和角色。
UserDatabaseRealm
介绍
UserDatabaseRealm 是 Tomcat Realm
接口的实现,它使用 JNDI 资源来存储用户信息。默认情况下,JNDI 资源由 XML 文件支持。它不是为大规模生产使用而设计的。在启动时,UserDatabaseRealm 会从 XML 文档中加载有关所有用户及其对应角色的信息(默认情况下,此文档从 $CATALINA_BASE/conf/tomcat-users.xml
加载)。用户、他们的密码和他们的角色都可以动态编辑,通常通过 JMX。更改可以保存,并将反映在 XML 文件中。
Realm 元素属性
要配置 UserDatabaseRealm,您将在 $CATALINA_BASE/conf/server.xml
文件中创建一个 <Realm>
元素,并将其嵌套在其中,如 上面 所述。UserDatabaseRealm 的属性在 Realm 配置文档中定义。
用户文件格式
对于基于 XML 文件的 UserDatabase
,用户文件使用与 MemoryRealm 相同的格式。
示例
Tomcat 的默认安装配置了一个嵌套在 <Engine>
元素中的 UserDatabaseRealm,以便它适用于所有虚拟主机和 Web 应用程序。conf/tomcat-users.xml
文件的默认内容是
<tomcat-users>
<user username="tomcat" password="tomcat" roles="tomcat" />
<user username="role1" password="tomcat" roles="role1" />
<user username="both" password="tomcat" roles="tomcat,role1" />
</tomcat-users>
补充说明
UserDatabaseRealm 按照以下规则运行
- 当 Tomcat 首次启动时,它会从用户文件加载所有定义的用户及其关联信息。对该文件中的数据进行的更改将不会在 Tomcat 重新启动之前被识别。可以通过 UserDatabase 资源进行更改。Tomcat 提供了可以通过 JMX 访问的 MBean 来实现此目的。
- 当用户首次尝试访问受保护的资源时,Tomcat 将调用此
Realm
的authenticate()
方法。 - 一旦用户通过身份验证,用户将在 Tomcat 中与用户登录期间相关联。(对于基于 FORM 的身份验证,这意味着直到会话超时或失效;对于 BASIC 身份验证,这意味着直到用户关闭浏览器)。但是,用户角色仍然会反映
UserDatabase
的内容,这与其他领域不同。如果用户从数据库中删除,则将被视为没有角色。UserDatabaseRealm
的useStaticPrincipal
属性可用于缓存用户及其所有角色。缓存的用户不会在会话序列化之间保存和恢复。当用户的 principal 对象出于任何原因被序列化时,它也将被替换为具有不再反映数据库内容的角色的静态等效对象。
MemoryRealm
介绍
MemoryRealm 是 Tomcat Realm
接口的一个简单演示实现。它不是为生产环境设计的。在启动时,MemoryRealm 从 XML 文档加载有关所有用户及其对应角色的信息(默认情况下,此文档从 $CATALINA_BASE/conf/tomcat-users.xml
加载)。对该文件中的数据进行的更改将不会在 Tomcat 重新启动之前被识别。
Realm 元素属性
要配置 MemoryRealm,您将在 $CATALINA_BASE/conf/server.xml
文件中创建一个 <Realm>
元素,并将其嵌套在其中,如 上面 所述。MemoryRealm 的属性在 Realm 配置文档中定义。
用户文件格式
用户文件(默认情况下,conf/tomcat-users.xml
必须是一个 XML 文档,具有根元素 <tomcat-users>
。嵌套在根元素中的将是每个有效用户的 <user>
元素,包含以下属性
- name - 此用户必须登录的用户名。
- password - 此用户必须登录的密码(如果
<Realm>
元素上未设置digest
属性,则为明文,否则按 此处 所述进行适当的摘要)。 - roles - 与此用户关联的角色名称的逗号分隔列表。
补充说明
MemoryRealm 按照以下规则运行
- 当 Tomcat 首次启动时,它会从用户文件加载所有定义的用户及其关联信息。对该文件中的数据进行的更改将不会在 Tomcat 重新启动之前被识别。
- 当用户首次尝试访问受保护的资源时,Tomcat 将调用此
Realm
的authenticate()
方法。 - 用户经过身份验证后,用户(及其关联的角色)将在 Tomcat 中缓存,持续时间为用户登录期间。(对于基于 FORM 的身份验证,这意味着直到会话超时或失效;对于 BASIC 身份验证,这意味着直到用户关闭浏览器)。缓存的用户不会在会话序列化之间保存和恢复。
- 管理用户文件中的信息是应用程序的责任。Tomcat 不提供任何内置功能来维护用户和角色。
JAASRealm
介绍
JAASRealm 是 Tomcat Realm
接口的实现,它通过 Java 身份验证和授权服务 (JAAS) 框架对用户进行身份验证,该框架现在作为标准 Java SE API 的一部分提供。
使用 JAASRealm 使开发人员能够将几乎所有可想象的安全领域与 Tomcat 的 CMA 相结合。
JAASRealm 是 Tomcat 对基于 JAAS 的 J2EE 身份验证框架的原型,用于 J2EE v1.4,基于 JCP 规范请求 196,以增强容器管理的安全性和促进“可插拔”的身份验证机制,其实现将与容器无关。
基于 JAAS 登录模块和主体(参见 javax.security.auth.spi.LoginModule
和 javax.security.Principal
),您可以开发自己的安全机制或包装另一个第三方机制以与 Tomcat 实现的 CMA 集成。
快速入门
要设置 Tomcat 以使用 JAASRealm 和您自己的 JAAS 登录模块,您需要按照以下步骤操作
- 编写您自己的基于 JAAS 的 LoginModule、User 和 Role 类(参见 JAAS 身份验证教程 和 JAAS 登录模块开发人员指南)由 JAAS 登录上下文 (
javax.security.auth.login.LoginContext
) 管理。在开发您的 LoginModule 时,请注意 JAASRealm 的内置CallbackHandler
目前仅识别NameCallback
和PasswordCallback
。 - 虽然 JAAS 中没有指定,但您应该创建单独的类来区分用户和角色,扩展
javax.security.Principal
,以便 Tomcat 可以区分从您的登录模块返回的哪些主体是用户,哪些是角色(参见org.apache.catalina.realm.JAASRealm
)。无论如何,第一个返回的主体始终被视为用户主体。 - 将编译后的类放在 Tomcat 的类路径上
- 为 Java 设置一个 login.config 文件(参见 JAAS LoginConfig 文件)并告诉 Tomcat 在哪里找到它,方法是将它的位置指定给 JVM,例如通过设置环境变量:
JAVA_OPTS=$JAVA_OPTS -Djava.security.auth.login.config==$CATALINA_BASE/conf/jaas.config
- 在您的 web.xml 中配置您的安全约束,以保护您想要保护的资源
- 在您的 server.xml 中配置 JAASRealm 模块
- 如果 Tomcat 正在运行,请重新启动它。
Realm 元素属性
要像步骤 6 中那样配置 JAASRealm,您需要创建一个 <Realm>
元素,并将它嵌套在您 $CATALINA_BASE/conf/server.xml
文件中的 <Engine>
节点内。JAASRealm 的属性在 Realm 配置文档中定义。
示例
以下是一个 server.xml 代码片段的示例。
<Realm className="org.apache.catalina.realm.JAASRealm"
appName="MyFooRealm"
userClassNames="org.foobar.realm.FooUser"
roleClassNames="org.foobar.realm.FooRole"/>
您的登录模块负责创建和保存代表用户的 Principal(javax.security.auth.Subject
)的用户和角色对象。如果您的登录模块没有创建用户对象,但也没有抛出登录异常,那么 Tomcat CMA 将会崩溃,您将被留在 https://127.0.0.1:8080/myapp/j_security_check URI 或其他未指定的位置。
JAAS 方法的灵活性体现在两个方面:
- 您可以在自己的登录模块中进行任何所需的后台处理。
- 您可以通过更改配置并重新启动服务器来插入完全不同的 LoginModule,而无需对应用程序进行任何代码更改。
补充说明
- 当用户首次尝试访问受保护资源时,Tomcat 将调用此
Realm
的authenticate()
方法。因此,您在安全机制中直接进行的任何更改(新用户、更改的密码或角色等)将立即反映出来。 - 用户经过身份验证后,用户(及其关联的角色)将在 Tomcat 中缓存,直到用户登录会话超时或失效。对于基于表单的身份验证,这意味着直到会话超时或失效;对于基本身份验证,这意味着直到用户关闭浏览器。对已验证用户的安全信息的任何更改将 **不会** 反映出来,直到该用户下次登录为止。
- 与其他
Realm
实现一样,如果server.xml
中的<Realm>
元素包含digest
属性,则支持摘要密码;JAASRealm 的CallbackHandler
将在将密码传递回LoginModule
之前对密码进行摘要。
CombinedRealm
介绍
CombinedRealm 是 Tomcat Realm
接口的实现,它通过一个或多个子 Realm 对用户进行身份验证。
使用 CombinedRealm 使开发人员能够组合相同类型或不同类型的多个 Realm。这可以用于针对不同的来源进行身份验证,在某个 Realm 失败时提供回退,或用于需要多个 Realm 的任何其他目的。
子 Realm 通过在定义 CombinedRealm 的 Realm
元素中嵌套 Realm
元素来定义。将尝试针对每个 Realm
按其列出的顺序进行身份验证。针对任何 Realm 的身份验证都足以验证用户。
Realm 元素属性
要配置 CombinedRealm,您需要创建一个 <Realm>
元素,并将其嵌套在您的 $CATALINA_BASE/conf/server.xml
文件中,位于 <Engine>
或 <Host>
内。您也可以将其嵌套在 context.xml
文件中的 <Context>
节点内。
示例
以下是一个使用 UserDatabase Realm 和 DataSource Realm 的 server.xml 代码片段示例。
<Realm className="org.apache.catalina.realm.CombinedRealm" >
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
<Realm className="org.apache.catalina.realm.DataSourceRealm"
dataSourceName="jdbc/authority"
userTable="users" userNameCol="user_name" userCredCol="user_pass"
userRoleTable="user_roles" roleNameCol="role_name"/>
</Realm>
LockOutRealm
介绍
LockOutRealm 是 Tomcat Realm
接口的一种实现,它扩展了 CombinedRealm,为用户提供锁定功能,如果在给定时间段内出现太多次身份验证失败尝试,则会锁定用户。
为了确保正常运行,此 Realm 具有合理的同步程度。
此 Realm 不需要修改底层 Realm 或关联的用户存储机制。它通过记录所有失败的登录尝试来实现这一点,包括那些不存在的用户。为了防止通过故意使用无效用户进行请求(从而导致此缓存增长)而导致的拒绝服务攻击,对失败身份验证用户列表的大小进行了限制。
子 Realm 通过将 Realm
元素嵌套在定义 LockOutRealm 的 Realm
元素内来定义。身份验证将按列表中的顺序针对每个 Realm
进行尝试。针对任何 Realm 的身份验证都足以验证用户。
Realm 元素属性
要配置 LockOutRealm,您需要创建一个 <Realm>
元素,并将其嵌套在您的 $CATALINA_BASE/conf/server.xml
文件中,位于 <Engine>
或 <Host>
内。您也可以将其嵌套在 context.xml
文件中的 <Context>
节点内。LockOutRealm 的属性在 Realm 配置文档中定义。
示例
以下是一个示例,说明如何将锁定功能添加到 UserDatabase Realm 的 server.xml 代码片段。
<Realm className="org.apache.catalina.realm.LockOutRealm" >
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>