美文网首页
深入剖析Tomcat(How Tomcat works)读书笔记

深入剖析Tomcat(How Tomcat works)读书笔记

作者: 抬头挺胸才算活着 | 来源:发表于2019-12-08 20:11 被阅读0次

    参考资料:
    [1]. 深入剖析Tomcat(How Tomcat works)书籍代码下载地址

    第八章:载入器
    第九章:Session管理器
    第十章:安全性

    • 为什么Tomcat需要自定义Loader?
    1. 因为使用系统的类加载器,servlet可以访问所有的类,这是非常危险的,servlet应该只允许载入WEB-INF/classes目录下的所有类和库。2. 为了提供自动重载的功能,当类的目录下的类方式变化时,WEB应用会重新载入这些类。类加载器使用一个额外的线程来不断地检查servlet类及其他类的文件的时间戳,若要支持自动重载功能,类加载器要实现Reloader接口。类记载器要实现Loader接口。
    • 为什么Java类的加载要加载双亲委派机制?
      为了解决安全问题。安全管理器用来限制某个类对某个路径的访问,如果JVM是新人Object类的,没有双亲委派机制,用户可以加载自定义的Object类,安全管理器就这样被轻易绕过。也就是系统和用户自定义类有优先级的概念,优先记载系统的类,用户的类就不能混淆到系统的类,带来一些问题。

    • 自定义类加载器可能有以下的原因:

    1. 为了在加载器中指定某些规则
    2. 为了缓存已经载入的类
    3. 为了实现类的预载入,方便使用
    • 载入器
      Tomcat载入器通常会与一个Context级别的servlet容器相关联。

    • Session管理器
      Session跟一个Context容器相关联,有些Session是可以存储到磁盘文件或者数据库中的。还有个Manager接口,差不多相当于Session的工厂。

    • 安全相关类
      GenericPrincipal:实现了Principal接口,它代表的是一个用户的身份,包括用户的名字,角色,密码等。
      Realm:用来进行身份验证,它的主要方法有autheticate,Realm跟Context一对一绑定。
      LoginConfig:类包含了登录的所有配置信息,验证方法authMethod、错误界面errorPage、登录界面loginPage、realmName。

    • Context容器安全验证配置
      下面这段代码是关于Context容器在安全验证方面的配置,大致看一下。

    public final class Bootstrap1 {
      public static void main(String[] args) {
    
        LifecycleListener listener = new SimpleContextConfig();
        ((Lifecycle) context).addLifecycleListener(listener);
    
        // 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);
        }
    }
    
    
    • 验证器的安装
      验证器的安装是采用监听器的方式实现的,SimpleContextConfig类继承自LifecycleListener,监听方法lifecycleEvent中只对开始时间做了处理,调用了authenticatorConfig方法进行验证器的安装。
    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方法首先检查了是否配置了SecurityConstraint,是否已经实现了安装了Authenticator,Context是否已经配置了Realm(因为Authenticator最后验证还是调用的Realm),最后再给Context配置一个阀门BasicAuthenticator。

    • 安全验证的流程
      从上面我们知道安全验证最后配置为一个阀门,那么我们找到阀门的invoke方法就可以知道安全验证到底做了什么。
      是否缓存在Request,Session中,如果是获取principal,往下;
      是否是表格验证,如果是进行验证;
      有无限制SecurityConstraint,没有调用后面的阀门,然后返回;
      还有几个Constraint相关的验证,包括密码(用子类的authenticate方法),角色等;
      如果上面都通过验证,调用阀门继续向前处理。
    public abstract class AuthenticatorBase
        extends ValveBase
        implements Authenticator, Lifecycle {
    
        public void invoke(Request request, Response response,
                           ValveContext context)
            throws IOException, ServletException {
    
            // If this is not an HTTP request, do nothing
            if (!(request instanceof HttpRequest) ||
                !(response instanceof HttpResponse)) {
                context.invokeNext(request, response);
                return;
            }
            if (!(request.getRequest() instanceof HttpServletRequest) ||
                !(response.getResponse() instanceof HttpServletResponse)) {
                context.invokeNext(request, response);
                return;
            }
            HttpRequest hrequest = (HttpRequest) request;
            HttpResponse hresponse = (HttpResponse) response;
            if (debug >= 1)
                log("Security checking request " +
                    ((HttpServletRequest) request.getRequest()).getMethod() + " " +
                    ((HttpServletRequest) request.getRequest()).getRequestURI());
            LoginConfig config = this.context.getLoginConfig();
    
            // Have we got a cached authenticated Principal to record?
            if (cache) {
                Principal principal =
                    ((HttpServletRequest) request.getRequest()).getUserPrincipal();
                if (principal == null) {
                    Session session = getSession(hrequest);
                    if (session != null) {
                        principal = session.getPrincipal();
                        if (principal != null) {
                            if (debug >= 1)
                                log("We have cached auth type " +
                                    session.getAuthType() +
                                    " for principal " +
                                    session.getPrincipal());
                            hrequest.setAuthType(session.getAuthType());
                            hrequest.setUserPrincipal(principal);
                        }
                    }
                }
            }
    
            // Special handling for form-based logins to deal with the case
            // where the login form (and therefore the "j_security_check" URI
            // to which it submits) might be outside the secured area
            String contextPath = this.context.getPath();
            String requestURI = hrequest.getDecodedRequestURI();
            if (requestURI.startsWith(contextPath) &&
                requestURI.endsWith(Constants.FORM_ACTION)) {
                if (!authenticate(hrequest, hresponse, config)) {
                    if (debug >= 1)
                        log(" Failed authenticate() test");
                    return;
                }
            }
    
            // Is this request URI subject to a security constraint?
            SecurityConstraint constraint = findConstraint(hrequest);
            if ((constraint == null) /* &&
                (!Constants.FORM_METHOD.equals(config.getAuthMethod())) */ ) {
                if (debug >= 1)
                    log(" Not subject to any constraint");
                context.invokeNext(request, response);
                return;
            }
            if ((debug >= 1) && (constraint != null))
                log(" Subject to constraint " + constraint);
    
            // Make sure that constrained resources are not cached by web proxies
            // or browsers as caching can provide a security hole
            if (!(((HttpServletRequest) hrequest.getRequest()).isSecure())) {
                HttpServletResponse sresponse = 
                    (HttpServletResponse) response.getResponse();
                sresponse.setHeader("Pragma", "No-cache");
                sresponse.setHeader("Cache-Control", "no-cache");
                sresponse.setDateHeader("Expires", 1);
            }
    
            // Enforce any user data constraint for this security constraint
            if (debug >= 1)
                log(" Calling checkUserData()");
            if (!checkUserData(hrequest, hresponse, constraint)) {
                if (debug >= 1)
                    log(" Failed checkUserData() test");
                // ASSERT: Authenticator already set the appropriate
                // HTTP status code, so we do not have to do anything special
                return;
            }
    
            // Authenticate based upon the specified login configuration
            if (constraint.getAuthConstraint()) {
                if (debug >= 1)
                    log(" Calling authenticate()");
                if (!authenticate(hrequest, hresponse, config)) {
                    if (debug >= 1)
                        log(" Failed authenticate() test");
                    // ASSERT: Authenticator already set the appropriate
                    // HTTP status code, so we do not have to do anything special
                    return;
                }
            }
    
            // Perform access control based on the specified role(s)
            if (constraint.getAuthConstraint()) {
                if (debug >= 1)
                    log(" Calling accessControl()");
                if (!accessControl(hrequest, hresponse, constraint)) {
                    if (debug >= 1)
                        log(" Failed accessControl() test");
                    // ASSERT: AccessControl method has already set the appropriate
                    // HTTP status code, so we do not have to do anything special
                    return;
                }
            }
    
            // Any and all specified constraints have been satisfied
            if (debug >= 1)
                log(" Successfully passed all security constraints");
            context.invokeNext(request, response);
    
        }
    }
    

    相关文章

      网友评论

          本文标题:深入剖析Tomcat(How Tomcat works)读书笔记

          本文链接:https://www.haomeiwen.com/subject/rutpgctx.html