Tomcat 屏蔽错误信息。java.lang.IllegalArgumentException: 在请求目标中找到无效字符。有效字符在RFC 7230和RFC 3986中定义
<h1>HTTP状态 400 - 错误的请求</h1>
<hr class="line" />
<p><b>类型</b> 异常报告</p>
<p><b>消息</b> 在请求目标中找到无效字符。有效字符在RFC 7230和RFC 3986中定义</p>
<p><b>描述</b> 由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者虚拟的请求路由),服务器无法或不会处理当前请求。</p>
<p><b>例外情况</b></p>
<pre>java.lang.IllegalArgumentException: 在请求目标中找到无效字符。有效字符在RFC 7230和RFC 3986中定义
org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:512)
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:503)
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831)
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1631)
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
java.lang.Thread.run(Thread.java:750)
简介
在开发和生产环境中,出于安全和隐私的考虑,我们可能不希望将详细的错误信息暴露给用户。Tomcat 提供了一种机制,允许我们通过配置 ErrorReportValve
来控制错误报告的显示。本文将详细介绍如何在 Tomcat 的 server.xml
文件中配置 ErrorReportValve
,以屏蔽错误报告和服务器信息。
为什么需要屏蔽错误报告?
-
安全考虑:错误报告可能包含敏感信息,如数据库连接字符串、系统路径等,这些信息如果被恶意用户获取,可能会对系统安全造成威胁。
-
用户体验:对于最终用户来说,看到详细的错误信息可能会引起困惑或不安。提供一个友好的错误页面可以改善用户体验。
-
避免信息泄露:在生产环境中,错误报告可能会泄露系统的内部工作原理,这可能会被恶意用户利用。
如何配置 Tomcat 屏蔽错误报告
步骤 1:打开 server.xml
文件
server.xml
文件通常位于 Tomcat 的 conf
目录下。使用文本编辑器打开此文件。
步骤 2:添加 ErrorReportValve
在 <Host>
标签内添加 <Valve>
标签,并设置 className
为 org.apache.catalina.valves.ErrorReportValve
,同时将 showReport
和 showServerInfo
设置为 false
。如下所示:
xml复制
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!-- 其他配置 -->
<Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false"/>
</Host>
步骤 3:保存并重启 Tomcat
保存 server.xml
文件后,重启 Tomcat 以使更改生效。
步骤 4:测试配置
为了验证配置是否生效,可以尝试访问一个不存在的页面或故意引发一个错误。Tomcat 应该不再显示详细的错误报告和服务器信息。
注意事项
-
调试问题:在开发环境中,关闭错误报告可能会使调试问题变得更加困难。因此,建议仅在生产环境中关闭错误报告。
-
日志记录:虽然错误报告被屏蔽了,但错误信息仍然会记录在 Tomcat 的日志文件中。确保你的日志记录策略能够满足故障排查的需求。
-
自定义错误页面:为了提供更好的用户体验,你可以配置自定义的错误页面,当发生错误时,引导用户到这些页面。
结论
通过在 Tomcat 中配置 ErrorReportValve
,我们可以有效地屏蔽错误报告和服务器信息,从而提高系统的安全性和用户体验。然而,这也意味着我们需要更加依赖日志文件来进行故障排查,因此建立一个有效的日志记录和监控策略是非常重要的。
##tomcat-embed-core-8.5.72-sources.jar!/org/apache/catalina/valves/ErrorReportValve.java
源码
isShowReport() 展示错误堆栈
isShowServerInfo() 展示版本信息
protected void report(Request request, Response response, Throwable throwable) {
int statusCode = response.getStatus();
// Do nothing on a 1xx, 2xx and 3xx status
// Do nothing if anything has been written already
// Do nothing if the response hasn't been explicitly marked as in error
// and that error has not been reported.
if (statusCode < 400 || response.getContentWritten() > 0 || !response.setErrorReported()) {
return;
}
// If an error has occurred that prevents further I/O, don't waste time
// producing an error report that will never be read
AtomicBoolean result = new AtomicBoolean(false);
response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result);
if (!result.get()) {
return;
}
String message = Escape.htmlElementContent(response.getMessage());
if (message == null) {
if (throwable != null) {
String exceptionMessage = throwable.getMessage();
if (exceptionMessage != null && exceptionMessage.length() > 0) {
try (Scanner scanner = new Scanner(exceptionMessage)) {
message = Escape.htmlElementContent(scanner.nextLine());
}
}
}
if (message == null) {
message = "";
}
}
// Do nothing if there is no reason phrase for the specified status code and
// no error message provided
String reason = null;
String description = null;
StringManager smClient = StringManager.getManager(
Constants.Package, request.getLocales());
response.setLocale(smClient.getLocale());
try {
reason = smClient.getString("http." + statusCode + ".reason");
description = smClient.getString("http." + statusCode + ".desc");
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
if (reason == null || description == null) {
if (message.isEmpty()) {
return;
} else {
reason = smClient.getString("errorReportValve.unknownReason");
description = smClient.getString("errorReportValve.noDescription");
}
}
StringBuilder sb = new StringBuilder();
sb.append("<!doctype html><html lang=\"");
sb.append(smClient.getLocale().getLanguage()).append("\">");
sb.append("<head>");
sb.append("<title>");
sb.append(smClient.getString("errorReportValve.statusHeader",
String.valueOf(statusCode), reason));
sb.append("</title>");
sb.append("<style type=\"text/css\">");
sb.append(TomcatCSS.TOMCAT_CSS);
sb.append("</style>");
sb.append("</head><body>");
sb.append("<h1>");
sb.append(smClient.getString("errorReportValve.statusHeader",
String.valueOf(statusCode), reason)).append("</h1>");
if (isShowReport()) {
sb.append("<hr class=\"line\" />");
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.type"));
sb.append("</b> ");
if (throwable != null) {
sb.append(smClient.getString("errorReportValve.exceptionReport"));
} else {
sb.append(smClient.getString("errorReportValve.statusReport"));
}
sb.append("</p>");
if (!message.isEmpty()) {
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.message"));
sb.append("</b> ");
sb.append(message).append("</p>");
}
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.description"));
sb.append("</b> ");
sb.append(description);
sb.append("</p>");
if (throwable != null) {
String stackTrace = getPartialServletStackTrace(throwable);
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.exception"));
sb.append("</b></p><pre>");
sb.append(Escape.htmlElementContent(stackTrace));
sb.append("</pre>");
int loops = 0;
Throwable rootCause = throwable.getCause();
while (rootCause != null && (loops < 10)) {
stackTrace = getPartialServletStackTrace(rootCause);
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.rootCause"));
sb.append("</b></p><pre>");
sb.append(Escape.htmlElementContent(stackTrace));
sb.append("</pre>");
// In case root cause is somehow heavily nested
rootCause = rootCause.getCause();
loops++;
}
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.note"));
sb.append("</b> ");
sb.append(smClient.getString("errorReportValve.rootCauseInLogs"));
sb.append("</p>");
}
sb.append("<hr class=\"line\" />");
}
if (isShowServerInfo()) {
sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
}
sb.append("</body></html>");
try {
try {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("status.setContentType", t);
}
}
Writer writer = response.getReporter();
if (writer != null) {
// If writer is null, it's an indication that the response has
// been hard committed already, which should never happen
writer.write(sb.toString());
response.finishResponse();
}
} catch (IOException | IllegalStateException e) {
// Ignore
}
}