《TOMCAT权威指南》摘抄

1.对每条ab请求,可以采用下列步骤得到基准调校的结果:

 1.配置并重启被测试的Apache httpd和/或Tomcat实例。

2.确信服务器日志没有启动错误。如果有,请按照前面描述的步骤修正错误。

3.在服务器重启后,运行ab命令请求,让服务器服务于首次请求。

4.作为基准调校的一部分,再次运行ab命令行。

5.在所有的请求都完成时,确信ab报告了零错误及non-2xx零响应。

6.在ab请求之间等待几秒钟,从而服务器能返回空闲状态。

7.注意在ab统计信息中每秒的请求数。

8.如果每秒的请求数发生了明显变化,请返回到第4步;否则,每秒的反复请求数就是基准调校的结果。如果该数字经过ab的10次反复请求仍继续发生明显变化,则放弃,记录最后的每秒请求数,作为基准调校的结果。

2.预编译的jsps.xml Ant构建文件

<project name="pre-compile-jsps" default="compile-jsp-servlets">

    <!-- 私有属性 -->
    <property name="webapp.dir" value="${basedir}/webapp-dir" />
    <property name="tomcat.home" value="/opt/tomcat"/>
    <property name="jspc.pkg.prefix" value="com.mycompany"/>
    <property name="jspc.dir.prefix" value="com/mycompany"/>

    <!-- 编译属性 -->
    <property name="debug" value="on" />
    <property name="debuglevel" value="lines,vars,source" />
    <property name="deprecation" value="on" />
    <property name="encoding" value="ISO-8859-1" />
    <property name="optimize" value="off" />
    <property name="build.compiler" value="modern"/>
    <property name="source.version" value="1.5" />
    
    <!-- 初始化路径 -->
    <path id="jspc.classpath">
        <fileset dir="${tomcat.home}/bin">
            <include name="*.jar"/>
        </fileset>
        <fileset dir="${tomcat.home}/server/lib">
            <include name="*.jar"/>
        </fileset>
        <fileset dir="${tomcat.home}/common/i18n">
            <include name="*.jar"/>
        </fileset>
        <fileset dir="${tomcat.home}/common/lib">
            <include name="*.jar"/>
        </fileset>
        <fileset dir="${webapp.dir}/WEB-INF">
            <include name="lib/*.jar"/>
        </fileset>
        <pathelement location="${webapp.dir}/WEB-INF/classes"/>
        <pathelement location="${ant.home}/lib/ant.jar"/>
        <pathelement location="${java.home}/../lib/tools.jar"/>
    </path>
    <property name="jspc.classpath" refid="jspc.classpath"/>

    <!-- ========================================== -->
    <!--  从JSP文件产生JAVA源码和web.xml文件 -->
    <!-- ========================================== -->
    <target name="generate-jsp-java-src">
        <mkdir dir="${webapp.dir}/WEB-INF/jspc-src/${jspc.dir.prefix}"/>
        <taskdef classname="org.apache.jasper.JspC" name="jasper2">
            <classpath>
                <path refid="jspc.classpath"/>
            </classpath>
        </taskdef>
        <touch file="${webapp.dir}/WEB-INF/jspc-web.xml"/>
        <jasper2 uriroot="${webapp.dir}"
            package="${jspc.pkg.prefix}"
            webXmlFragment="${webapp.dir}/WEB-INF/jspc-web.xml"
            outputDir="${webapp.dir}/WEB-INF/jspc-src/${jspc.dir.prefix}"
            verbose="1" />
    </target>

    <!-- ========================================= -->
    <!-- 编译JSP servlet并由此产生Java类文件 -->
    <!-- 由JspC任务产生的源码       -->
    <!-- ========================================= -->
    <target name="compile-jsp-servlets" depends="generate-jsp-java-src">
        <mkdir dir="${webapp.dir}/WEB-INF/classes"/>
        <javac srcdir="${webapp.dir}/WEB-INF/jspc-src"
            destdir="${webapp.dir}/WEB-INF/classes"
            includes="**/*.java"
            debug="${debug}"
            debuglevel="${debuglevel}"
            deprecation="${deprecation}"
            encoding="${encoding}"
            optimize="${optimize}"
            source="${source.version}">
            <classpath>
                <path refid="jspc.classpath"/>
            </classpath>
        </javac>
    </target>

    <!-- ========================================= -->
    <!-- 清除所有预编译的JSP源码、类和jspc-web.xml -->
    <!-- ========================================= -->
    <target name="clean">
        <delete dir="${webapp.dir}/WEB-INF/jspc-src"/>
        <delete dir="${webapp.dir}/WEB-INF/classes/${jspc.dir.prefix}"/>
        <delete file="${webapp.dir}/WEB-INF/jspc-web.xml"/>
    </target>

