问题:
线上日志里发现有StackOverflowError
18:47:33.535 [http-nio-8443-exec-6] ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]:175 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause
java.lang.StackOverflowError: null
at java.base/java.lang.StringUTF16.length(StringUTF16.java:74)
at java.base/java.lang.StringUTF16.checkIndex(StringUTF16.java:1470)
at java.base/java.lang.StringUTF16.charAt(StringUTF16.java:1267)
at java.base/java.lang.String.charAt(String.java:695)
原因:
是因为使用日志组件logbook,会默认配置一个PrimitiveJsonPropertyBodyFilter,这个Filter是用来隐藏一些access_token之类的敏感数据的。使用的方式是正则匹配。
正则匹配在规则比较复杂,匹配内容比较大时很容易StackOverflowError。
LogbookAutoConfiguration.java
@API(status = INTERNAL)
@Bean
@ConditionalOnMissingBean(BodyFilter.class)
public BodyFilter bodyFilter() {
final LogbookProperties.Write write = properties.getWrite();
final int maxBodySize = write.getMaxBodySize();
if (maxBodySize < 0) {
return defaultValue();
}
return BodyFilter.merge(defaultValue(), truncate(maxBodySize));
}
BodyFilters.java
public static BodyFilter defaultValue() {
return defaultValues(BodyFilter.class).stream()
.reduce(oauthRequest(), BodyFilter::merge);
}
DefaultFilters.java 通过SPI加载BodyFilter
static <T> Collection<T> defaultValues(final Class<T> defaultType) {
return stream(load(defaultType).spliterator(), false).collect(toList());
}
logbook-json-2.13.0.jar/META-INFO/services/org.zalando.logbook.BodyFilter
org.zalando.logbook.json.CompactingJsonBodyFilter //压缩json
org.zalando.logbook.json.AccessTokenBodyFilter //隐藏json里敏感的key
JsonBodyFilters.java
public static BodyFilter accessToken() {
final Set<String> properties = new HashSet<>(Arrays.asList(
"access_token", "refresh_token", "open_id", "id_token"));
return replaceJsonStringProperty(properties, "XXX");
}
public static BodyFilter replaceJsonStringProperty(
final Predicate<String> predicate, final String replacement) {
return replaceString(predicate, replacement);
}
PrimitiveJsonPropertyBodyFilter.java
static BodyFilter replaceString(
final Predicate<String> predicate, final String replacement) {
return create(STRING, predicate, new StaticReplacement(replacement).andThen(quote()));
}
@Override
public String filter(@Nullable final String contentType, final String body) {
if (JsonMediaType.JSON.test(contentType)) {
final Matcher matcher = pattern.matcher(body);
final StringBuffer result = new StringBuffer(body.length());
while (matcher.find()) {
if (predicate.test(matcher.group("property"))) {
// this preserves whitespaces around properties
matcher.appendReplacement(result, "${key}");
result.append(replacement.apply(
matcher.group("property"),
matcher.group("propertyValue")));
} else {
matcher.appendReplacement(result, "$0");
}
}
matcher.appendTail(result);
return result.toString();
}
return body;
}
解决方案:
覆盖声明bodyFilter,移除这个Filter
@Bean
public BodyFilter bodyFilter() {
org.zalando.logbook.autoconfigure.LogbookProperties.Write write = this.logbookProperties.getWrite();
int maxBodySize = write.getMaxBodySize();
BodyFilter defaultBodyFilter = new CompactingJsonBodyFilter();
return maxBodySize < 0 ? defaultBodyFilter : BodyFilter.merge(defaultBodyFilter, BodyFilters.truncate(maxBodySize));
}
这个StackOverflowError问题,还会导致另外一个问题,查看另外一篇 SpringBoot Filter Exception导致响应json额外追加了一段json。