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
小结
通过本文学习,你应该掌握:
- Tomcat基础安全加固方法
- 用户认证与授权配置技术
- 访问控制和IP过滤设置
- SSL/TLS安全配置最佳实践
- 会话安全管理技术
- 安全监控与审计方法
- 自定义安全Valve开发
- 安全配置检查清单
下一篇文章将专门介绍Tomcat SSL/TLS的详细配置。