</project>

            

3.记住虽然Tomcat是开源的,但它也是一个非常复杂的应用程序,因此在开始修改源码之前,一定要小心谨慎。如果决定要投入Tomcat源码开发,请使用Tomcat的邮件列表以分享您的想法,并与社区互动。

4.使用APR连接器,而不使用JIO或NIO的连接器,原因有以下几点:

1.HTTPS使用APR连接器可能会更快,因为APR连接器是叫做OpenSSL库文件的本地代码。而JIO和NIO连接器是纯粹的Java代码,并使用了纯粹的Java TLS/SSL编码,大家都知道,比OpenSSL要慢一些。但是,如果没有使用HTTPS,则不用关心这一点。

2.对于某些代码场合(主要是AJP),APR可能会更高效。对这种配置,具有最大吞吐量是比较重要的。

3.由于使用的sendfile(2)系统呼叫,所以APR被设计为处理大型静态文本文件(如提供媒体文件)时更高效。

4.任何连接器实现的所有底层网络编码都是固有的(native)(如JVM是在C/C++中编写的),因此,无论您是否更喜欢APR,取决于您是喜欢来自ASF还是JVM提供商提供的本地网络编码(native network code)。

5.APR一个被广泛接受的用于Web服务器的I/O实现,用C编程语言编写(供Apache httpd使用),而且工作正常。

6.在MS-Windows上,NIO连接器并没有真正起作用,因为NIO似乎在Windows上无法正常发挥作用,至少在使用Sun的Java VM时是这样。在其他操作系统上,这不成为问题。

7.APR使用了便携的、安全随机数产生器,从而Tomcat会话ID在Windows上默认是安全的。

8.APR还有一些其他功能是非常有用的,而核心Java平台并没有提供这些功能。关键是APR是不同于Java的实现,而且包含一组不同的功能。这些新功能可能会放在任何新版的APR中,而且可以修改APR连接器以充分利用这些功能。

5.如果想显示配置一个Connector以使用APR,则可按如下方式设置协议属性:

<Connector port="8080"
    protocol="org.apache.coyote.http11.Http11AprProtocol"
    disableUploadTimeout="false"
    maxThreads="150" connectionTimeout="20000"
    redirectPort="8443" />

6.若要让Apache httpd保护WEB-INF及META-INF目录,请在httpd.conf中追加下列内容:

<LocationMatch "/WEB-INF/">
    AllowOverride None
    deny from all
</LocationMatch>
<LocationMatch "/META-INF/">
    AllowOverride None
    deny from all
</LocationMatch>

把所有Web应用程中对.htaccess的请求都映射给叫做forbidden.jsp的JSP文件:

<servlet>
    <servlet-name>htaccess</servlet-name>
    <jsp-file>/forbidden.jsp</jsp-file>
</servlet>

<servlet-mapping>
    <servlet-name>htaccess</servlet-name>
    <url-pattern>*.htaccess</url-pattern>
</servlet-mapping>

7.一个简单的HttpServlet,它会在文件系统上创建一个文件,并输出文件成功写入的消息。

package com.oreilly.tomcat.servlets;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOTxception;
import java.io.PrintWriter;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * 该servlet尝试在Web应用程序的文档中写入文档根目录
 */
public class WriteFileServlet extends GenericServlet {
    public void service(ServletRequest request, ServletResponse response)
        throws IOException, ServletException
    {
        //尝试打开一个文件并写入。
        String catalinaHome = "/opt/tomcat";
        File testFile = new File(catalinaHome + "/webapps/ROOT", "test.txt");
        FileOutputStream fileOutputStream = new FileOutputStream(testFile);
        fileOutputStream.write(new String("testing...\n").getBytes());
        fileOutputStream.close();

        //如果执行到此,表示文件已成功地创建了。
        PrintWriter out = response.getWriter();
        out.println("File created successfully!");
    }
}

为了便于在ROOT Web应用程序中编译、安装及测试,编写了下面这个servlet:

# mkdir $CATALINA_HOME/webapps/ROOT/WEB-INF/classes

# export CATALINA_HOME=/opt/tomcat

