本文分析tomcat容器的安全管理,servlet技术支持通过配置部署描述器(web.xml文件)来对受限内容进行访问控制;servlet容器是通过一个名为验证器的阀来支持安全限制的,当servlet容器启动时,验证器阀会被添加到Context容器的管道中。在调用Wrapper阀之前,会先调用验证器阀,对当前用户进行身份验证;验证器阀会调用Context容器的Realm对象的authenticate()方法,传入用户输入的用户名和密码来对用户进行身份验证。

Realm对象是用来对用户进行身份验证的组件,它会对用户输入的用户名和密码对进行有效性判断,通常与一个Context容器相关联;那么Realm对象是如何验证用户身份的呢?实际上,它保存了所有有效用户的用户名和密码对,或者它会访问存储这些数据的存储器。这些数据的具体存储依赖于Realm对象的具体实现,在tomcat中,有效用户信息默认存储在tomcat-user.xml文件中;当然也可以使用其他的针对其他资源验证Realm对象实现,比如关系数据库

在tomcat中,Realm对象是org.apache.catalina.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);

此外,Realm接口的getContainer()方法与settContainer()方法用来将Realm实例与一个Context实例相关联

在tomcat中,Realm接口的基本实现形式是org.apache.catalina.realm.RealmBase类,该类是一个抽象类,org.apache.catalina.realm包还提供了RealmBase类的一些继承类的实现,包括JDBCRealm、JNDIRealm、MemoryRealm和UserDatabaseRealm类等。 默认情况下,会使用MemoryRealm类的实例作为验证用的Realm对象。当第一次调用MemoryRealm实例时,它会读取tomcat-user.xml文档的内容。

在tomcat中,java.security.Principal接口的实例为org.apache.catalina.realm.GenericPrincipal类,GenericPrincipal实例必须始终与一个Realm对象相关联,正如其两个构造函数所示:

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()方法,并传入字符串形式的角色名来检查该Principal对象是否拥有指定角色

public boolean hasRole(String role) {

if (role == null)
            return (false);
        return (Arrays.binarySearch(roles, role) >= 0);

}

下面我们来看一个简单的Realm对象是怎样工作的,里面采用了硬编码的方式保存了两个用户名和密码对

SimpleRealm类的源码如下(略去了无关代码)

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;
}
/**
* 验证用户名和密码,返回Principal类型对象
*/
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());
}
/**
* 判断Principal类型对象是有拥有指定角色
*/
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;
} 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;
}
} }

其中的authenticate()方法由验证器调用,如果用户提供的用户名和密码是无效的便返回null,否则返回代表该用户的Principal对象

上面部分是描述Realm对象相关的,接下来描述与验证器相关的实现。验证器是org.apache.catalina.Authenticator接口的实例,Authenticator接口是一个标识接口,没有声明任何方法;在tomcat中提供了Authenticator接口的基本实现org.apache.catalina.authenticator.AuthenticatorBase类,同时AuthenticatorBase类还继承了org.apache.catalina.valves.ValveBase类,也就是说,AuthenticatorBase类也是一个阀;在tomat中,提供了很多AuthenticatorBase类的继承类,包括BasicAuthenticator类、FormAuthenticator类、DigestAuthenticator类和SSLAuthenticator类等;此外当tomcat用户没有指定验证方法名时,NonLoginAuthenticator类用来对来访者的身份进行验证。

AuthenticatorBase类的invoke()方法会调用authenticate()抽象方法,后者的实现依赖于子类,这里类似与templet方法模式

那么在我们的web应用程序中,具体采用那个验证器实现呢,这依赖于我们在web.xml文件中的配置(配置示例如下)

    <web-app>
<security-constraint>
<web-resource-collection>
<web-resource-name>
Member Area
</web-resource-name>
<description>
Only registered members can access this area.
</description>
<url-pattern>/member/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>member</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
<security-role>
<role-name>member</role-name>
</security-role>
</web-app>

上面示例是采用BASIC验证,也可以设置为FORM、DIGEST或CLIENT-CERT等,分别对应不同的验证器类(BasicAuthenticator类、FormAuthenticator类、DigestAuthenticator类和SSLAuthenticator类),若没有设置auth-method元素,则LoginConfig对象auth-method属性的默认值为NONE,使用NonLoginAuthenticator进行安全验证。(注:LoginConfig对象封装了Realm对象名和要使用的身份验证方法)

最后我们来描述一个Context容器实例是如何将验证器阀的,这里介绍的是一个SimpleContextConfig类,它是作为Context容器实例的监听器,在监听方法lifecycleEvent()中,调用authenticatorConfig()方法实例化BasicAuthenticator类,并将其作为阀添加到StandardContext实例的管道中

下面是SimpleContextConfig类的authenticatorConfig()方法实现

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) {
}
}

---------------------------------------------------------------------------

本系列How Tomcat Works系本人原创

转载请注明出处 博客园 刺猬的温驯

