本文档介绍了如何借助一个“数据库”来配置 Tomcat ,从而实现容器管理安全性。所要连接的这种数据库含有用户名、密码以及用户角色。你只需知道的是,如果使用的 Web 应用含有一个或多个 <security-constraint>
元素,<login-config>
元素定义了用户验证的必需细节信息。
Realm(安全域)其实就是一个存储用户名和密码的“数据库”再加上一个枚举列表。“数据库”中的用户名和密码是用来验证 Web 应用(或 Web 应用集合)用户合法性的,而每一合法用户所对应的角色存储在枚举列表中。可以把这些角色看成是类似 UNIX 系统中的 group(分组),因为只有能够拥有特定角色的用户才能访问特定的 Web 应用资源(而不是通过对用户名列表进行枚举适配)。特定用户的用户名下可以配置多个角色。
虽然 Servlet 规范描述了一个可移植机制,使应用可以在 web.xml
部署描述符中声明它们的安全需求,但却没有提供一种可移植 API 来定义出 Servlet 容器与相应用户及角色信息的接口。然而,在很多情况下,非常适于将 Servlet 容器与一些已有的验证数据库或者生产环境中已存在的机制“连接”起来。因此,Tomcat 定义了一个 Java 接口(org.apache.catalina.Realm
),通过“插入”组件来建立连接。提供了 6 种标准插件,支持与各种验证信息源的连接:
conf/tomcat-users.xml
)。
conf/tomcat-users.xml
)。
另外,还可以编写自定义 Realm
实现,将其整合到 Tomcat 中,只需这样做:
org.apache.catalina.Realm
接口。
在详细介绍标准 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 应用所共享,除非该 Realm 元素被内嵌入下属 <Host>
或 <Context>
元素的 Realm 元素所覆盖。
<Host>
元素 内嵌入该元素中的这种 Realm 元素可以被这一虚拟主机上的所有 Web 应用所共享。除非该 Realm 元素被内嵌入下属 <Context>
元素的 Realm 元素所覆盖。
<Context>
元素 内嵌入该元素中的这种 Realm 元素只能被这一 Web 应用所使用。
对于每种标准 Realm
实现来说,用户的密码默认都是以明文方式保存的。在很多情况下,这种方式都非常糟糕,即使是一般的用户也能收集到足够的验证信息,从而以其他用户的信息成功登录。为了避免这种情况的发生,标准 Realm
实现支持一种对用户密码进行摘要式处理的机制,它能以无法轻易破解的形式对存储的密码进行加密处理,同时保证Realm
实现仍能使用这种加密后的密码进行验证。
在标准的 Realm 验证时,会将存储的密码与用户所提供的密码进行比对,这时,我们可以通过指定 <Realm>
元素中的digest
属性来选择摘要式密码。该属性值必须是一种java.security.MessageDigest
类所支持的摘要式算法(SHA、MD2、或 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}
。如果盐的长度为 0,迭代次数为 1,则输出将简化为 {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>] <credentials>
-a
用来生成存储凭证的算法。如未指定,将使用凭证处理器(CredentialHandler)的默认值,如果认证处理器和算法均未指定,则使用默认值 SHA-512
。
-e
指定用于任何必要的字节与字符间转换的字符编码方案。如未指定,使用系统默认的字符编码方案Charset#defaultCharset()
。
-i
生成存储的凭证时所使用的迭代次数。如未指定,使用 CredentialHandler 的默认值。
-s
生成并存储到认证中的盐的长度(字节)。如未指定,使用 CredentialHandler 的默认值。
-k
(生成凭证时,如果随带创建了)键的长度(位)。如未指定,则使用 CredentialHandler 的默认值
-h
CredentialHandler 使用的完整类名。如未指定,则轮流使用内建的凭证处理器(MessageDigestCredentialHandler
,然后是 SecretKeyCredentialHandler
),将使用第一个接受指定算法的凭证处理器。
Tomcat 自带的范例应用中包含一个受到安全限制保护的区域,使用表单式登录方式。为了访问它,在你的浏览器地址栏中输入 http://localhost:8080/examples/jsp/security/protected/
,并使用 UserDatabaseRealm 默认的用户名和密码进行登录。
如果你希望使用 Manager 应用在一个运行的 Tomcat 安装上来部署或取消部署 Web 应用,那么必须在一个选定的 Realm 实现上,将 manager-gui 角色添加到至少一个用户名上。这是因为 Manager 自身使用一个安全限制,要想在该应用的 HTML 界面中访问请求 URI,就必须要有 manager-gui 角色。
出于安全性考虑,默认情况下,Realm 中的用户名(比如使用 conf/tomcat-users.xml
)没有被分配 manager-gui 角色。因此,用户起初无法使用这个功能,除非 Tomcat 管理员特意将这一角色分配给他们。
Realm 的容器(Context、Host 及 Engine)所对应的日志配置文件将记录 Realm
所记录下的调试和异常信息。