# javac -classpath $CATALINA_HOME/lib/servlet-api.jar -d $CATALINA_HOME/webapps/ROOT/WEB-INF/classes WriteFileServlet.java

然后, 在ROOT Web应用程序的WEB-INF/web.xml部署描述符中加入servlet和servlet-mapping元素:

现在启用SecurityManager并重新启动Tomcat.

然后,访问URL http://localhost:8080/writefile。

8.编写tc-chroot init脚本,以便使用jbchroot而非chroot的绝对路径,并给jbchroot传入一个或多个开关参数来更改用户组和/或组:

#!/bin/sh
#
# chrooted Apache Tomcat servlet容器的linux init脚本。
#
# chkconfig: 2345 96 14
# 说明: Apache Tomcat servlet容器
# 进程名: tc-chroot
# config: /opt/chroot/tomcat/conf/tomcat-env.sh
#

APP_ENV="/opt/tomcat/conf/tomcat-env.sh"

# 如果存在,则寻找app config文件的源文件。
[ -r "$APP_ENV" ] && . "${APP_ENV}"

# 对应于Tomcat 启动/停止 脚本的路径。
TOMCAT_SCRIPT=$CATALINA_HOME/bin/catalina.sh

# 程序名
PROG="$0"

# 解决 links - $0 可以是一个软链接。
while [ -h "$PROG" ]; do
    ls=`ls -ld "$PROG"`
    link=`expr "$ls" : `.*-> \(.*\)$'`
    if expr "$link" : '.*/.*' > /dev/null; then
        PROG="$link"
    else
        PROG=`dirname "$PROG"`/"$link"
    fi
done

PROG="`basename $PROG`"
case "$1" in
    start)
        echo -n "Starting $PROG: "
        
        # Mount /proc.
        mkdir -p /opt/chroot/proc
        mount -t proc proc /opt/chroot/proc &>/dev/null

        /usr/local/bin/jbchroot -U tomcat -- /opt/chroot \
            /bin/bash -c "set -a; . $APP_ENV; \
            $TOMCAT_SCRIPT start" &>/dev/null

        let RETVAL=$?
        if [ $RETVAL -eq 0 ]; then
            echo "[ OK ]"
        else
            echo "[ FAILED ]"
        fi
        ;;
    stop)
        echo -n "Stopping $PROG: "
        
        /usr/local/bin/jbchroot -U tomcat -- /opt/chroot \
            /bin/bash -c "set -a; . $APP_ENV; \
            $TOMCAT_SCRIPT stop" &>/dev/null

        let RETVAL=$?
        if [ $RETVAL -eq 0 ]; then
            # Give Tomcat some time to perperly stop all webapps.
            sleep 3

            # Unmount /proc.
            umount /opt/chroot/proc &>/dev/null
            echo "[ OK ]"
        else
            echo "[ FAILED ]"
        fi
        ;;
    *)
        echo "Usage: tc-chroot {start|stop}"
        exit 1
esac

9.存在SQL注入的一段代码:

//已经连接到数据库了,创建使用的Statement
Statement statement = connection.createStatement();

//创建用于用户登陆的包含SQL查询的常规字符串
// inserting the username and password into the String.
String queryString = "select * from USER_TABLE where USERNAME='" + username + "' and PASSWORD='" + password + "';";

// 用计划好的字符串执行该SQL查询
ResultSet resultSet = statement.executeQuery(queryString);

// 从数据库中返回结果行,表明用户已登录。

10.BadInputValve属性

className       此Valve实现的Java类名,必须设为com.oreilly.tomcat.valves.BadInputValve

escapeQuotes   在执行请求之前、决定此Valuve是否要转义请求参数中的任何引号(包括双引、单引号和反引号)。默认值为false

escapeAngleBrackets  在执行请求之前,决定此Valves是否要转移参数中的任何尖括号。默认值为false

escapeJavaScript  决定此Valve是否要转义请求参数中任何潜在威胁JavaScript函数与对象的引用。默认值为true

allow  以逗号分隔的一组常规表达式,使该Valve允许处理一个请求。可以不设定,表示指定none。如果没有设置allow而设置了一个或多个deny,则不允许处理请求

deny   以逗号分隔的一组常规表达式,使该Valve拒绝请求。如果在deny清单中,有一个常规表达式与参数名或值的一部分相匹配,则决绝请求。如果没有设定deny且没有设定allow,则表示允许所有请求并过滤其参数。如果没有设定deny,但设置了一个或多个allow,则只有一个或多个allow模式与参数名或值的一部分匹配时,该Valve才允许处理请求。