本人邮箱: chenying998179#163.com (#改为@)

本文链接http://www.cnblogs.com/chenying99/p/3242277.html

How Tomcat Works(十三)的更多相关文章

  1. 攻城狮在路上(肆)How tomcat works(零) 前言说明

    最近几篇是关于How tomcat works一书的读书笔记. 通过数个章节逐渐实现一个tomcat的功能. 源码下载地址:http://zhidao.baidu.com/share/7007af0f ...

  2. How Tomcat works — 四、tomcat启动(3)

    上一节说到StandardService负责启动其子组件:container和connector,不过注意,是有先后顺序的,先启动container,再启动connector,这一节先来看看conta ...

  3. How Tomcat Works(十四)补充

    在How Tomcat Works(十四)中,本人并没有对javax.servlet.Filter及javax.servlet.FilterChain做详细的描述,本文在这里做一下补充 FilterC ...

  4. How Tomcat Works(十八)

    在前面的文章中,如果我们要启动tomcat容器,我们需要使用Bootstrap类来实例化连接器.servlet容器.Wrapper实例和其他组件,然后调用各个对象的set方法将它们关联起来:这种配置应 ...

  5. How Tomcat Works(十七)

    在前面的文章中,已经学会了如何通过实例化一个连接器和容器来获得一个servlet容器,并将连接器和容器相关联:但在前面的文章中只有一个连接器可用,该连接器服务8080端口上的HTTP请求,无法添加另一 ...

  6. How Tomcat Works(十六)

    本文接下来会介绍Host容器和Engine容器,在tomcat的实际部署中,总是会使用一个Host容器:本文介绍Host接口和Engine接口及其相关类 Host容器是org.apache.catal ...

  7. How Tomcat Works(十五)

    本文接下来分析Context容器,Context容器实例表示一个具体的Web应用程序,其中包括一个或多个Wrapper实例:不过Context容器还需要其他的组件支持,典型的如载入器和Session管 ...

  8. How Tomcat Works(十四)

    我们已经知道,在tomcat中有四种类型的servlet容器,分别为Engine.Host.Context 和Wrapper,本文接下来对tomcat中Wrapper接口的标准实现进行说明. 对于每个 ...

  9. How Tomcat Works(十二)

    tomcat容器通过一个称为Session管理器的组件来管理建立的Session对象,该组件由org.apache.catalina.Manager接口表示:Session管理器必须与一个Contex ...

随机推荐

  1. 每个极客都应该知道的Linux技巧

    每个极客都应该知道的Linux技巧 2014/03/07 | 分类: IT技术 | 0 条评论 | 标签: LINUX 分享到:18 本文由 伯乐在线 - 欣仔 翻译自 TuxRadar Linux. ...

  2. (五)用正则化(Regularization)来解决过拟合

    1 过拟合 过拟合就是训练模型的过程中,模型过度拟合训练数据,而不能很好的泛化到测试数据集上.出现over-fitting的原因是多方面的: 1) 训练数据过少,数据量与数据噪声是成反比的,少量数据导 ...

  3. php的setcookie

    不同浏览器对cookie的原理不同,导致cookie的过期时间有些模糊. 经测试:火狐浏览器的cookie过期时间设置是根据增量原则.服务器端设置time()+num,或者time()-num,传递到 ...

  4. 【英文】Bingo口语笔记(18) - Cover系列

    cover charge 服务费 cover version 翻唱版本 cover the news 头条新闻

  5. aspose.Cells 导出Excel

    aspose aspse.Cells可以操作Excel,且不依赖于系统环境. 使用模板,通过绑定输出数据源 这种适合于对格式没有特别要求的,直接绑定数据源即可.和数据绑定控件差不多. Workbook ...

  6. Sqoop的使用(Mysql To HBase)

    最近需要将mysql的数据整合到HBase中,原本使用MapReduce,自己制作job将mysql的数据导入, 查阅资料过程中,发现了开源工具sqoop(关系性数据库与HDFS,HBASE,HIVE ...

  7. A. Puzzles CodeForces Round #196 (Div.2)

    题目的大意是,给你 m 个数字,让你从中选 n 个,使得选出的数字的极差最小. 好吧,超级大水题.因为要极差最小,所以当然想到要排个序咯,然后去连续的 n 个数字,因为数据不大,所以排完序之后直接暴力 ...

  8. Bootstrap--全局CSS样式之栅格系统

    Bootstrap 提供了一套响应式.移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列.它包含了易于使用的预定义类,还有强大的mixin 用于生成更具 ...

  9. Java文件下载的几种方式

    public HttpServletResponse download(String path, HttpServletResponse response) { try { // path是指欲下载的 ...

  10. 自定义View实现图片的绘制、旋转、缩放

    1.图片 把一张JPG图片改名为image.jpg,然后拷贝到项目的res-drawable中. 2.activity_main.xml <LinearLayout xmlns:android= ...