链接 用户手册 参考 Apache Tomcat 开发 | 快 速 上 手 |
这个文档描述了怎样通过用一个现有"数据库"里的用户名,密码以及角色来配置
Tomcat,从而来支持容器管理的安全性(container managed security)。
如果你使用一个网络程序,而这个程序里包括了一个或多个<security-constraint>
元素,以及一个定义用户怎样认证他们自己的<login-config>元素,那你就需要设置
这些域(Realm)。如果你不使用这些功能,你就可以跳过这个文档。
关于容器管理安全性的基础背景知识,参看
Servlet
规范 (Version 2.4),第12章。
关于使用Tomcat 5的一次性登录特性(允许用户只需认证他自己一次就可以使用和虚拟
主机相关的所有网络程序)的信息。请看这里。
|
概述 |
什么是域(Realm)? |
域(Realm)是一个存储用户名,密码以及和用户名相联系的角色的”数据库”, 用户名,密码用来验证
用户对一个或多个web应用程序的有效性。访问应用程序中特定资源的权限是被授予了拥有特殊角色的用户,
而不是相关的用户名。通过用户名相关联,一个用户可以有任意数量的角色。
尽管Servlet规范描述了一个可以让应用程序声明它们安全性要求(在web.xml
部署描述符里)的机制,但是并没有的API(应用编程接口)来定义一个基于servlet容器和其
相关用户角色之间的界面(interface)。然而在许多情况下,最好能把一个servlet容器和那些已经存在
的认证数据库或机制“连接”起来。因此,Tomcat 5定义了一个
Java界面(org.apache.catalina.Realm ),它可以通过"插件"来实现
这种连接。这里提供了五个标准的插件,用来支持与各个认证信息来源的连接:
- JDBCRealm - 通过JDBC驱动来访问贮存在关系数据库里的认证信息。
- DataSourceRealm - 通过一个叫做JNDI JDBC 的
数据源(DataSource)来访问贮存在关系数据库里的认证信息。
- JNDIRealm - 通过JNDI provider来访问贮存在基于
LDAP(轻量目录访问协议)的目录服务器里的认证信息。
- MemoryRealm - 访问贮存在电脑内存里的认证信息,它是通过一个
XML文件(
conf/tomcat-users.xml )来进行初始化的。
- JAASRealm -
使用 Java Authentication & Authorization Service (JAAS).
也可以编写你自己的Realm implementation,然后与Tomcat整合在一起。你需要:
- 实现
org.apache.catalina.Realm
- 把编译过的文件放置到$CATALINA_HOME/server/lib里边
- 象下面的"配置一个域(Realm)" 章节里描述的那样声明你的realm。
- 用MBeans 描述符声明你的realm。
|
|
共有特性 |
Tomcat管理员程序 |
如果你想要使用管理员程序(Manager Application)
在运行的Tomcat安装里部署和删除应用程序,你必须在你选择的Realm实现中至少给一个用户名添加
"manager"角色。这是因为管理员网络应用程序本身使用一个安全限制,
需要"manager"的角色在这个程序里面访问任何请求URI。
因为安全的原因,在默认的Realm里(i.e. 使用
conf/tomcat-users.xml 文件)没有用户名被指派"manager"角色。
因此,在Tomcat administrator特别地给一个或几个用户指派这个角色之前,没有人能够利用这个程序。
|
|
Tomcat现有的标准域(Realm) |
JNDIRealm |
简介
DataSourceRealm 是一个Tomcat 5 Realm界面实现,它通过一个JNDI provider
来访问查询LDAP目录里的用户信息(通常是与JNDI API类一起的标准LDAP
provider)。这个realm支持一系列使用目录来进行认证的方法。
与目录相连接
这个realm与目录的连接是由connectionURL配置属性定义的。这个URL的
格式又是由JNDI provider来定义的。它通常是一个LDAP URL,用来指定相连接的目录服务器的领域
名称,或者端口数码以及所需的root naming context的特殊名称。
如果你有不止一个供应者(provider),你可以配置一个alternateURL。如果
不能在connectionURL与供应者连接上,机器就会试图使用alternateURL来连接。
在为了查询目录和搜索用户及其角色进行连接的时候,这个realm就通过connectionName
和 connectionPassword 属性指定的用户名称和密码来向这个目录认证它自己。
如果属性没有被指定,连接就是无名的。在许多情形下,这也足够了。
选择用户的目录进入
每一个能被认证的用户必须在目录里由一个单个登录(individual entry)来代表,这个单个登录与
初始DirContext 里边被connectionURL属性定义的一个元素相
对应。这个用户登录必须有一个包含认证用的用户名称的属性(attribute).
通常这个用户登录的特殊名称包含提供给认证用的用户名,否则所有用户都一样。在这种情况下,
userPattern这个属性也许可用来指定DN(特殊名称),用"{0}"
来标记用户名在什么地方可被替代。
否则这个realm必须搜寻(search)目录才能找到包含用户名称的独特的登录。这样的搜寻由下面的这些
属性配置:
- userBase - 这个输入是作为包含用户信息subtree的基础。如果没有
指定,search base是最上层context。
- userSubtree - 搜寻范围。如果你想搜寻根基于
userBase entry的整个subtree,把它设置为
true 。
默认的值false 要求仅仅包括上层的单层搜寻。
- userSearch - 指定要使用的LDAP搜寻过滤器的模式
认证用户
-
绑定模式
在默认的情况下,realm通过与带有用户和用户密码的目录绑定在一起来认证一个用户。如果这个简单
的绑定成功的话,用户就被认为被认证过了。
因为安全的原因,目录会储存digest形式的用户密码,而不是明码版本(更多信息请看
Digested 密码)。在这种情况下,作为简单绑定操作的一部分,
目录在与储存的值核对之前自动把用户提供的正文密码换算成正确的digest。因此在绑定模式里,域(realm)
没有参与digest过程。这个digest属性没有被使用,即使被设置了,也被忽略掉。
-
比较模式
另外,域(realm)可以从目录中取出储存的密码并与用户提供的值做直接比较。通过把
userPassword属性设定为带有密码的用户输入里的一个
directory attribute的名字来配置这个模式。
比较模式有一些不利的地方。第一,connectionName 和
connectionPassword 属性必须配置能让realm去阅读在目录里的用户密码。
因为安全的原因,这通常是不可取的,事实上,许多目录implementations不允许,即使是目录管理员
也不允许阅读这些密码。另外,realm必须自己处理password digests,包括使用运算法则的变化,
和在目录里表示密码hashes的方法。不过,realm有时也需要访问储存的密码,例如,要支持
HTTP Digest Access Authentication (RFC 2069)。(注意,HTTP digest认证不同于上面所
讨论的在用户信息储藏室里的password digests储存)。
给用户指派角色
目录域(realm)支持下列两种方法来在目录里代表角色:
这两种代表角色方法的组合也可以被使用。
快 速 上 手
要设置好Tomcat来使用JNDIRealm,你必须遵守下面这些步骤:
- 确保你的目录服务器是依据上面的要求配置的。
- 如果需要,配置一个可供Tomcat使用的用户名称和密码,它应该有只读(read only)权限来访问
上述表格。(Tomcat从不会试图去改写这些表格)。
- 把你将要使用的JNDI驱动(通常
ldap.jar available with JNDI)复制件放置
到$CATALINA_HOME/server/lib目录(如果你不想让网络程序使用它的话)或者放在
$CATALINA_HOME/common/lib(如果Tomcat 5和你的程序都要使用它的话)。
- 象下面所描述的那样,在你的$CATALINA_HOME/conf/server.xml文件设置一个
<Realm>元素。
- 如果Tomcat 5已经在运行,重新启动它。
Realm元素属性
为了配置一个 JNDIRealm, 你必须如上面描述的那样在
$CATALINA_HOME/conf/server.xml 文件里产生一个<Realm> 元素
该元素支持的属性可在Realm 配置里面找到。
例子
因为每个目录服务器的实现都不同,在你的目录服务器里产生相适宜的schema超出了本文的范围。在
下面的例子里,我们假设你使用的是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 provider的信息,请参看http://java.sun.com/products/jndi/docs.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 | | | | |
假设用户使用他们的uid (e.g. jjones)来登入应用程序,并且无名的连接足够用来搜索目录和取得
角色信息,如上面所描述配置的OpenLDAP目录服务器的Realm 元素示例看起来象这样:
| | | | <Realm className="org.apache.catalina.realm.JNDIRealm" debug="99"
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})"
/> | | | | |
在这样的配置下,realm通过把用户名替代到userPattern 里去来决定用户的特殊的
唯一名字,通过把这个特殊的唯一名字(DN)和用户提供的密码与目录绑定来认证,并搜索目录来发现用户的角色。
现在假设用户在登入时使用他们的email地址,而不是他们的userid。在这种情况下,realm必须搜索
目录来寻找用户的输入。(当用户输入被放在与不同的组织单位或公司位置相对应的多个subtrees里时,
搜索也是必要的)。
进一步,假设除了群体输入外,你想要使用关于用户输入的一个属性来保存角色信息。这里,关于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" debug="99"
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在目录里搜索具
有那个值作为它的mail属性的独特输入,并以所给的密码,及以下uid=jjones,ou=people,dc=mycompany,dc=com
与目录绑定。如果认证成功,她就被指派给三个角色:"role2" and "role3"分别作为
她的目录输入里"memberOf"属性的值,"tomcat"作为唯一一个群体(她是其中
一个成员)输入"cn"属性的值。
最后,要想通过从目录中取得密码,并在realm里做局部比较来认证,你也许需要使用象这样的一个realm配置:
| | | | <Realm className="org.apache.catalina.realm.JNDIRealm" debug="99"
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 5会调用这个
Realm
的authenticate() 方法。因此,你在目录里的任何更改(新用户,更改过的密码或角色,
等等)会立即被反映出来。
- 一旦用户被认证过,这个用户(及其相关的角色)在这个用户登录期间被高速缓存(cached)在Tomcat
里。(对于基于-表格(FORM-based)的认证,这意味着直致会话时间结束或无效;对于BASIC认证,这
意味着用户关闭他们的浏览器)。被缓存的用户在会话系列之间不
被保存和再贮存。一个已经被认证过的用户的任何目录信息的更改,直到下次用户再登录时才会被反映出来。
- 管理目录服务器里的信息是你的程序自身的责任。Tomcat不提供任何内在的功能来维持用户及角色信息。
|
UserDatabaseRealm |
简介
UserDatabaseRealm 是一个简单的Tomcat 5 Realm界面实现。所有用户和
他们的角色信息都来自于 JNDI 资源。这个 JNDI资源实现的是UserDatabase 界面。
Realm元素属性
为了配置一个 UserDatabaseRealm, 你必须如上面描述的那样在
$CATALINA_HOME/conf/server.xml 文件里产生一个<Realm> 元素
该元素支持的属性可在Realm 配置里面找到。
例子
缺省安装的Tomcat 5 配置的 UserDatabaseRealm 嵌套在<Engine> 元素内,
所以它适用于所有虚拟主机和web应用。这个域(realm)使用与MemoryRealm相同的
tomcat-users.xml 。
|
JAASRealm |
简介
JAASRealm 是一个通过Java
Authentication & Authorization Service (JAAS)框架认证用户的Tomcat 4 Realm 接口
的实现,而这个JAAS是一个Java软件包,它在Java 2 SDK 1.3里是作为选择性软件包被使用,在
SDK 1.4里它则完全整合在其中了。
使用JAASRealm给予开发人员这种能力,那就是把任何可想象的安全realm与 Tomcat
的CMA组合在一起。
JAASRealm是基于JAAS的J2EE 1.4版本的J2EE认证框架的原型(prototype),它是在
JCP Specification Request 196 基础上来加强容器-管理的安全性,并促进不依赖于容器
的实现的'pluggable'认证机制。 JCP Specification Request 196
在JAAS登录模块和原则(参看javax.security.auth.spi.LoginModule
和javax.security.Principal )的基础上,你可以开发你自己的安全机制或者把第三方
的机制与Tomcat执行的CMA整合包裹在一起。
快 速 上 手
要设置好Tomcat来与你自己JAAS login模块一起使用JAASRealm,你必须遵守下面这些步骤:
- 在JAAS (参看the JAAS Authentication Tutorial 和 the JAAS Login
Module Developer's Guide)基础上编写你自己的由JAAS Login Context
(
javax.security.auth.login.LoginContext ) 管理的 LoginModule,
User 和Role 类。在开发你的LoginModule时,注意JAASRealm自身具有的
CallbackHandler 目前仅仅能识别NameCallback
和PasswordCallback 。
the
JAAS Authentication Tutorial the
JAAS Login Module Developer's Guide
- 尽管JAAS里没有指明,但你还是应该通过扩展
javax.security.Principal 产生
不同的类来区分用户和角色,这样Tomcat可以从你的login module返回的Principals中区别
哪个是用户,哪个是角色(参看org.apache.catalina.realm.JAASRealm )。
不然的话,返回的第一个Principal总是被当作用户Principal。
- 把编译过的类放置在Tomcat的classpath上。
- 设置一个Java的login.config文件(参看JAAS LoginConfig file),并对JVM指明
这个文件的位置告诉Tomcat到哪里去找到它。例如,可以把环境变量设置如下:
JAVA_OPTS=-DJAVA_OPTS=-Djava.security.auth.login.config==$CATALINA_HOME/conf/jaas.config 。
JAAS LoginConfig file
JAVA_OPTS=-DJAVA_OPTS=-Djava.security.auth.login.config==$CATALINA_HOME/conf/jaas.config
- 在你的web.xml文件里为你想要保护的资源配置安全-限制(security-constraints) 。
- 在你的server.xml文件里配置JAASRealm模块(module)。
- 如果Tomcat 5已经在运行,重新启动它。
Realm元素属性
为了配置一个 JAASRealm, 你必须如上面描述的那样在
$CATALINA_HOME/conf/server.xml 文件里产生一个<Realm> 元素
该元素支持的属性可在Realm 配置里面找到。
例子
你的server.xml文件看起来应该象这个例子这样。
| | | | <Realm className="org.apache.catalina.realm.JAASRealm"
appName="MyFooRealm"
userClassNames="org.foobar.realm.FooUser"
roleClassNames="org.foobar.realm.FooRole"
debug="99"/> | | | | |
是你的login模块的责任来产生并保存代表用户的Principals的( javax.security.auth.Subject )
User和Role对象(objects)。如果你的login模块没有产生一个user object ,也没有抛出一个login异常,
那么Tomcat CMA 会终断,你会被停留在http://localhost:8080/myapp/j_security_check URI 或其他未指定的位置。
JAAS方法的可塑性有两层:
- 你可以在你自己的login模块里实现任何你需要的程序处理。
- 不用对你的应用程序编码有任何更改,你可以通过更改配置并重新启动服务器来插入(plug in)一个完全
不同的LoginModule。
另外的注意事项
- 当一个用户第一次试图访问一个被保护的资源,Tomcat 5会调用这个
Realm 的
authenticate() 方法。这样一来,你在这个安全机制里做的任何更改(新用户,更改密码
和角色,等等)会立即被反映出来。
- 一旦用户被认证过,这个用户(及其相关的角色)在这个用户登录期间被零时缓存(cached)在Tomcat里。
对于基于-表格(FORM-based)的认证,这意味着直致会话时间结束或无效;对于BASIC认证,这意味着直到
用户关闭他们的浏览器。对于被认证过的用户,任何安全信息更改在这个用户再次登录之前都不
会被反映出来。
- 和其他
Realm 实现一样,如果server.xml 里面的
<Realm> 元素包含有一个digest 属性,digested密码就被支持;
JAASRealm 的 CallbackHandler 在把密码传递回LoginModule
之前会先digest密码。
|
|
|