11.以下是请求所需并安装商业服务器X.509认证的简要步骤:

1.产生服务器端的密钥对,并存储在一个密钥库中;

2.根据密钥对产生认证签名请求(sertificate signing request, CSR).

3.要想从谁那里购买商业服务器授权证书(CA),就把CSR发送给他。

4.接收返回的CA认证及新签名的认证;

5.把该CA认证导入到所安装的Java的cacerts密钥库中。这将允许JVM把您的CA认证辨认为证书授权认证。

6.把签名服务器证书导入到已存储服务器密钥对的相同密钥库中。

12.$CATALINA_HOME/conf目录中的主要配置文件:

server.xml     Tomcat主配置文件

web.xml   servlet与其他适用于整个Web应用程序设置的配置文件,必须符合servlet规范的标准格式。

tomcat-users.xml   Tomcat的UserDatabaseRealm用于认证的默认角色、用户以及密码清单。

catalina.policy  Tomcat的Java安全防护策略文件。

context.xml  默认的context设置,应用于安装了Tomcat的所有主机的所有部署内容。

13.filter过滤器允许您用几个程序建立管道。过滤器可以在待定的URL模式交给目标的servlet之前,以及在执行servlet之后,用一段程序来处理这些URL模式。Filter元素有几个子元素:

元素必要性含义
icon可选的用于在GUI工具中显示
filter-name必要的用于filter-mapping的名称
display-name可选的用于在GUI工具中显示
description可选的用于在GUI工具中显示
filter-class必要的过滤器的完整Java类名
init-param0或更多此特定过滤器的初始化参数

14.servlet元素可以让您给servlet或JSP分配名称,以便用于servlet-mapping及指向一个servlet的其他元素中。

子元素:

子元素名可允许的质量含义
icon可选择显示图形的图标
servlet-name必要名称,如前所述
display-name可选择表达GUI工具中的显示名称与描述信息
description可选择servlet的描述
servlet-class后jsp-file必要一个被命名与描述的servlet或JSP名称
init-param0个或多个servlet专属的初始化参数
load-on-startup可选择当启动Tomcat时加载servlet的顺序
run-as可选择用来执行此servlet的用户角色名称
security-role-ref0个或多个安全防护的角色(相关细节请参阅第2章)

在Tomcat中,用来编译及执行所有Jsp的JspServlet,其url-pattern元素为:

<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
</servlet-mapping>

15.捕捉给定Context(已改变URI)的任何请求,并将这些请求映射到JSP(以输出更新后的URI):

<web-app>
    <servlet>
        <servlet-name>Redirector</servlet-name>
        <jsp-file>/redirector.jsp</jsp-file>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <!-- 将所有的请求都映射到Redirector servlet -->
    <servlet-mapping>
        <servlet-name>Redirector</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

16.session-config

<session-config>
    <session-timeout>30</session-timeout>
</session-config>

17.如果想将符合*.foo的文件名映射到MIME类型的application/x-ian-test-file,则可以通过追加下列mime-mapping元素:

<mime-mapping>
    <extension>foo</extension>
    <mime-type>application/x-ian-test-file</mime-type>
</mime-mapping>

18.jdchroot.c源码:

/*
 * jdchroot.c
 * Linux 和 Solaris 上OpenBSD的chroot命令,由Jason Brittain提供
*/
#ifndef lint
static const char copyright[] =
    "@(#) Copyright (c) 1988, 1993\n\
        The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */

#ifndef lint
#if 0
static const char sccsid[] = "@(#)chroot.c   8.1(Berkeley) 6/9/93";
#else
static const char rcsid[] = "$OpenBSD: chroot.c,v 1.7 2002/10/29 23:12:06 millert Exp $";
#endif
#endif  /* not lint */

