• How Tomcat Works(十三)


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

  • 相关阅读:
    jquery easyui-datagrid手动增加删除重置行
    jsp中一行多条数据情况
    JQuery操作下拉框
    解决juqery easyui combobox只能选择问题
    oracle中WMSYS.WM_CONCAT函数的版本差异
    oracle wm_concat(column)函数的使用
    Javascript九大排序算法详解
    C#和VB新版本的最新特性列表
    Oracle中如何区别用户和模式
    远程控制数据库实用SQL重启功能
  • 原文地址:https://www.cnblogs.com/chenying99/p/3242277.html
Copyright © 2020-2023  润新知