君子博学而日参省乎己 则知明而行无过矣

博客园 首页 新随笔 联系 订阅 管理

本文分析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

posted on 2013-08-07 02:03  刺猬的温驯  阅读(592)  评论(0编辑  收藏  举报