#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int, char **);
void usage(char *);
static char *getToken(char **, const char *);
int main(int argc, char **argv) 
{
    struct group *gp;
    struct passwd *pw;
    const char *shell;
    char *fulluser, *user, *group, *grouplist, *endp, *p;
    gid_t gid, gidlist[NGROUPS_MAX];
    uid_t uid;
    int ch, gids;
    unsigned long ul;
    char *myname;

    myname = argv[0];

    gid = 0;
    uid = 0;
    gids = 0;
    user = fulluser = group = grouplist = NULL;
    while ((ch = getopt(argc, argv, "G:g:U:u:")) != -1) {
        switch(ch) {
            case 'U':
                fulluser = optarg;
                if (*fulluser == '\0')
                    usage(myname);
                break;
            case 'u':
                user = optarg;
                if (*user == '\0')
                    usage(myname);
                break;
            case 'g':
                group = optarg;
                if (*group == '\0')
                    usage(myname);
                break;
            case 'G':
                grouplist = optarg;
                if (*grouplist == '\0')
                    usage(myname);
                break;
            case '?':
            default:
                usage(myname);
        }
    }
    argc -= optind;
    argv += optind;

    if (argc < 1) 
        usage(myname);
    if (fulluser && (user || group || grouplist)) {
        fprintf(stderr, "%s: The -U option may not be specified with any other option\n",
            myname);
        exit(-1);
    }

    if (group != NULL) {
        if ((gp = getgrnam(group)) != NULL)
            gid = gp->gr_gid;
        else if (isdigit((unsigned char)*group)) {
            errno = 0;
            ul = strtoul(group, &endp, 10);
            if (*endp != '\0' || (ul == ULONG_MAX && errno == ERANGE)) {
                fprintf(stderr, "%s: Invalid group ID `$s'\n", myname, group);
                exit(-1);
            }
            gid = (gid_t)ul;
        } 
        else {
            fprintf(stderr, "%s: No such group `%s'\n", myname, group);
            exit(-1);
        }
        if (grouplist != NULL) 
            gidlist[gids++] = gid;
        if (setgid(gid) != 0) {
            fprintf(stderr, "%s: setgid", myname);
            exit(-1);
        }
    }

    while ((p = getToken(&grouplist, ",")) != NULL && gids < NGROUPS_MAX) {
        if (*p == '\0')
            continue;
        if ((gp = getgrnam(p)) != NULL) 
            gidlist[gids] = gp->gr_gid;
        else if (isdigit((unsigned char)*p)) {
            errno = 0;
            ul = strtoul(p, &endp, 10);
            if (*endp != '\0' || (ul == ULONG_MAX && errno == ERANGE)) {
                fprintf(stderr, "%s: INvalid group ID `%s'\n", myname, p);
                exit(-1);
            }
            gidlist[gids] = (gid_t)ul;
        }
        else {
            fprintf(stderr, "%s: No such group `%s'\n", myname, p);
            exit(-1);
        }
        /*
         * 如果指定,则忽视主群组,笔者在上面已经追加了一个主群组.
         */
        if (group == NULL || gidlist[gids] != gid)
            gids++;
    }
    if (p != NULL && gids == NGROUPS_MAX) {
        fprintf(stderr, "%s: Too many supplementary groups provided\n", myname);
        exit(-1);
    }
    if (gids && setgroups(gids, gidlist) != 0){
        fprintf(stderr, "%s: setgroups", myname);
        exit(-1);
    }
    
    if (user != NULL) {
        if ((pw = getpwnam(user)) != NULL)
            uid = pw->pw_uid;
        else if (isdigit((unsigned char)*user)) {
            errno = 0;
            ul = strtoul(user, &endp, 10);
            if (*endp != '\0' || (ul = ULONG_MAX && errno == ERANGE)) {
                fprintf(stderr, "%s: Invalid user ID `%s'\n", myname, user);
                exit(-1);
            }
            uid = (uid_t)ul;
        }
        else {
            fprintf(stderr, "%s: No such user `%s'\n", myname, user);
            exit(-1);
        }
    }

    if (fulluser != NULL) {
        if ((pw = getpwnam(fulluser)) == NULL) {
            fprintf(stderr, "%s: No such user `%s'\n", myname, fulluser);
            exit(-1);
        }
        uid = pw->pw_uid;
        gid = pw->pw_gid;
        if (setgid(gid) != 0) {
            fprintf(stderr, "%s: setgid\n", myname);
            exit(-1);
        }
        if (initgroups(fulluser, gid) == -1) {
            fprintf(stderr, "%s: initgroups\n", myname);
            exit(-1);
        }
    }

    if (chroot(argv[0]) != 0 || chdir("/") != 0) {
        fprintf(stderr, "%s: %s\n", myname, argv[0]);
        exit(-1);
    }

    if ((user || fulluser) && setuid(uid) != 0) {
        fprintf(stderr, "%s: setuid\n", myname);
        exit(-1);
    }

    if (argv[1]) {
        execvp(argv[1], &argv[1]);
        fprintf(stderr, "%s: %s\n", myname, argv[1]);
        exit(-1);
    }

    if ((shell = getenv("SHELL")) == NULL) 
        shell = "/bin/sh";
    execlp(shell, shell, "-i", (char *)NULL);
    fprintf(stderr, "%s, %s\n", myname, shell);
    /* 没有走样 */
}

