回顾
一个web应用程序的一些内容是受限的,只有在用户输入了正确的用户名和密码后,经过验证后才被允许访问它们。servlet技术支持对这些内容申请安全约束,通过部署描述符的配置(web.xml文件)。现在在这一章节中,我们将看到web应用如何支持安全约束功能。
一个servlet容器通过一个叫做authenticator的value支持安全约束。在servlet容器启动的时候,authenticator 值被添加到上下文的管道中。
这个authentictor值在wrapper值调用前被调用。这个值校验用户。如果用户输入了正确的用户名和密码,其调用下一个值,这个值显示了请求的servlet。如果验证失败,authentictor值不调用下一个value返回。结果是,用户看不到请求的servlet。
authentictor value 调用context的 realm的authenticate方法来校验用户,通过传递的用户名和密码。一个realm有权访问有效用户名和密码的集合。
这章以安全相关的类开始,比如:realms,principals,roles等等。之后演示了一个简单的校验功能。
Realm
一个Realm是一个用于校验用户的组件。它能告诉你用户名和密码是否有效。一个Realm通常依附在一个context上,一个容器只能由一个realm。通过Container的setRealm方法使一个realm依附到一个容器上。
realm如何知道校验用户了?当然了,它含有全部有效的用户名和密码或者能够访问持有用户名和密码的存储。这个信息存储在哪里取决于realm的实现。在Tomcat中,默认的有效用户存储在tomcat-users.xml中。可是,你可以使用其他的realm实现,从其他来源校验,比如关系型数据库。
在Catalina中,realm是由Realm接口实现的。有四个需要重载的方法最重要。
public Principal authenticate(String username, String credentials);
public Principal authenticate(String username, byte[] credentials);
public Principal authenticate(String username, String digest,
String nonce, String nc, String cnonce, String qop, String realm,
String md5a2);
public Principal authenticate(X509Certificate certs[]);
第一个经常用到。Realm接口也有hasRole方法,其签名如下:
public boolean hasRole(Principal principal, String role);
getContainer和setContainer方法用于使一个realm与一个Container关联。
RealBase类是Realm接口的抽象类。org.apache.catalian.realm包也提供了大量的扩展RealmBase的实现类:JDBCRealm,JNDIRealm,MemoryRealm和UserDatabaseRealm。默认的使用MemoryRealm。当MemoryRealm第一次启动的时候,它读取tomcat-users.xml中的数据。在本章中, 你讲构建一个简单的realm,其在其对象本身中存储用户信息。
GenericPrincipal
Principal接口由java.security.Principal接口实现。它的实现类是org.apache.catalian.realm.GenericPrincipal类。一个GenericPrincipal必须总是与一个realm关联,如同GenericPrincipal的两个构造器显示的一样:
public GenericPrincipal(Realm realm, String name, String password) {
this(realm, name, password, null);
}
public GenericPrincipal(Realm realm, String name, String password,
List roles) {
super();
this.realm = realm;
this.name = name;
this.password = password;
if (roles != null) {
this.roles = new String[roles.size()];
this.roles = (String[]) roles.toArray(this.roles);
if (this.roles.length > 0)
Arrays.sort(this.roles);
}
}
GenericPrincipal也必须有一个用户名和密码。作为可选的,你也可以传递一组角色给它。你稍后调用它的hasRole方法校验这个当事人是否有一个指定的角色,传递role的字符串表达式。
下面是Tomcat 4的hasRole方法的代码片段:
public boolean hasRole(String role) {
if (role == null)
return (false);
return (Arrays.binarySearch(roles, role) >= 0);
}
在Tomcat 5中 支持Servlet 2.4 并认可特殊字符“*”代表任何角色。
public boolean hasRole(String role) {
if ("*".equals(role)) // Special 2.4 role meaning everyone
return true;
if (role == null)
return (false);
return (Arrays.binarySearch(roles, role) >= 0);
}
LoginConfig 登陆配置
一个Login 配置包含了一个realm名字并由org.apache.catalian.deploy.LoginConfig final类表示。LoginConfig实例封装了realm名字和使用的校验方法。你可以调用Loginconfig实例的getRealmName方法并调用它的getAuthName方法获取realm的名字。校验名的值必须是下面之一:BASIC,DIGEST,FORM,或者CLIENT-CERT。
如果基于形式的校验使用了,LoginConfig对象也包含了URLs的字符串表达式,分别指向loginPage和errorPage的登陆和错误页面。
在一个部署中,Tomcat 在启动的时候读取web.xml文件。如果这个文件包含一个login-config标签,Tomcat创建LoginConfig对象并设置它相符的属性。校验器值调用LoginConfig的getRealmName方法的并发送realm的名字到浏览器,用来在登陆框中显示。如果getRealmName返回的是null,服务器的名字和端口发送到浏览器。
Authentictor 校验器
org.apache.catalian.Authentictor接口表示一个Authentictor。它没有一个方法,扮演一个标识。这样其他的组件可以通过instanceof发现一个组件是否是一个校验器。
Catalian提供了Authentictor接口的基本实现:org.apache.catalian.authentictor.AuthentictorBase类。除了实现了Authentictor接口,还有org.apache.catalian.values.ValueBase类。意味着:AuthentictorBase也是一个Value。大量的实现类存在于org.apache.catalian.Authentictor包中,包括BaseAuthentictor类,用于基本校验,FormAuthentictor类用于form-based校验。DigestAuthentictor类用于数字校验。SSLAuthentictor用于SSL校验。另外,NonLoginAuthentictor类用于如果Tomcat的用户没有指定auth-method元素的值。NonLoginAuthentictor类表示一个校验器只能检查安全约束而不能检查用户校验。
UML图表示otg.apache.catalian.Authentictor包类的关系:
图10.2:表示校验器相关类
校验器的主要工作是校验用户。因此,调用AuthentictorBase类的invoke方法调用其抽象方法Authenticate,其实现取决于继承它的子类。例如在BasicAuthentictor,authenticate方法是用基本的校验用户。