How Tomcat Works(十三)

系统 1730 0

本文分析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(十三)


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论