void usage(char *myname)
{
    (void)fprintf(stderr, "usage: %s [-g group] [-G group,group,...] "
        "[-u user] [-U user] newroot [command]\n", myname);
    exit(1);
}

/* 这是Solaris上丢失的strsep的替代品. */
static char *getToken(char **str, const char *delims)
{
    char *token;
    
    if (*str == NULL) {
        /* No more tokens */
        return NULL;
    }

    token = *str;
    while (**str != '\0') {
        if (strchr(delims, **str) != NULL) {
            **str = '\0';
            (*str)++;
            return token;
        }
        (*str)++;
    }
    /* There is no other token */
    *str = NULL;
    return token;
}

19.BadInputValve.java

package com.oreilly.tomcat.valve;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.ParameterMap;
import org.apache.catalina.valves.RequestFilterValve;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

/**
 * 过滤恶意用户从HTT请求产生的输入,
 * 避免遭受包括Cross Site Scripting(XSS)、SQL注入、
 * 和HTML注入漏洞及其他黑客攻击。
*/
public class BadInputValve extends RequestFilterValve {
    // -----------------------------------------静态变量

    /**
     * 记录日志的Log实例。
     */
    private static Log log = LogFactory.getLog(BadInputValve.class);

    /**
     * 关于这一实现的描述信息
     */
    protected static String info =
        "com.oreilly.tomcat.valve.BadInputValve/2.0";

    /**
     * 空字符串数组,重用为toArray()的类型指示器。
    */
    private static final String[] STRING_ARRAY = new String[0];

    // -----------------------------------------实例变量
    
    /**
     * 该标志判断转义引号(escape quotes)是否为请求的一部分。
     */
    protected boolean escapeQuotes = false;

    /**
     * 该标志判断转义尖括号(escape angle brackets)是否为请求的一部分。
     */
    protected boolean escapeAngleBrackets = false;
    
    /**
     * 该标志判断转义JavaScript (escape JavaScript)函数和对象名是否为请求的一部分.
     */
    protected boolean escapeJavaScript = false;

    /**
     * 替代映射 (映射、替代的常规表达式)。
     * 用于替代单引号(') 和双引号(")的转义等同物。
     * 使其无法用于恶意目的。
     */
    protected HashMap<String, String> quotesHashMap =
        new HashMap<String, String>();

    /**
     * 替代映射(映射、替代的常规表达式)。
     * 用于替代尖括号(<>)的转义等同物。使其无法用于恶意目的。
     */
    protected HashMap<String, String> angleBracketsHashMap = 
        new HashMap<String, String>();

    /**
     * 替代映射( 映射、替代的常规表达式)。
     * 用于替代存在安全隐患的JavaScript函数调用转义等同物。
     * 使其无法用于恶意目的。
     */
    protected HashMap<String, String> javaScriptHashMap = 
        new HashMap<String, String>();

    /**
     * 用于过滤参数的常规表达式映射。
     * 如果找到了检索的字符串,则关键在于检查的常规表达式字符串,
     * 及用于修改参数的常规表达式字符串。
     */
    protected HashMap<String, String> parameterEscapes =
        new HashMap<String, String>();

    // ------------------------------构造器
    
    /**
     * 构建这个类的新实例,包括默认属性值。
     */
    public BadInputValve() {
        super();

        //输出regex转义映射。
        quotesHashMap.put("\"", "&quot;");
        quotesHashMap.put("\'", "&#39;");
        quotesHashMap.put("`", "&#96;");
        angleBracketsHashMap.put("<", "&lt;");
        angleBracketsHashMap.put(">", "&gt;");
        javaScriptHashMap.put(
            "document(.*)\\.(.*)cookie", "document&#46;&#99;ookie");
        javaScriptHashMap.put("eval(\\s*)\\(", "eval&#40;");
        javaScriptHashMap.put("setTimeout(\\s*)\\(", "setTimeout$1&#40;");
        javaScriptHashMap.put("setInterval(\\s*)\\(", "setInterval$1&#40;");
        javaScriptHashMap.put("execScript(\\s*)\\(", "execScript$1&#40;");
        javaScriptHashMap.put("(?i)javascript(?-i):", "javascript&#58;");

        log.info("BadInputValve instantiated.");
    }

