概述
一些web 应用内容是受限的,只有在有特定权限的用户输入正确的用户名和密码后才能访问。Servlet 通过配置部署文件 web.xml 来对安全性提供技术支持。本章将介绍 container 如何支持安全性控制。
servlet 容器通过一个称为 authenticator 的阀门(valve)来支持安全认证。当 container 启动时,authenticator 被添加到container 的 pipeline中。如果忘记了pipeline工作机制,请阅读第6章。
在 wrapper阀门被调用之前,会先调用 authenticator阀门,用来对用户进行认证。若用户输入了正确的用户名密码,则 authenticator 会调用下一个阀门,否则会直接返回,不再继续执行剩余的阀门。由于验证失败,用户并不能看到请求的 servlet。
在用户验证时, authenticator 阀门调用的是上下文域(realm)内的authenticate()方法,将用户名和密码传递给它。领域(Realm)可以访问有效用户名和密码的集合。
本章会先介绍一些 servlet 编程中与安全相关的对象(realm,role,principal 等),然后通过一个应用程序来演示如何为servlet 添加基本的认证功能。
注意:这里假设大家已经熟悉 servlet 编程安全性的相关概念,包括: principals, roles, realms, login configuration等。如果对这些概念还不清楚可以阅读《Java for the Web with Senlets, JSP,and EJB》或者其它相关书籍。
10.1 领域(Realm)
域(Realm)是用于进行用户验证的一个组件。它验证一用户名&密码是否是合法。一个域跟一个上下文容器相联系,并且一个容器只有一个域。可以使用容器的 setRealm()方法来建立它们之间的联系。
一个域是如何验证一个用户的合法性的昵?一个域拥有所有的合法用户的用户名和密码或
者是可以访问到存储它们的地方。至于它们存放在哪里则取决于域的具体实现。在 Tomcat默认实现里,合法用户被存储在 tomcat-users.xml 文件里。但是,也可以使用域的其它实现,如关系数据库。
在 Catalina 中,一个域用接口 org.apache.catalina.Realm 表示。该接口最重要的方法是4个 重载authenticate()方法:
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 接口还有一个 getRole()方法,签名如下:
public boolean hasRole(Principal principal, String role);
另外,域还有 getContainer() 和 setContainer() 方法用于建立域与容器关联。
抽象类 org.apache.catalina.realm.RealmBase是一个域的基本实现。org.apache.catalina.realm 包中还提供了其它继承了 RealmBase的一些实现类,如:JDBCRealm, JNDIRealm, MemoryRealm和 UserDatabaseRealm。默认情况下使用的域是 MemoryRealm。当MemoryRealm首次启动时,它会读取tomcat-users.xml文件。在本章Demo中,将会自定义一简单的Realm来存储用户信息。
注意:在 Catalina 中,authenticator阀门调用相关域的 authenticate()方法来验证一个用户。
10.2 GenericPrincipal类
一个主体(principal)使用 java.security.Principal 接口来表示,Catalina中的实现类是 org.apache.catalina.realm.GenericPrincipal。一个GenericPrincipal 必须跟一个域相关联,如下是GenericPrincipal的2个构造函数:
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 必须拥有一个用户名和一个密码,此外还可选择性的传递一角色列表(List roles)给它。我们可以使用 hasRole()方法来检查一个 principal 是否有一个特定的角色,传递的参数为角色的字符串表示形式。如下是 Tomcat4 中的 hasRole() 方法:
public boolean hasRole(String role) {
if (role == null)
return (false);
return (Arrays.binarySearch(roles, role) >= 0);
}
Tomcat5 支持 servlet2.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);
}
10.3 LoginConfig类
一个登录配置包括一个域名,这里是通过org.apache.catalina.deploy.LoginConfig 不变类表示。LoginConfig 的实例封装了域名和验证要用的方法。可以通过LoginConfig实例的 getRealmName()方法来获得域名,可以使用 getAuthName()方法来验证用户。一个验证的名字必须是下面的之一:BASIC, DIGEST, FORM或者 CLIENT-CERT。如果用到的是基于表单(form)的验证,该 LoginConfig 对象还包括登录或者错误页面像对应的 URL。
Tomcat 在启动时,会先读取 web.xml。如果 web.xml 包括一个login-config元素,那么Tomcat 则会创建一 LoginConfig 对象并相应地设置它的属性。验证阀门调用 LoginConfig 的 getRealmName() 方法并将域名发送给浏览器显示登录表单。如果 getRealmName()名返回值为 null,则将发送服务器的名字和端口名给浏览器作为代替。图 10.1 是在Window XP 上 IE6 浏览器上显示的基本验证会话窗。
图10.1: The basic authentication dialog
10.4 验证器(Authenticator)
org.apache.catalina.Authenticator 接口用来表示一个验证器。该接口并没有方法,只是一个组件的标志器,这样就能使用 “instanceof”来检查一个组件是否为验证器。
Catalina 提供了 Authenticator 接口的基本实现:org.apache.catalina.authenticator.AuthenticatorBase 类。除了实现Authenticator 接口外,AuthenticatorBase 还继承了org.apache.catalina.valves.ValveBase 类。这就是说 AuthenticatorBase 也是一个阀门。可以在 org.apache.catalina.authenticator 包中找到该接口的几个类:BasicAuthenticator 用于基本验证, FormAuthenticator 用于基于表单的验证, DigestAuthentication 用于摘要(digest)验证, SSLAuthenticator 用于 SSL 验证。NonLoginAuthenticator 用于 Tomcat 没有指定验证元素的时候。NonLoginAuthenticator 类表示只是检查安全限制的验证器,但是不进行用户验证。
org.apache.catalina.authenticator 包中类的 UML 结构图如图 10.2 所示:
图10.2: Authenticator-related classes
一个验证器的主要工作是验证用户。因此AuthenticatorBase 类的 invoke 方法调用了抽象方法 authenticate()也不足为奇了,该方法的具体实现由子类完成。例如,在BasicAuthenticator中,authenticate()方法使用基本认证来认证用户。
10.5 安装验证器阀门
在部署文件中,只能出现一个 login-config 元素,login-config 元素包括了auth-method 元素用于定义验证方法。这也就是说一个上下文容器只能有一个LoginConfig 对象来使用一个 authentication 的实现类。
具体AuthenticatorBase 的子类哪一个在上下文中被用作验证阀门,这依赖于部署文件中auth-method 元素的值。表 10.1 为 auth-method 元素的值,可以用于确定验证器。
表10.1: The authenticator implementation class
Value of the auth-method element | Authenticator class |
---|---|
BASIC | BasicAuthenticator |
FORM | FormAuthenticator |
DIGEST | DigestAuthenticator |
CLIENT-CERT | SSLAuthenticator |
如果没有使用 auth-method 值,则认为 LoginConfig 对象的 auth-method 属性值为NONE,那么NonLoginAuthenticator类将被使用。
因为验证器类只在运行时知道,所以类是动态加载的。 StandardContext类使用org.apache.catalina.startup.ContextConfig类来配置StandardContext实例的许多设置。 此配置包括实例化验证器类并将该实例与上下文相关联。本章应用Demo附带着一个简单的上下文配置类:ex10.pyrmont.core.SimpleContextConfig。 正如你所见,这个类的实例负责动态加载BasicAuthenticator类,实例化它,并将其安装为StandardContext实例中的一个阀门。
注意:我们将在第15章讨论org.apache.catalina.startup.ContextConfig类。
10.6 应用Demo
本章附带的应用程序中使用了几个与安全约束相关的Catalina类。还使用了类似于第9章中的SimplePipeline,SimpleWrapper和SimpleWrapperValve类。此外,SimpleContextConfig类与第9章中的SimpleContextConfig类也相似,除了它具有authenticatorConfig()方法,该方法向StandardContext添加一个BasicAuthenticator实例 。 这2个应用Demo还使用了PrimitiveServlet和ModernServlet。 这些类在2个附带的应用Demo中使用。
第1个Demo中使用ex10.pyrmont.startup.Bootstrap1和ex10.pyrmont.realm.SimpleRealm 2个类;第二个Demo中使用了ex10.pyrmont.startup.Bootstrap2 ex10.pyrmont.realm.SimpleUserDatabaseRealm。每个类将在下面子章节中介绍。
10.6.1 ex10.pyrmont.core.SimpleContextConfig类
Listing10.1中的SimpleContextConfig类与第9章中的SimpleContextConfig类似。org.apache.catalina.core.StandardContext实例需要将其配置属性设置为true。 但是,本章中的SimpleContextConfig类添加了从lifeCycleEvent()方法调用的authenticatorConfig()方法。 authenticatorConfig()方法实例化BasicAuthenticator类,并将其作为阀门添加到StandardContext实例管道中。
Listing 10.1: The SimpleContextConfig class
package ex10.pyrmont.core;
import org.apache.catalina.Authenticator;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.deploy.LoginConfig;
public class SimpleContextConfig implements LifecycleListener {
private Context context;
public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.START_EVENT.equals(event.getType())) {
context = (Context) event.getLifecycle();
authenticatorConfig();
context.setConfigured(true);
}
}
private synchronized void authenticatorConfig() {
// Does this Context require an Authenticator?
SecurityConstraint constraints[] = context.findConstraints();
if ((constraints == null) || (constraints.length == 0))
return;
LoginConfig loginConfig = context.getLoginConfig();
if (loginConfig == null) {
loginConfig = new LoginConfig("NONE", null, null, null);
context.setLoginConfig(loginConfig);
}
// Has an authenticator been configured already?
Pipeline pipeline = ((StandardContext) context).getPipeline();
if (pipeline != null) {
Valve basic = pipeline.getBasic();
if ((basic != null) && (basic instanceof Authenticator))
return;
Valve valves[] = pipeline.getValves();
for (int i = 0; i < valves.length; i++) {
if (valves[i] instanceof Authenticator)
return;
}
}
else { // no Pipeline, cannot install authenticator valve
return;
}
// Has a Realm been configured for us to authenticate against?
if (context.getRealm() == null) {
return;
}
// Identify the class name of the Valve we should configure
String authenticatorName = "org.apache.catalina.authenticator.BasicAuthenticator";
// Instantiate and install an Authenticator of the requested class
Valve authenticator = null;
try {
Class authenticatorClass = Class.forName(authenticatorName);
authenticator = (Valve) authenticatorClass.newInstance();
((StandardContext) context).addValve(authenticator);
System.out.println("Added authenticator valve to Context");
}
catch (Throwable t) {
}
}
}
开始,authenticatorConfig()方法检查在相关联的上下文中是否存在安全约束。如果没有,则该方法直接返回而不安装验证器。
// Does this Context require an Authenticator?
SecurityConstraint constraints[] = context.findConstraints();
if ((constraints == null) || (constraints.length == 0))
return;
如果存在安全约束,则继续检测上下文中是否存在LoginConfig对象,如果不存在,则创建一个:
LoginConfig loginConfig = context.getLoginConfig();
if (loginConfig == null) {
loginConfig = new LoginConfig("NONE", null, null, null);
context.setLoginConfig(loginConfig);
}
然后,authenticatorConfig()方法检查StandardContext对象管道中的基本阀门或附加阀门是否是验证器。 由于上下文只能有一个验证器,所以如果其中一个阀门是认证器,则authenticatorConfig()方法将返回。
// Has an authenticator been configured already?
Pipeline pipeline = ((StandardContext) context).getPipeline();
if (pipeline != null) {
Valve basic = pipeline.getBasic();
if ((basic != null) && (basic instanceof Authenticator))
return;
Valve valves[] = pipeline.getValves();
for (int i = 0; i < valves.length; i++) {
if (valves[i] instanceof Authenticator)
return;
}
}else { // no Pipeline, cannot install authenticator valve
return;
}
然后它检查领域(Realm)是否已与上下文相关联。 如果没有发现领域,则不需要安装验证器,因为用户不能被认证:
// Has a Realm been configured for us to authenticate against?
if (context.getRealm() == null) {
return;
}
此时,authenticatorConfig()方法将动态加载BasicAuthenticator类,创建该类的实例,并将其作为阀门添加到StandardContext实例:
// Identify the class name of the Valve we should configure
String authenticatorName = "org.apache.catalina.authenticator.BasicAuthenticator";
// Instantiate and install an Authenticator of the requested class
Valve authenticator = null;
try {
Class authenticatorClass=Class.forName(authenticatorName);
authenticator = (Valve) authenticatorClass.newInstance();
((StandardContext) context).addValve(authenticator);
System.out.println("Added authenticator valve to Context");
}catch (Throwable t) {}
10.6.2 ex10.pyrmont.realm.SimpleRealm类
Listing 10.2中的SimpleRealm类演示了领域是如何工作。这个类在本章的第一个Demo中使用,包含两个硬编码的用户名和密码。
Listing 10.2: The SimpleRealm Class
package ex10.pyrmont.realm;
import java.beans.PropertyChangeListener;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.catalina.Container;
import org.apache.catalina.Realm;
import org.apache.catalina.realm.GenericPrincipal;
public class SimpleRealm implements Realm {
public SimpleRealm() {
createUserDatabase();
}
private Container container;
private ArrayList users = new ArrayList();
public Container getContainer() {
return container;
}
public void setContainer(Container container) {
this.container = container;
}
public String getInfo() {
return "A simple Realm implementation";
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
}
public Principal authenticate(String username, String credentials) {
System.out.println("SimpleRealm.authenticate()");
if (username==null || credentials==null)
return null;
User user = getUser(username, credentials);
if (user==null)
return null;
return new GenericPrincipal(this, user.username, user.password, user.getRoles());
}
public Principal authenticate(String username, byte[] credentials) {
return null;
}
public Principal authenticate(String username, String digest, String nonce,
String nc, String cnonce, String qop, String realm, String md5a2) {
return null;
}
public Principal authenticate(X509Certificate certs[]) {
return null;
}
public boolean hasRole(Principal principal, String role) {
if ((principal == null) || (role == null) ||
!(principal instanceof GenericPrincipal))
return (false);
GenericPrincipal gp = (GenericPrincipal) principal;
if (!(gp.getRealm() == this))
return (false);
boolean result = gp.hasRole(role);
return result;
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
}
private User getUser(String username, String password) {
Iterator iterator = users.iterator();
while (iterator.hasNext()) {
User user = (User) iterator.next();
if (user.username.equals(username) && user.password.equals(password))
return user;
}
return null;
}
private void createUserDatabase() {
User user1 = new User("ken", "blackcomb");
user1.addRole("manager");
user1.addRole("programmer");
User user2 = new User("cindy", "bamboo");
user2.addRole("programmer");
users.add(user1);
users.add(user2);
}
class User {
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String username;
public ArrayList roles = new ArrayList();
public String password;
public void addRole(String role) {
roles.add(role);
}
public ArrayList getRoles() {
return roles;
}
}
}
SimpleRealm类实现了Realm接口。 在构造函数中调用createUserDatabase()方法创建2个用户。 在内部,用户由内部类User表示。 第一个用户具有用户名ken和密码blackcomb。 这个用户有两个角色:manager和programmer。 第二个用户的用户名和密码分别为cindy和bamboo。 这个用户拥有programmer的角色。 然后,这2个用户被添加到变量users中。代码如下:
User user1 = new User("ken", "blackcomb");
user1.addRole("manager");
user1.addRole("programmer");
User user2 = new User("cindy", "bamboo");
user2.addRole("programmer");
users.add(user1);
users.add(user2);
SimpleRealm类提供了四种重载验证方法中的1个实现:
public Principal authenticate(String username, String credentials) {
System.out.println("SimpleRealm.authenticate()");
if (username==null || credentials==null)
return null;
User user = getUser(username, credentials);
if (user==null)
return null;
return new GenericPrincipal(this, user.username, user.password, user.getRoles());
}
此authenticate()方法由验证器调用。 如果用户名和密码作为参数传递的用户不是有效的用户,则返回null。否则,它返回表示用户的Principal对象。
10.6.3 ex10.pyrmont.realm.SimpleUserDatabaseRealm类
SimpleUserDatabaseRealm类表示更复杂的领域。 它没在代码中存储用户列表。相反,它读取conf目录中的tomcat-users.xml文件,并将内容加载到内存中。然后针对该列表进行认证。 在随附zip文件的conf目录中,可以找到tomcat-users.xml文件的副本,如下所示:
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="tomcat"/>
<role rolename="role1"/>
<role rolename="manager"/>
<role rolename="admin"/>
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="role1" password="tomcat" roles="role1"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="admin" password="password" roles="admin,manager"/>
</tomcat-users>
类SimpleUserDatabaseRealm 代码如Listing 10.3:
Listing 10.3: The SimpleUserDatabaseRealm class
package ex10.pyrmont.realm;
// modification of org.apache.catalina.realm.UserDatabaseRealm
import java.security.Principal;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.catalina.Group;
import org.apache.catalina.Role;
import org.apache.catalina.User;
import org.apache.catalina.UserDatabase;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.apache.catalina.users.MemoryUserDatabase;
public class SimpleUserDatabaseRealm extends RealmBase {
protected UserDatabase database = null;
protected static final String name = "SimpleUserDatabaseRealm";
protected String resourceName = "UserDatabase";
public Principal authenticate(String username, String credentials) {
// Does a user with this username exist?
User user = database.findUser(username);
if (user == null) {
return (null);
}
// Do the credentials specified by the user match?
// FIXME - Update all realms to support encoded passwords
boolean validated = false;
if (hasMessageDigest()) {
// Hex hashes should be compared case-insensitive
validated = (digest(credentials).equalsIgnoreCase(user.getPassword()));
}
else {
validated = (digest(credentials).equals(user.getPassword()));
}
if (!validated) {
return null;
}
ArrayList combined = new ArrayList();
Iterator roles = user.getRoles();
while (roles.hasNext()) {
Role role = (Role) roles.next();
String rolename = role.getRolename();
if (!combined.contains(rolename)) {
combined.add(rolename);
}
}
Iterator groups = user.getGroups();
while (groups.hasNext()) {
Group group = (Group) groups.next();
roles = group.getRoles();
while (roles.hasNext()) {
Role role = (Role) roles.next();
String rolename = role.getRolename();
if (!combined.contains(rolename)) {
combined.add(rolename);
}
}
}
return (new GenericPrincipal(this, user.getUsername(),
user.getPassword(), combined));
}
// ------------------------------------------------------ Lifecycle Methods
/**
* Prepare for active use of the public methods of this Component.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents it from being started
*/
protected Principal getPrincipal(String username) {
return (null);
}
protected String getPassword(String username) {
return null;
}
protected String getName() {
return this.name;
}
public void createDatabase(String path) {
database = new MemoryUserDatabase(name);
((MemoryUserDatabase) database).setPathname(path);
try {
database.open();
}
catch (Exception e) {
}
}
}
在实例化SimpleUserDatabaseRealm类之后,必须调用createDatabase()方法。 createDatabase()方法实例化org.apache.catalina.users.MemoryUserDatabase读取并解析XML文档。
10.6.4 ex10.pyrmont.startup.Bootstrap1类
Bootstrap1在本章第一个Demo中使用,代码如Listing 10.4:
Listing 10.4: The Bootstrap1 Class
package ex10.pyrmont.startup;
import ex10.pyrmont.core.SimpleWrapper;
import ex10.pyrmont.core.SimpleContextConfig;
import ex10.pyrmont.realm.SimpleRealm;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Realm;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityCollection;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.loader.WebappLoader;
public final class Bootstrap1 {
public static void main(String[] args) {
//invoke: http://localhost:8080/Modern or http://localhost:8080/Primitive
System.setProperty("catalina.base", System.getProperty("user.dir"));
Connector connector = new HttpConnector();
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
Context context = new StandardContext();
// StandardContext's start method adds a default mapper
context.setPath("/myApp");
context.setDocBase("myApp");
LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);
context.addChild(wrapper1);
context.addChild(wrapper2);
// for simplicity, we don't add a valve, but you can add
// valves to context or wrapper just as you did in Chapter 6
Loader loader = new WebappLoader();
context.setLoader(loader);
// context.addServletMapping(pattern, name);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
// add ContextConfig. This listener is important because it configures
// StandardContext (sets configured to true), otherwise StandardContext
// won't start
// add constraint
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.addPattern("/");
securityCollection.addMethod("GET");
SecurityConstraint constraint = new SecurityConstraint();
constraint.addCollection(securityCollection);
constraint.addAuthRole("manager");
LoginConfig loginConfig = new LoginConfig();
loginConfig.setRealmName("Simple Realm");
// add realm
Realm realm = new SimpleRealm();
context.setRealm(realm);
context.addConstraint(constraint);
context.setLoginConfig(loginConfig);
connector.setContainer(context);
try {
connector.initialize();
((Lifecycle) connector).start();
((Lifecycle) context).start();
// make the application wait until we press a key.
System.in.read();
((Lifecycle) context).stop();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
在Bootstrap1的main()方法中创建了PrimitiveServlet和ModernServlet对应的SimpleWrapper。
然后创建设置StandardContext,添加SimpleContextConfig监听器等基本如第9章一样。如下代码是新的:
// add constraint
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.addPattern("/");
securityCollection.addMethod("GET");
main()方法创建一个SecurityCollection对象并调用其addPattern()和addMethod()方法。addPattern()方法指定安全性的URL约束。addMethod()方法添加受此限制的方法约束。addMethod()方法获取GET,因此HTTP请求的GET方式将受此安全约束的制约。
接下来,main()方法实例化SecurityConstraint类并将其添加到集合中。它还设置了可以访问受限资源的角色。通过manager,那些拥有manager角色的用户将能够查看资源。注意在SimpleRealm类只有用户ken具有manager角色,他的密码是blackcomb。
SecurityConstraint constraint = new SecurityConstraint();
constraint.addCollection(securityCollection);
constraint.addAuthRole("manager");
接下来,创建了LoginConfig和SimpleRealm对象:
LoginConfig loginConfig = new LoginConfig();
loginConfig.setRealmName("Simple Realm");
// add realm
Realm realm = new SimpleRealm();
然后,将realm, constraint和loginConfig对象和StandardContext相关联:
context.setRealm(realm);
context.addConstraint(constraint);
context.setLoginConfig(loginConfig);
接下来,启动context。这部分已在前面几章讨论过。
实际上,当前对PrimitiveServlet和ModernServlet的访问是受限的。 如果是用户请求任何servlet,他/她必须使用basic验证认证。只有在他/她键入正确的用户名和密码(在此,ken和blackcomb),他/她将被允许访问。
10.6.5 ex10.pyrmont.startup.Boo tstrap2类
Bootstrap2类启动第2个应用Demo。除了它使用SimpleUserDatabase实例作为领域关联到StandardContext外,几乎与Bootstrap1类类似。 要访问PrimitiveServlet和ModernServlet,正确的用户名和密码分别是:admin和password。
Listing 10.5: The Bootstrap2 class
package ex10.pyrmont.startup;
import ex10.pyrmont.core.SimpleWrapper;
import ex10.pyrmont.core.SimpleContextConfig;
import ex10.pyrmont.realm.SimpleUserDatabaseRealm;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Realm;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityCollection;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.loader.WebappLoader;
public final class Bootstrap2 {
public static void main(String[] args) {
//invoke: http://localhost:8080/Modern or http://localhost:8080/Primitive
System.setProperty("catalina.base", System.getProperty("user.dir"));
Connector connector = new HttpConnector();
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
Context context = new StandardContext();
// StandardContext's start method adds a default mapper
context.setPath("/myApp");
context.setDocBase("myApp");
LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);
context.addChild(wrapper1);
context.addChild(wrapper2);
// for simplicity, we don't add a valve, but you can add
// valves to context or wrapper just as you did in Chapter 6
Loader loader = new WebappLoader();
context.setLoader(loader);
// context.addServletMapping(pattern, name);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
// add ContextConfig. This listener is important because it configures
// StandardContext (sets configured to true), otherwise StandardContext
// won't start
// add constraint
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.addPattern("/");
securityCollection.addMethod("GET");
SecurityConstraint constraint = new SecurityConstraint();
constraint.addCollection(securityCollection);
constraint.addAuthRole("manager");
LoginConfig loginConfig = new LoginConfig();
loginConfig.setRealmName("Simple User Database Realm");
// add realm
Realm realm = new SimpleUserDatabaseRealm();
((SimpleUserDatabaseRealm) realm).createDatabase("conf/tomcat-users.xml");
context.setRealm(realm);
context.addConstraint(constraint);
context.setLoginConfig(loginConfig);
connector.setContainer(context);
try {
connector.initialize();
((Lifecycle) connector).start();
((Lifecycle) context).start();
// make the application wait until we press a key.
System.in.read();
((Lifecycle) context).stop();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
10.6.6 运行Demo
在 windows 下运行第一个Demo,可以在工作目录下面如下运行该程序:
java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;./ex10.pyrmont.startup.Bootstrap1
在 Linux 下,使用冒号分开两个库:
java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:./ex10.pyrmont.startup.Bootstrap1
在 windows 下运行第二个Demo,可以在工作目录下面如下运行该程序:
java-classpath ./lib/servlet.jar;./lib/commons-collections.jar;./lib/commons-digester.jar;./lib/commons-logging.jar;./ex10.pyrmont.startup.Bootstrap2
在 Linux 下,使用冒号分开两个库:
java-classpath ./lib/servlet.jar:./lib/commons-collections.jar:./lib/commons-digester.jar:./lib/commons-logging.jar :./ex10.pyrmont.startup.Bootstrap2
在二个Demo中,调用PrimitiveServlet和ModernServlet,可以分别使用下面的 URL 来请求:
http://localhost:8080/Primitive
http://localhost:8080/Modern
10.7 小结
安全性是servlet编程和servlet规范中的一个重要主题,通过提供安全相关的对象来满足安全的需要,例如主体(principal),角色(roles),安全约束(securityConstraint),登录配置(login Config)等。在本章中,我们已经学习了解到servlet容器如何解决安全性问题。