Tomcat 安全配置与防护

Security Configuration and Protection

概述

Tomcat安全配置是生产环境部署的关键要素。本文介绍Tomcat的安全加固方法、访问控制、认证授权、安全Valve配置和安全监控等关键技术。

1. 基础安全加固

1.1 移除默认应用

#!/bin/bash
# remove-default-apps.sh

CATALINA_HOME="/opt/tomcat9"
WEBAPPS_DIR="$CATALINA_HOME/webapps"

# 移除不安全的默认应用
echo "移除默认Web应用..."

# 移除示例应用
rm -rf "$WEBAPPS_DIR/examples"
rm -rf "$WEBAPPS_DIR/docs"

# 可选:移除ROOT应用(如果不需要)
# rm -rf "$WEBAPPS_DIR/ROOT"

# 可选:移除host-manager(如果不需要)
# rm -rf "$WEBAPPS_DIR/host-manager"

# 保留manager应用但限制访问
if [ -d "$WEBAPPS_DIR/manager" ]; then
    echo "配置manager应用访问限制..."
    cat > "$WEBAPPS_DIR/manager/META-INF/context.xml" << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="false" privileged="true">
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="127\.0\.0\.1|192\.168\.1\..*" />
</Context>
EOF
fi

echo "默认应用清理完成"

1.2 隐藏服务器信息

<!-- server.xml - 隐藏服务器版本信息 -->
<Connector port="8080" 
           protocol="HTTP/1.1"
           server="Apache"
           connectionTimeout="20000"
           redirectPort="8443" />

<!-- 配置错误页面 -->
<Host name="localhost" appBase="webapps">
  <Valve className="org.apache.catalina.valves.ErrorReportValve"
         showReport="false"
         showServerInfo="false" />
</Host>

1.3 文件权限设置

#!/bin/bash
# secure-permissions.sh

CATALINA_HOME="/opt/tomcat9"
TOMCAT_USER="tomcat"

# 设置目录所有权
chown -R $TOMCAT_USER:$TOMCAT_USER $CATALINA_HOME

# 配置文件权限
chmod 600 $CATALINA_HOME/conf/server.xml
chmod 600 $CATALINA_HOME/conf/tomcat-users.xml
chmod 600 $CATALINA_HOME/conf/web.xml
chmod 600 $CATALINA_HOME/conf/context.xml