    // --------------------------------------------属性
    /**
     * 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的引号(双引号和单引号)。
     */
    public boolean getEscapeQuotes() {
        return escapeQuotes;
    }

    /**
     * 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的引号(双引号和单引号)。
     */

    /*
     * @param escapeQuotes
     */
    public void setEscapeQuotes(boolean escapeQuotes) {
        this.escapeQuotes = escapeQuotes;
        if (escapeQuotes) {
            //转义所有引号。
            parameterEscapes.putAll(quotesHashMap);
        }
    }

    /**
     * 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的尖括号(<>)。
     */
    public boolean getEscapeAngleBrackets() {
        return escapeAngleBrackets;
    }
    

    /**
     *在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的尖括号(<>)
     */
    public boolean getEscapeAngleBrackets() {
        return escapeAngleBrackets;
    }

    /**
     * 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的尖括号(<>)。
     * @param escapeAngleBrackets
     */
    public void setEscapeAngleBrackets(boolean escapeAngleBrackets) {
        this.escapeAngleBrackets = escapeAngleBrackets;
        if (escapeAngleBrackets) {
            // 转义所有尖括号.
            parameterEscapes.putAll(angleBracketsHashMap);
        }
    }

    /**
     * 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分
     *的对JavaScript函数的潜在危险引用。
     */
    public boolean getEscapeJavaScript() {
        return escapeJavaScript;
    }

    /**
     * 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求
     * 一部分的对JavaScript函数和对象的潜在危险引用。
     *
     * @param escapeJavaScript
     */
    public void setEscapeJavaScript(boolean escapeJavaScript) {
        this.escapeJavaScript = escapeJavaScript;
        if (escapeJavaScript) {
            // Escape potentially dangerous JavaScript method calls.
            parameterEscapes.putAll(javaScriptHashMap);
        }
    }

    /**
     * 返回关于该门限(Valve)实现的描述信息。
     */
    public String getInfo() {
        return info;
    }

    // ------------------------------------公共方法
    /**
     * 在恶意用户进入web应用程序之前,消除请求参数。
     *
     * @param request 要被处理的servlet请求
     * @param response 要被创建的servlet响应
     *
     * @excaption IOException 如果发生输入/输出错误
     * @exception ServletException 如果发生servlet错误
     */
    @Override
    public void invoke(Request request, Response response) 
        throws IOException, ServletException {
        
        //跳过对non-HTTP请求与响应的过滤。
        if (!(request instanceof HttpServletRequest) ||
            !(response instanceof HttpServletResponse)) {
            getNext().invoke(request, response);
            return;
        }

        //基于allow和deny,只让请求通过。
        if (processAllowAndDenies(request, response)) {
            //过滤存在安全隐患的JavasScript代码,
            //从而在Tomcat开始执行请求之前,
            //清楚了黑客的请求。
            filterparameters(request);

            //执行该请求
            getNext().invoke(request, response);
        }
    }

    /**
     * 使用(摘要)RequestFilterValve 的泛函性
     * 以终止在参数名和参数值中包含禁用字符串模式的请求
     *
     * 
     * @param request 要被处理的servlet请求
     * @param response 要被创建的servlet响应
     *
     * @exception IOException 如果发生输入/输出错误
     * @exception ServletException 如果发生servlet错误
     *
     * @return false 如果请求禁止,否则为true。
     */
    public boolean processAllowsAndDenies(Request request, Response response)
        throws IOException, ServletException {
        ParameterMap paramMap = 
            (ParameterMap) ((HttpServletRequest) request).getParameterMap();
        //循环参数清单。
        Iterator y = paramMap.keySet().iterator();
        while (y.hasNext()) {
            String name = (String) y.next();
            String[] values = ((HttpServletRequest)
                request).getParameterValues(name);

            //查看名字中是否包含禁止模式。
            if (!checkAllowsAndDenies(name, response)) {
                return false;
            }

            // 检查该模式的参数值。
            if (values != null) {
                for (int i=0; i<values.length; i++) {
                    String value = values[i];
                    if (!checkAllowsAndDenies(value, response)) {
                        return false;
                    }
                }
            }
        }
        
        // 没有参数则导致拒绝,将继续响应请求。
        return true;
    }

