域配置操作指南
目录
快速开始
本文档描述了如何配置 Tomcat 以支持容器管理的安全,通过连接到现有的用户名、密码和用户角色“数据库”来实现。您只有在使用包含一个或多个<security-constraint>
元素以及定义用户如何进行身份验证的<login-config>
元素的Web应用程序时才需要关注此内容。如果您不使用这些功能,则可以安全地跳过本文档。
有关容器管理安全的基本背景信息,请参阅Servlet 规范(版本 2.4)的第 12 节。
有关使用 Tomcat 的单点登录功能(允许用户在与虚拟主机关联的所有Web应用程序中进行一次身份验证)的信息,请参阅此处。
概述
什么是域?
一个 域(Realm) 是一个“数据库”,其中包含识别 Web 应用程序(或一组 Web 应用程序)有效用户的用户名和密码,以及与每个有效用户关联的角色列表的枚举。您可以将角色视为与 Unix 类操作系统中的组类似,因为对特定 Web 应用程序资源的访问权限是授予拥有特定角色的所有用户(而不是枚举关联用户名的列表)。一个特定用户可以拥有与其用户名关联的任意数量的角色。
尽管 Servlet 规范描述了一种可移植的机制,用于应用程序声明其安全要求(在web.xml
部署描述符中),但没有可移植的 API 来定义 servlet 容器与相关用户和角色信息之间的接口。然而,在许多情况下,希望将 servlet 容器“连接”到生产环境中已存在的某个身份验证数据库或机制。因此,Tomcat 定义了一个 Java 接口(org.apache.catalina.Realm
),可以通过“插件”组件来实现此连接。提供了六个标准插件,支持连接到各种身份验证信息源:
- DataSourceRealm - 访问存储在关系数据库中的身份验证信息,通过命名 JNDI JDBC DataSource 进行访问。
- 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
, - 按照下面“配置域”部分所述声明您的域,
- 将您的域声明给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>
元素内放置一个CredentialHandler
元素来选择使用摘要密码。一个简单的选择是使用MessageDigestCredentialHandler
来支持 SSHA、SHA 或 MD5 算法中的一种。此元素必须配置为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 随附的示例应用程序包含一个受安全约束保护的区域,使用基于表单的登录。要访问它,请在浏览器中输入http://localhost:8080/examples/jsp/security/protected/,并使用默认UserDatabaseRealm中描述的用户名和密码之一登录。
管理器应用
如果您希望使用管理器应用程序在运行中的 Tomcat 安装中部署和取消部署应用程序,您必须将“manager-gui”角色添加到您所选 Realm 实现中的至少一个用户名。这是因为管理器 Web 应用程序本身使用了一个安全约束,要求具有“manager-gui”角色才能访问该应用程序 HTML 界面中的任何请求 URI。
出于安全原因,默认 Realm(即使用conf/tomcat-users.xml
)中没有用户名被分配“manager-gui”角色。因此,在 Tomcat 管理员明确将此角色分配给一个或多个用户之前,没有人能够使用此应用程序的功能。
标准域实现
DataSourceRealm
简介
DataSourceRealm 是 Tomcat Realm
接口的一个实现,它通过 JNDI 命名的 JDBC DataSource 查找关系数据库中的用户。它提供了很大的配置灵活性,允许您适应现有的表和列名,只要您的数据库结构符合以下要求:
- 必须有一个表(下文称为用户表),其中包含
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 正在运行,请重启它。
域元素属性
要配置 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 中缓存,直至用户登录结束。(对于基于表单的身份验证,这意味着直到会话超时或失效;对于基本身份验证,这意味着直到用户关闭浏览器)。缓存的用户在会话序列化过程中不会被保存和恢复。对于已通过身份验证的用户,其数据库信息发生任何更改都不会反映,直到该用户下次再次登录。
- 管理用户和用户角色表中的信息是您自己应用程序的责任。Tomcat 不提供任何内置功能来维护用户和角色。
JNDIRealm
简介
JNDIRealm 是 Tomcat Realm
接口的一个实现,它通过 JNDI 供应商(通常是与 JNDI API 类一起提供的标准 LDAP 供应商)查找 LDAP 目录服务器中的用户。该域支持多种使用目录进行身份验证的方法。
连接到目录
域与目录的连接由connectionURL配置属性定义。这是一个由 JNDI 供应商定义格式的 URL。它通常是一个 LDAP URL,指定要连接的目录服务器的域名,以及可选的端口号和所需根命名上下文的区分名(DN)。
如果您有多个提供程序,可以配置一个alternateURL。如果无法与connectionURL处的提供程序建立套接字连接,将尝试使用alternateURL。
为了搜索目录并检索用户和角色信息而建立连接时,域通过connectionName和connectionPassword属性指定的用户名和密码向目录进行身份验证。如果未指定这些属性,则连接是匿名的。这在许多情况下是足够的。
选择用户目录条目
每个可被认证的用户都必须在目录中以一个单独的条目表示,该条目对应于由connectionURL属性定义的初始DirContext
中的一个元素。此用户条目必须包含一个属性,该属性包含用于认证的用户名。
通常,用户条目的区分名(DN)包含用于身份验证的用户名,但除此之外对于所有用户都相同。在这种情况下,可以使用userPattern属性来指定 DN,其中“{0}”标记了应该替换用户名的地方。
否则,域必须搜索目录以查找包含用户名的唯一条目。以下属性配置此搜索:
- userBase - 包含用户的子树的根条目。如果未指定,搜索基准是顶层上下文。
- userSubtree - 搜索范围。如果您希望搜索以userBase条目为根的整个子树,请设置为
true
。默认值false
表示只进行单层搜索,仅包括顶层。 - userSearch - 指定在用户名替换后使用的 LDAP 搜索过滤器模式。
认证用户
-
绑定模式
默认情况下,域通过使用该用户的条目 DN 和用户提交的密码绑定到目录来认证用户。如果此简单绑定成功,则用户被视为已通过认证。
出于安全原因,目录可能存储用户密码的摘要而非明文版本(更多信息请参阅密码摘要)。在这种情况下,作为简单绑定操作的一部分,目录会自动计算用户提交的明文密码的正确摘要,然后与存储的值进行验证。因此,在绑定模式下,域不涉及摘要处理。digest属性不使用,如果设置也会被忽略。
-
比较模式
或者,域可以从目录中检索存储的密码并将其与用户提交的值进行显式比较。此模式通过将userPassword属性设置为用户条目中包含密码的目录属性的名称来配置。
比较模式存在一些缺点。首先,必须配置connectionName和connectionPassword属性,以允许域读取目录中用户的密码。出于安全原因,这通常是不可取的;事实上,许多目录实现甚至不允许目录管理器读取这些密码。此外,域必须自行处理密码摘要,包括所用算法的变化以及目录中表示密码哈希的方式。然而,域有时可能需要访问存储的密码,例如为了支持 HTTP 摘要访问认证(RFC 2069)。(请注意,HTTP 摘要认证与上面讨论的用于用户信息仓库中密码摘要的存储是不同的)。
为用户分配角色
目录域支持两种角色表示方法:
-
角色作为显式目录条目
角色可以通过显式目录条目表示。角色条目通常是 LDAP 组条目,其中一个属性包含角色的名称,另一个属性的值是属于该角色的用户的区分名(DN)或用户名。以下属性配置目录搜索以查找与已认证用户关联的角色名称:
- roleBase - 角色搜索的基准条目。如果未指定,搜索基准是顶层目录上下文。
- roleSubtree - 搜索范围。如果您希望搜索以
roleBase
条目为根的整个子树,请设置为true
。默认值false
表示只进行单层搜索,仅包括顶层。 - roleSearch - 用于选择角色条目的 LDAP 搜索过滤器。它可选地包含模式替换“{0}”用于已认证用户的区分名,和/或“{1}”用于用户名,和/或“{2}”用于用户目录条目中的属性。使用userRoleAttribute指定提供“{2}”值的属性名称。
- roleName - 角色条目中包含该角色名称的属性。
- roleNested - 启用嵌套角色。如果您想在角色中嵌套角色,请设置为
true
。如果配置,则每个新找到的 roleName 和 distinguished Name 将被递归尝试进行新的角色搜索。默认值为false
。
-
角色作为用户条目的属性
角色名称也可以作为用户目录条目中某个属性的值来保存。使用userRoleName来指定此属性的名称。
两种角色表示方法可以结合使用。
快速开始
要设置 Tomcat 使用 JNDIRealm,您需要遵循以下步骤:
- 确保您的目录服务器已配置与上述要求匹配的模式。
- 如果需要,配置一个供 Tomcat 使用的用户名和密码,该用户对上述信息具有只读访问权限。(Tomcat 绝不会尝试修改此信息。)
- 在您的
$CATALINA_BASE/conf/server.xml
文件中设置一个<Realm>
元素,如下所述。 - 如果 Tomcat 正在运行,请重启它。
域元素属性
要配置 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: 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 目录服务器,一个示例Realm
元素可能看起来像这样,假设用户使用他们的 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
中来确定用户的区分名,然后通过此 DN 和从用户接收到的密码绑定到目录进行身份验证,并搜索目录以查找用户的角色。
现在假设用户在登录时需要输入他们的电子邮件地址而不是用户ID。在这种情况下,域必须搜索目录以查找用户的条目。(当用户条目保存在可能对应于不同组织单位或公司位置的多个子树中时,搜索也是必要的)。
此外,假设除了组条目之外,您还想使用用户条目的一个属性来保存角色。现在,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 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”登录时,域会在目录中搜索一个具有该值作为其邮件属性的唯一条目,并尝试以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://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 中缓存,直至用户登录结束。(对于基于表单的身份验证,这意味着直到会话超时或失效;对于基本身份验证,这意味着直到用户关闭浏览器)。缓存的用户在会话序列化过程中不会被保存和恢复。对于已通过身份验证的用户,其目录信息发生任何更改都不会反映,直到该用户下次再次登录。
- 管理目录服务器中的信息是您自己应用程序的责任。Tomcat 不提供任何内置功能来维护用户和角色。
UserDatabaseRealm
简介
UserDatabaseRealm 是 Tomcat Realm
接口的一个实现,它使用 JNDI 资源来存储用户信息。默认情况下,JNDI 资源由 XML 文件支持。它并非为大规模生产使用而设计。在启动时,UserDatabaseRealm 从 XML 文档(默认情况下,此文档从$CATALINA_BASE/conf/tomcat-users.xml
加载)加载所有用户及其相应角色的信息。用户、他们的密码和角色都可以动态编辑,通常通过 JMX。更改可以保存并会反映在 XML 文件中。
域元素属性
要配置 UserDatabaseRealm,您将创建一个<Realm>
元素并将其嵌套在您的$CATALINA_BASE/conf/server.xml
文件中,如上文所述。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 访问的 MBeans。
- 当用户首次尝试访问受保护资源时,Tomcat 将调用此
Realm
的authenticate()
方法。 - 一旦用户通过身份验证,该用户将与 Tomcat 关联,直到用户登录结束。(对于基于表单的身份验证,这意味着直到会话超时或失效;对于基本身份验证,这意味着直到用户关闭浏览器)。然而,用户角色仍将反映
UserDatabase
的内容,这与其他域不同。如果用户从数据库中移除,则该用户将被视为没有角色。UserDatabaseRealm
的useStaticPrincipal
属性可以用于缓存用户及其所有角色。缓存的用户在会话序列化过程中不会被保存和恢复。当用户的 principal 对象因任何原因被序列化时,它也会被一个静态等价对象替换,该对象的角色将不再反映数据库内容。
MemoryRealm
简介
MemoryRealm 是 Tomcat Realm
接口的一个简单演示实现。它并非为生产用途而设计。在启动时,MemoryRealm 从 XML 文档(默认情况下,此文档从$CATALINA_BASE/conf/tomcat-users.xml
加载)加载所有用户及其相应角色的信息。此文件中数据的更改在 Tomcat 重启之前不会被识别。
域元素属性
要配置 MemoryRealm,您将创建一个<Realm>
元素并将其嵌套在您的$CATALINA_BASE/conf/server.xml
文件中,如上文所述。MemoryRealm 的属性在Realm配置文档中定义。
用户文件格式
用户文件(默认情况下为conf/tomcat-users.xml
)必须是一个 XML 文档,其根元素为<tomcat-users>
。在根元素内部,每个有效用户将有一个<user>
元素,包含以下属性:
- name - 此用户登录时必须使用的用户名。
- password - 此用户登录时必须使用的密码(如果
<Realm>
元素上未设置digest
属性,则为明文;否则,按此处所述进行适当的摘要)。 - roles - 与此用户关联的角色名称的逗号分隔列表。
附加说明
MemoryRealm 按照以下规则运行:
- 当 Tomcat 首次启动时,它会从用户文件中加载所有已定义的用户及其关联信息。对此文件中数据的更改在 Tomcat 重启之前不会被识别。
- 当用户首次尝试访问受保护资源时,Tomcat 将调用此
Realm
的authenticate()
方法。 - 一旦用户通过身份验证,用户(及其关联的角色)将在 Tomcat 中缓存,直至用户登录结束。(对于基于表单的身份验证,这意味着直到会话超时或失效;对于基本身份验证,这意味着直到用户关闭浏览器)。缓存的用户在会话序列化过程中不会被保存和恢复。
- 管理用户文件中的信息是您的应用程序的责任。Tomcat 不提供任何内置功能来维护用户和角色。
JAASRealm
简介
JAASRealm 是 Tomcat Realm
接口的一个实现,它通过 Java 身份验证与授权服务(JAAS)框架对用户进行身份验证,该框架现在作为标准 Java SE API 的一部分提供。
使用 JAASRealm 使开发人员能够将几乎任何可想象的安全域与 Tomcat 的 CMA(容器管理认证)结合起来。
JAASRealm 是 Tomcat 中基于 JAAS 的 J2EE v1.4 身份验证框架的原型,它基于 JCP 规范请求 196,旨在增强容器管理安全性并推广“可插拔”身份验证机制,其实现将与容器无关。
基于 JAAS 登录模块和 Principal(请参阅javax.security.auth.spi.LoginModule
和javax.security.Principal
),您可以开发自己的安全机制,或包装其他第三方机制以与 Tomcat 实现的 CMA 集成。
快速开始
要设置 Tomcat 以使用 JAASRealm 和您自己的 JAAS 登录模块,您需要遵循以下步骤:
- 编写您自己的基于 JAAS 的 LoginModule、User 和 Role 类(请参阅JAAS Authentication Tutorial和JAAS Login Module Developer's Guide),由 JAAS 登录上下文(
javax.security.auth.login.LoginContext
)管理。在开发您的 LoginModule 时,请注意 JAASRealm 的内置CallbackHandler
目前只识别NameCallback
和PasswordCallback
。 - 尽管 JAAS 中未指定,您应该创建独立的类来区分用户和角色,扩展
javax.security.Principal
,以便 Tomcat 能够识别从您的登录模块返回的哪些 Principal 是用户,哪些是角色(请参阅org.apache.catalina.realm.JAASRealm
)。无论如何,返回的第一个 Principal 总是被视为用户 Principal。 - 将编译后的类放置在 Tomcat 的类路径上
- 为 Java 设置一个 login.config 文件(请参阅JAAS LoginConfig 文件),并通过向 JVM 指定其位置来告诉 Tomcat 在哪里找到它,例如通过设置环境变量:
JAVA_OPTS=$JAVA_OPTS -Djava.security.auth.login.config==$CATALINA_BASE/conf/jaas.config
- 在您的 web.xml 中为您要保护的资源配置 security-constraints
- 在您的 server.xml 中配置 JAASRealm 模块
- 如果 Tomcat 正在运行,请重启它。
域元素属性
要像上述第 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"/>
创建和保存代表用户(javax.security.auth.Subject
)的 Principal(用户和角色对象)是您的登录模块的责任。如果您的登录模块没有创建用户对象,但也没有抛出登录异常,那么 Tomcat CMA 将会中断,您将被留在 http://localhost:8080/myapp/j_security_check URI 或其他未指定的位置。
JAAS 方法的灵活性体现在两个方面:
- 您可以在自己的登录模块中执行所需的任何幕后处理。
- 您可以通过更改配置和重启服务器来插入完全不同的 LoginModule,而无需对应用程序代码进行任何更改。
附加说明
- 当用户首次尝试访问受保护资源时,Tomcat 将调用此
Realm
的authenticate()
方法。因此,您直接在安全机制中进行的任何更改(新用户、更改的密码或角色等)将立即反映出来。 - 一旦用户通过身份验证,用户(及其关联的角色)将在 Tomcat 中缓存,直至用户登录结束。对于基于表单的身份验证,这意味着直到会话超时或失效;对于基本身份验证,这意味着直到用户关闭浏览器。对于已通过身份验证的用户,其安全信息发生任何更改都不会反映,直到该用户下次再次登录。
- 与其他
Realm
实现一样,如果server.xml
中的<Realm>
元素包含digest
属性,则支持摘要密码;JAASRealm 的CallbackHandler
将在将密码传递回LoginModule
之前对其进行摘要。
CombinedRealm
简介
CombinedRealm 是 Tomcat Realm
接口的一个实现,它通过一个或多个子域进行用户身份验证。
使用 CombinedRealm 赋予开发人员组合多个相同或不同类型的 Realm 的能力。这可用于针对不同来源进行身份验证,在某个 Realm 失败时提供备用方案,或用于任何其他需要多个 Realm 的目的。
子域通过在定义 CombinedRealm 的Realm
元素内部嵌套Realm
元素来定义。身份验证将按照列出的顺序尝试对每个Realm
进行。对任何 Realm 的身份验证成功都足以认证用户。
域元素属性
要配置 CombinedRealm,您需要在$CATALINA_BASE/conf/server.xml
文件中的<Engine>
或<Host>
内部创建一个<Realm>
元素并嵌套它。您也可以在context.xml
文件中的<Context>
节点内部嵌套。
示例
这是您的 server.xml 片段应如何使用 UserDatabase Realm 和 DataSource Realm 的示例。
<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 中存在合理的同步程度。
此域不需要修改底层域或相关的用户存储机制。它通过记录所有失败的登录来实现这一点,包括那些针对不存在用户的登录。为了防止通过故意对无效用户发出请求(从而导致此缓存增长)而导致的 DOS 攻击,已失败身份验证的用户列表的大小是受限制的。
子域通过在定义 LockOutRealm 的Realm
元素内部嵌套Realm
元素来定义。身份验证将按照列出的顺序尝试对每个Realm
进行。对任何 Realm 的身份验证成功都足以认证用户。
域元素属性
要配置 LockOutRealm,您需要在$CATALINA_BASE/conf/server.xml
文件中的<Engine>
或<Host>
内部创建一个<Realm>
元素并嵌套它。您也可以在context.xml
文件中的<Context>
节点内部嵌套。LockOutRealm 的属性在Realm配置文档中定义。
示例
这是您的 server.xml 片段如何为 UserDatabase Realm 添加锁定功能的示例。
<Realm className="org.apache.catalina.realm.LockOutRealm" >
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>