# 脚本文件权限
chmod 750 $CATALINA_HOME/bin/*.sh

# 日志目录权限
chmod 750 $CATALINA_HOME/logs
chmod 640 $CATALINA_HOME/logs/*.log

# 临时目录权限
chmod 750 $CATALINA_HOME/temp
chmod 750 $CATALINA_HOME/work

# webapps目录权限
chmod 750 $CATALINA_HOME/webapps
find $CATALINA_HOME/webapps -type d -exec chmod 750 {} \;
find $CATALINA_HOME/webapps -type f -exec chmod 640 {} \;

echo "文件权限设置完成"

2. 用户认证与授权

2.1 数据库认证配置

<!-- server.xml - 数据库Realm配置 -->
<Realm className="org.apache.catalina.realm.DataSourceRealm"
       dataSourceName="jdbc/AuthDB"
       userTable="users"
       userNameCol="username"
       userCredCol="password"
       userRoleTable="user_roles"
       roleNameCol="role_name"
       digest="SHA-256"
       digestEncoding="UTF-8" />

<!-- context.xml - 数据源配置 -->
<Resource name="jdbc/AuthDB" 
          auth="Container"
          type="javax.sql.DataSource"
          driverClassName="com.mysql.cj.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/authdb?useSSL=true"
          username="authuser"
          password="authpass"
          maxTotal="20"
          maxIdle="10"
          validationQuery="SELECT 1" />

数据库表结构:

-- 用户表
CREATE TABLE users (
    username VARCHAR(50) PRIMARY KEY,
    password VARCHAR(64) NOT NULL,
    email VARCHAR(100),
    enabled BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 角色表
CREATE TABLE user_roles (
    username VARCHAR(50) NOT NULL,
    role_name VARCHAR(50) NOT NULL,
    PRIMARY KEY (username, role_name),
    FOREIGN KEY (username) REFERENCES users(username)
);

-- 插入测试数据
INSERT INTO users VALUES 
('admin', SHA2('admin123', 256), 'admin@example.com', TRUE, NOW()),
('manager', SHA2('manager123', 256), 'manager@example.com', TRUE, NOW());

INSERT INTO user_roles VALUES 
('admin', 'admin'),
('admin', 'manager'),
('manager', 'manager');

2.2 LDAP认证配置

<!-- LDAP Realm配置 -->
<Realm className="org.apache.catalina.realm.JNDIRealm"
       connectionURL="ldap://ldap.example.com:389"
       connectionName="cn=admin,dc=example,dc=com"
       connectionPassword="ldappass"
       userPattern="uid={0},ou=people,dc=example,dc=com"
       userBase="ou=people,dc=example,dc=com"
       userSearch="(uid={0})"
       userSubtree="true"
       roleBase="ou=groups,dc=example,dc=com"
       roleName="cn"
       roleSearch="(member={0})"
       roleSubtree="true" />

2.3 多重认证配置

<!-- 组合Realm配置 -->
<Realm className="org.apache.catalina.realm.CombinedRealm">
  <!-- 主认证:LDAP -->
  <Realm className="org.apache.catalina.realm.JNDIRealm"
         connectionURL="ldap://ldap.example.com:389"
         userPattern="uid={0},ou=people,dc=example,dc=com"
         roleBase="ou=groups,dc=example,dc=com"
         roleName="cn"
         roleSearch="(member={0})" />

  <!-- 备用认证:数据库 -->
  <Realm className="org.apache.catalina.realm.DataSourceRealm"
         dataSourceName="jdbc/AuthDB"
         userTable="users"
         userNameCol="username"
         userCredCol="password"
         userRoleTable="user_roles"
         roleNameCol="role_name"
         digest="SHA-256" />
</Realm>

3. 访问控制配置

3.1 IP访问控制

<!-- 基于IP的访问控制 -->
<Host name="secure.example.com" appBase="webapps">

  <!-- 远程地址过滤器 -->
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="192\.168\.1\..*|10\.0\.0\..*|127\.0\.0\.1"
         deny=".*" />

  <!-- 远程主机过滤器 -->
  <Valve className="org.apache.catalina.valves.RemoteHostValve"
         allow=".*\.example\.com|localhost"
         deny=".*\.malicious\.com" />
</Host>

3.2 自定义安全Valve

// SecurityValve.java
package com.example.security;

import org.apache.catalina.valves.ValveBase;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;

import javax.servlet.ServletException;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class SecurityValve extends ValveBase {

    private int maxRequestsPerMinute = 60;
    private String blockedUserAgents = "bot|crawler|spider";
    private ConcurrentHashMap<String, AtomicInteger> requestCounts = new ConcurrentHashMap<>();

    @Override
    public void invoke(Request request, Response response) 
            throws IOException, ServletException {

        String clientIP = getClientIP(request);
        String userAgent = request.getHeader("User-Agent");

        // 检查恶意User-Agent
        if (isBlockedUserAgent(userAgent)) {
            response.sendError(403, "Forbidden User Agent");
            return;
        }

        // 限流检查
        if (isRateLimited(clientIP)) {
            response.sendError(429, "Too Many Requests");
            return;
        }

        // 检查恶意请求模式
        if (isMaliciousRequest(request)) {
            response.sendError(403, "Malicious Request Detected");
            return;
        }

        getNext().invoke(request, response);
    }

    private String getClientIP(Request request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }

    private boolean isBlockedUserAgent(String userAgent) {
        if (userAgent == null) return true;
        return userAgent.toLowerCase().matches(".*(" + blockedUserAgents + ").*");
    }

    private boolean isRateLimited(String clientIP) {
        AtomicInteger count = requestCounts.computeIfAbsent(clientIP, k -> new AtomicInteger(0));
        int currentCount = count.incrementAndGet();

        // 简化的滑动窗口实现
        if (currentCount > maxRequestsPerMinute) {
            return true;
        }

        // 重置计数器(实际应用中应使用更精确的时间窗口)
        if (currentCount % 100 == 0) {
            requestCounts.remove(clientIP);
        }

        return false;
    }

    private boolean isMaliciousRequest(Request request) {
        String uri = request.getRequestURI();
        String queryString = request.getQueryString();

        // 检查SQL注入模式
        String[] sqlPatterns = {"union", "select", "insert", "delete", "drop", "exec"};
        String fullUrl = uri + (queryString != null ? "?" + queryString : "");

        for (String pattern : sqlPatterns) {
            if (fullUrl.toLowerCase().contains(pattern.toLowerCase())) {
                return true;
            }
        }

        // 检查XSS模式
        String[] xssPatterns = {"<script", "javascript:", "onload=", "onerror="};
        for (String pattern : xssPatterns) {
            if (fullUrl.toLowerCase().contains(pattern.toLowerCase())) {
                return true;
            }
        }

        return false;
    }
}

配置安全Valve:

<Host name="app.example.com" appBase="webapps">
  <Valve className="com.example.security.SecurityValve"
         maxRequestsPerMinute="60"
         blockedUserAgents="bot|crawler|spider|scanner" />
</Host>

4. SSL/TLS安全配置

4.1 安全的SSL配置

<Connector port="8443" 
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           SSLEnabled="true"
           scheme="https" 
           secure="true">

  <SSLHostConfig protocols="TLSv1.2,TLSv1.3"
                 ciphers="ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305,ECDHE-RSA-CHACHA20-POLY1305"
                 honorCipherOrder="true"
                 disableCompression="true"
                 sessionTimeout="300"
                 certificateVerification="none">

    <Certificate certificateKeystoreFile="conf/keystore.jks"
                 certificateKeystorePassword="changeit"
                 type="RSA" />
  </SSLHostConfig>
</Connector>

4.2 强制HTTPS重定向

<!-- 自动重定向到HTTPS -->
<Host name="secure.example.com" appBase="webapps">

  <!-- HTTPS重定向阀门 -->
  <Valve className="org.apache.catalina.valves.SSLValve" />

  <!-- 安全头设置 -->
  <Valve className="org.apache.catalina.valves.HttpHeaderSecurityFilter"
         hstsEnabled="true"
         hstsMaxAgeSeconds="31536000"
         hstsIncludeSubdomains="true"
         hstsPreload="true"
         antiClickJackingEnabled="true"
         antiClickJackingOption="SAMEORIGIN"
         blockContentTypeSniffingEnabled="true" />
</Host>

5. 会话安全配置

5.1 安全会话管理

<!-- 安全会话配置 -->
<Context path="/secure-app" docBase="secure-app.war">

  <!-- 会话管理器 -->
  <Manager className="org.apache.catalina.session.StandardManager"
           sessionIdLength="32"
           secureRandomClass="java.security.SecureRandom"
           secureRandomAlgorithm="SHA1PRNG"
           sessionAttributeNameFilter=".*"
           sessionAttributeValueClassNameFilter="java\.lang\..*|java\.util\..*" />

  <!-- 会话cookie配置 -->
  <CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
                   sameSiteCookies="strict" />
</Context>

5.2 会话固化防护

// SessionSecurityFilter.java
package com.example.security;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;

public class SessionSecurityFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpSession session = httpRequest.getSession(false);

        // 检查会话固化攻击
        if (session != null && isSessionFixationAttack(httpRequest, session)) {
            session.invalidate();
            session = httpRequest.getSession(true);
        }

        // 设置安全头
        httpResponse.setHeader("X-Content-Type-Options", "nosniff");
        httpResponse.setHeader("X-Frame-Options", "SAMEORIGIN");
        httpResponse.setHeader("X-XSS-Protection", "1; mode=block");
        httpResponse.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");

        chain.doFilter(request, response);
    }

    private boolean isSessionFixationAttack(HttpServletRequest request, HttpSession session) {
        String remoteAddr = request.getRemoteAddr();
        String userAgent = request.getHeader("User-Agent");

        String sessionRemoteAddr = (String) session.getAttribute("REMOTE_ADDR");
        String sessionUserAgent = (String) session.getAttribute("USER_AGENT");

        if (sessionRemoteAddr == null || sessionUserAgent == null) {
            session.setAttribute("REMOTE_ADDR", remoteAddr);
            session.setAttribute("USER_AGENT", userAgent);
            return false;
        }

        return !remoteAddr.equals(sessionRemoteAddr) || 
               !userAgent.equals(sessionUserAgent);
    }
}

6. 安全监控与审计

6.1 安全日志配置

<!-- 安全审计阀门 -->
<Valve className="org.apache.catalina.valves.AccessLogValve" 
       directory="logs"
       prefix="security_audit_log" 
       suffix=".txt"
       pattern='%h %l %u %t "%r" %s %b %D "%{Referer}i" "%{User-Agent}i" %{X-Forwarded-For}i'
       rotatable="true"
       maxDays="90" />

<!-- 错误审计配置 -->
<Valve className="org.apache.catalina.valves.ErrorReportValve"
       showReport="false"
       showServerInfo="false" />

6.2 安全监控脚本

#!/bin/bash
# security-monitor.sh

LOG_DIR="/opt/tomcat9/logs"
ALERT_EMAIL="security@example.com"
ALERT_THRESHOLD=10

# 检查可疑活动
check_suspicious_activity() {
    echo "=== 安全监控报告 $(date) ==="

    # 检查404错误激增
    error_404_count=$(grep "\" 404 " "$LOG_DIR"/localhost_access_log.*.txt | wc -l)
    if [ "$error_404_count" -gt "$ALERT_THRESHOLD" ]; then
        echo "警告: 404错误数量异常 ($error_404_count)"
    fi

    # 检查SQL注入尝试
    sql_injection_attempts=$(grep -i "union\|select\|insert\|delete\|drop" "$LOG_DIR"/*.txt | wc -l)
    if [ "$sql_injection_attempts" -gt 0 ]; then
        echo "警告: 检测到SQL注入尝试 ($sql_injection_attempts)"
    fi

    # 检查XSS尝试
    xss_attempts=$(grep -i "script\|javascript\|onload\|onerror" "$LOG_DIR"/*.txt | wc -l)
    if [ "$xss_attempts" -gt 0 ]; then
        echo "警告: 检测到XSS攻击尝试 ($xss_attempts)"
    fi

    # 检查暴力破解尝试
    brute_force_ips=$(grep "\" 401 " "$LOG_DIR"/*.txt | awk '{print $1}' | sort | uniq -c | awk '$1>10{print $2}')
    if [ ! -z "$brute_force_ips" ]; then
        echo "警告: 检测到暴力破解尝试IP:"
        echo "$brute_force_ips"
    fi

    # 检查异常User-Agent
    bot_requests=$(grep -E "(bot|crawler|spider|scanner)" "$LOG_DIR"/*.txt | wc -l)
    if [ "$bot_requests" -gt 100 ]; then
        echo "警告: 机器人请求过多 ($bot_requests)"
    fi
}

# 生成安全报告
generate_security_report() {
    report_file="/tmp/security_report_$(date +%Y%m%d).txt"
    check_suspicious_activity > "$report_file"

    # 如果有安全问题,发送邮件
    if [ -s "$report_file" ]; then
        mail -s "Tomcat安全监控报告" "$ALERT_EMAIL" < "$report_file"
    fi
}

case "$1" in
    "check") check_suspicious_activity ;;
    "report") generate_security_report ;;
    *) echo "用法: $0 {check|report}" ;;
esac

7. 安全配置检查清单

7.1 安全检查脚本

#!/bin/bash
# security-checklist.sh

CATALINA_HOME="/opt/tomcat9"
SCORE=0
TOTAL_CHECKS=0

check_item() {
    local description="$1"
    local check_command="$2"

    TOTAL_CHECKS=$((TOTAL_CHECKS + 1))

    if eval "$check_command"; then
        echo "✓ $description"
        SCORE=$((SCORE + 1))
    else
        echo "✗ $description"
    fi
}

echo "=== Tomcat安全配置检查 ==="

# 1. 检查默认应用是否移除
check_item "移除examples应用" "[ ! -d '$CATALINA_HOME/webapps/examples' ]"
check_item "移除docs应用" "[ ! -d '$CATALINA_HOME/webapps/docs' ]"

# 2. 检查文件权限
check_item "server.xml权限正确" "[ $(stat -c %a '$CATALINA_HOME/conf/server.xml') = '600' ]"
check_item "tomcat-users.xml权限正确" "[ $(stat -c %a '$CATALINA_HOME/conf/tomcat-users.xml') = '600' ]"

# 3. 检查连接器配置
check_item "隐藏服务器版本" "grep -q 'server=\"Apache\"' '$CATALINA_HOME/conf/server.xml'"
check_item "错误报告已禁用" "grep -q 'showReport=\"false\"' '$CATALINA_HOME/conf/server.xml'"

# 4. 检查SSL配置
check_item "启用HTTPS连接器" "grep -q 'SSLEnabled=\"true\"' '$CATALINA_HOME/conf/server.xml'"
check_item "安全SSL协议" "grep -q 'protocols=\"TLS' '$CATALINA_HOME/conf/server.xml'"

# 5. 检查访问控制
check_item "Manager应用有访问限制" "[ -f '$CATALINA_HOME/webapps/manager/META-INF/context.xml' ]"

# 输出安全评分
echo
echo "=== 安全评分: $SCORE/$TOTAL_CHECKS ==="
percentage=$((SCORE * 100 / TOTAL_CHECKS))

if [ $percentage -ge 90 ]; then
    echo "安全状态: 优秀 ($percentage%)"
elif [ $percentage -ge 70 ]; then
    echo "安全状态: 良好 ($percentage%)"
elif [ $percentage -ge 50 ]; then
    echo "安全状态: 一般 ($percentage%)"
else
    echo "安全状态: 需要改进 ($percentage%)"
fi

小结

通过本文学习,你应该掌握:

  1. Tomcat基础安全加固方法
  2. 用户认证与授权配置技术
  3. 访问控制和IP过滤设置
  4. SSL/TLS安全配置最佳实践
  5. 会话安全管理技术
  6. 安全监控与审计方法
  7. 自定义安全Valve开发
  8. 安全配置检查清单

下一篇文章将专门介绍Tomcat SSL/TLS的详细配置。

powered by Gitbook© 2025 编外计划 | 最后修改: 2025-08-29 15:40:15

results matching ""

    No results matching ""