    /**
     * 执行为该Valve配置的过滤,
     * 映射指定的请求属性。如果允许处理请求,
     * 则该方法返回true,否则,
     * 该方法发送一条Forbidden错误响应网页,并返回false。
     *
     * <br><br>
     *
     * 该方法借鉴了RequestFilterValve.process()的大部分精华,
     * 只有这一方法拥有boolean返回类型而不用调用
     * getNext().invoke(request, response).
     *
     * @param property 要过滤的请求属性。
     * @param response 要被处理的servlet响应。
     *
     * @exception IOException 如果发生一个输入/输出错误
     * @exception ServletException 如果发生一个error错误
     *
     * @return true 如果仍然允许处理该请求。
     */
    public boolean checkAllowsAndDenies(String property, Response response)
        throws IOException, ServletException {
    
        // 如果既没有deny也没有allow,则处理该请求。
        if (denies.length == 0 && allows.length == 0) {
            return true;
        }

        // 如果有的话,则检查deny模式。
        for (int i=0; i<denies.length; i++) {
            Matcher m = denies[i].matcher(property);
            if (m.find()) {
                ServletResponse sres = response.getResponse();
                if (sres instanceof HttpServletResponse) {
                    HttpServletResponse hres = (HttpServletResponse) sres;
                    hres.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return false;
                }
            }
        }
        
        //如果有的话,则检查allow模式
        for (int i=0; i<allows.length; i++) {
            Matcher m = allows[i].matcher(property);
            if (m.find()) {
                return true;
            }
        }

        // Allow, 如果指定了denies而没有指定allows
        if (denies.length > 0 && allows.length == 0) {
                return true;
        }

        // 否则,拒绝响应请求。
        ServletResponse sres = response.getResponse();
        if (sres instanceof HttpServletResponse) {
            HttpServletResponse hres = (HttpServletResponse) sres;
            hres.sendError(HttpServletResponse.SC_FORBIDDEN);
        }
        return false;
    }

    /**
     * 对具有安全隐患的内容,如果找到了转义字符,则过滤所有现存的参数。
     *
     * @param request 包含该参数的请求。
     */
    public void filterParameters(Request request) {
        ParameterMap paramMap = 
            (ParameterMap) ((HttpServletRequest) request).getParameterMap();
        // 解除参数映射上的锁,从而可以修改该参数。
        paramMap.setLocked(false);

        // 循环每个替代模式。
        Iterator escapesIterator = parameterEscapes.keySet().iterator();
        while (escapesIterator.hasNext()) {
            String patternString = (String) escapesIterator.next();
            Pattern pattern = Pattern.compile(patternString);

            // 循环参数列表。
            @SuppressWarnings("unchecked")
            String[] paramNames = 
                (String[]) paramMap.keySet().toArray(STRING_ARRAY);
            for (int i=0; i<paramNames.length; i++) {
                String name = paramNames[i];
                String[] values = ((HttpServletRequest)request).getParameterValues(name);
                //查看名字中是否包含该模式。
                boolean nameMatch;
                Matcher matcher = pattern.matcher(name);
                nameMatch = matcher.find();
                if (nameMatch) {
                    // 参数名与模式匹配,从而可以用修改名字,
                    // 追加参数后作为新名字、移除老名字
                    // 等方法修补之。
                    String newName = matcher.replaceAll(
                        (String) parameterEscapes.get(patternString));
                    request.addParameter(newName, values);
                    paramMap.remove(name);
                    log.warn("Parameter name " + name +
                        " matched pattern \"" + patternString +
                        "\". Remote addr: " +
                        ((HttpServletRequest) request).getRemoteAddr( ));
                }
                // 检查该模式的参数值。
                if (values != null) {
                    for (int j=0; j<values.length; j++) {
                        String value = values[j];
                        boolean valueMatch;
                        matcher = pattern.matcher(value);
                        valueMatch = matcher.find( );
                        if (valueMatch) {
                            // 匹配的值,所有修改它,然后赋回数组
                            String newValue;
                            newValue = matcher.replaceAll((String) parameterEscapes.get(patternString));
                            values[j] = newValue;
                            log.warn("Parameter \"" + name +
                                "\"'s value \"" + value +
                                "\" matched pattern \"" +
                                patternString + "\". Remote addr: " +
                                ((HttpServletRequest)request).getRemoteAddr( ));
                        }
                    }
                }
            }
        }
        // 在完成以后,确保锁住了参数。
        paramMap.setLocked(true);
    }

    /**
     * 返回该对象的文本表现
     */
    @Override
    public String toString() {
        return "BadInputValve";
    }
}
                    
     

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值