Disable exception details on default error views

Prior to this commit, default error responses included the message
from a handled exception. When the exception was a BindException, the
error responses could also include an errors attribute containing the
details of the binding failure. These details could leak information
about the application.

This commit removes the exception message and binding errors detail
from error responses by default, and introduces a
`server.error.include-details` property that can be used to cause
these details to be included in the response.

Fixes gh-20505
This commit is contained in:
Scott Frederick 2020-04-14 09:29:54 -05:00
parent 866147405c
commit 70d4994502
25 changed files with 617 additions and 180 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -47,7 +47,7 @@ public class ManagementErrorEndpoint {
@RequestMapping("${server.error.path:${error.path:/error}}")
@ResponseBody
public Map<String, Object> invoke(ServletWebRequest request) {
return this.errorAttributes.getErrorAttributes(request, false);
return this.errorAttributes.getErrorAttributes(request, false, false);
}
}

View File

@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Integration tests for {@link WebMvcEndpointChildContextConfiguration}.
*
* @author Phillip Webb
* @author Scott Frederick
*/
class WebMvcEndpointChildContextConfigurationIntegrationTests {
@ -59,7 +60,8 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests {
WebClient client = WebClient.create("http://localhost:" + port);
ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON)
.exchange().block();
assertThat(response.bodyToMono(String.class).block()).contains("message\":\"Epic Fail");
assertThat(response.bodyToMono(String.class).block())
.contains("message\":\"An error occurred while processing the request");
});
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -61,6 +61,7 @@ import static org.mockito.Mockito.verify;
*
* @param <T> the type of application context used by the tests
* @author Andy Wilkinson
* @author Scott Frederick
*/
public abstract class AbstractWebEndpointIntegrationTests<T extends ConfigurableApplicationContext & AnnotationConfigRegistry> {
@ -409,8 +410,10 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
BiConsumer<ApplicationContext, WebTestClient> consumer) {
T applicationContext = this.applicationContextSupplier.get();
contextCustomizer.accept(applicationContext);
applicationContext.getEnvironment().getPropertySources()
.addLast(new MapPropertySource("test", Collections.singletonMap("endpointPath", endpointPath)));
Map<String, Object> properties = new HashMap<>();
properties.put("endpointPath", endpointPath);
properties.put("server.error.include-details", "always");
applicationContext.getEnvironment().getPropertySources().addLast(new MapPropertySource("test", properties));
applicationContext.refresh();
try {
InetSocketAddress address = new InetSocketAddress(getPort(applicationContext));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value;
* @author Michael Stummvoll
* @author Stephane Nicoll
* @author Vedran Pavic
* @author Scott Frederick
* @since 1.3.0
*/
public class ErrorProperties {
@ -40,10 +41,15 @@ public class ErrorProperties {
private boolean includeException;
/**
* When to include a "stacktrace" attribute.
* When to include the "trace" attribute.
*/
private IncludeStacktrace includeStacktrace = IncludeStacktrace.NEVER;
/**
* When to include "message" and "errors" attributes.
*/
private IncludeDetails includeDetails = IncludeDetails.NEVER;
private final Whitelabel whitelabel = new Whitelabel();
public String getPath() {
@ -70,6 +76,14 @@ public class ErrorProperties {
this.includeStacktrace = includeStacktrace;
}
public IncludeDetails getIncludeDetails() {
return this.includeDetails;
}
public void setIncludeDetails(IncludeDetails includeDetails) {
this.includeDetails = includeDetails;
}
public Whitelabel getWhitelabel() {
return this.whitelabel;
}
@ -96,6 +110,28 @@ public class ErrorProperties {
}
/**
* Include error details attributes options.
*/
public enum IncludeDetails {
/**
* Never add error detail information.
*/
NEVER,
/**
* Always add error detail information.
*/
ALWAYS,
/**
* Add error details information when the "details" request parameter is "true".
*/
ON_DETAILS_PARAM
}
public static class Whitelabel {
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -54,6 +54,7 @@ import org.springframework.web.util.HtmlUtils;
* Abstract base class for {@link ErrorWebExceptionHandler} implementations.
*
* @author Brian Clozel
* @author Scott Frederick
* @since 2.0.0
* @see ErrorAttributes
*/
@ -131,10 +132,26 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
* views or JSON payloads.
* @param request the source request
* @param includeStackTrace whether to include the error stacktrace information
* @return the error attributes as a Map.
* @return the error attributes as a Map
* @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)}
*/
@Deprecated
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return this.errorAttributes.getErrorAttributes(request, includeStackTrace);
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, false);
}
/**
* Extract the error attributes from the current request, to be used to populate error
* views or JSON payloads.
* @param request the source request
* @param includeStackTrace whether to include the error stacktrace information
* @param includeDetails whether to include message and errors attributes
* @return the error attributes as a Map
*/
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
boolean includeDetails) {
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeDetails);
}
/**
@ -152,7 +169,21 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
* @return {@code true} if the error trace has been requested, {@code false} otherwise
*/
protected boolean isTraceEnabled(ServerRequest request) {
String parameter = request.queryParam("trace").orElse("false");
return getBooleanParameter(request, "trace");
}
/**
* Check whether the details attribute has been set on the given request.
* @param request the source request
* @return {@code true} if the error details have been requested, {@code false}
* otherwise
*/
protected boolean isDetailsEnabled(ServerRequest request) {
return getBooleanParameter(request, "details");
}
private boolean getBooleanParameter(ServerRequest request, String parameterName) {
String parameter = request.queryParam(parameterName).orElse("false");
return !"false".equalsIgnoreCase(parameter);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -71,6 +71,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
* payload.
*
* @author Brian Clozel
* @author Scott Frederick
* @since 2.0.0
*/
public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
@ -113,7 +114,8 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
*/
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
boolean includeDetails = isIncludeDetails(request, MediaType.TEXT_HTML);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
int errorStatus = getHttpStatus(error);
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
return Flux.just(getData(errorStatus).toArray(new String[] {}))
@ -141,7 +143,8 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
*/
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
boolean includeDetails = isIncludeDetails(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(error));
}
@ -163,6 +166,23 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
return false;
}
/**
* Determine if the message and errors attributes should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the message and errors attributes should be included
*/
protected boolean isIncludeDetails(ServerRequest request, MediaType produces) {
ErrorProperties.IncludeDetails include = this.errorProperties.getIncludeDetails();
if (include == ErrorProperties.IncludeDetails.ALWAYS) {
return true;
}
if (include == ErrorProperties.IncludeDetails.ON_DETAILS_PARAM) {
return isDetailsEnabled(request);
}
return false;
}
/**
* Get the HTTP error status information from the error map.
* @param errorAttributes the current error information

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -38,6 +38,7 @@ import org.springframework.web.servlet.ModelAndView;
*
* @author Dave Syer
* @author Phillip Webb
* @author Scott Frederick
* @since 1.3.0
* @see ErrorAttributes
*/
@ -66,13 +67,35 @@ public abstract class AbstractErrorController implements ErrorController {
return sorted;
}
/**
* Returns a {@link Map} of the error attributes.
* @param request the source request
* @param includeStackTrace if stack trace elements should be included
* @return the error attributes
* @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(HttpServletRequest, boolean, boolean)}
*/
@Deprecated
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
return this.getErrorAttributes(request, includeStackTrace, false);
}
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace,
boolean includeDetails) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeDetails);
}
protected boolean getTraceParameter(HttpServletRequest request) {
String parameter = request.getParameter("trace");
return getBooleanParameter(request, "trace");
}
protected boolean getDetailsParameter(HttpServletRequest request) {
return getBooleanParameter(request, "details");
}
protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) {
String parameter = request.getParameter(parameterName);
if (parameter == null) {
return false;
}

View File

@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeDetails;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
@ -47,6 +48,7 @@ import org.springframework.web.servlet.ModelAndView;
* @author Phillip Webb
* @author Michael Stummvoll
* @author Stephane Nicoll
* @author Scott Frederick
* @since 1.0.0
* @see ErrorAttributes
* @see ErrorProperties
@ -87,8 +89,8 @@ public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.TEXT_HTML), isIncludeDetails(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
@ -100,7 +102,8 @@ public class BasicErrorController extends AbstractErrorController {
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL),
isIncludeDetails(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
@ -127,6 +130,23 @@ public class BasicErrorController extends AbstractErrorController {
return false;
}
/**
* Determine if the error details attributes should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the error details attributes should be included
*/
protected boolean isIncludeDetails(HttpServletRequest request, MediaType produces) {
IncludeDetails include = getErrorProperties().getIncludeDetails();
if (include == IncludeDetails.ALWAYS) {
return true;
}
if (include == IncludeDetails.ON_DETAILS_PARAM) {
return getDetailsParameter(request);
}
return false;
}
/**
* Provide access to the error properties.
* @return the error properties

View File

@ -106,6 +106,10 @@
"description": "Minimum \"Content-Length\" value that is required for compression to be performed.",
"defaultValue": "2KB"
},
{
"name": "server.error.include-details",
"defaultValue": "never"
},
{
"name": "server.error.include-stacktrace",
"defaultValue": "never"

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -55,6 +55,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* Integration tests for {@link DefaultErrorWebExceptionHandler}
*
* @author Brian Clozel
* @author Scott Frederick
*/
@ExtendWith(OutputCaptureExtension.class)
class DefaultErrorWebExceptionHandlerIntegrationTests {
@ -78,8 +79,8 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
client.get().uri("/").exchange().expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody()
.jsonPath("status").isEqualTo("500").jsonPath("error")
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("path").isEqualTo(("/"))
.jsonPath("message").isEqualTo("Expected!").jsonPath("exception").doesNotExist().jsonPath("trace")
.doesNotExist().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
.jsonPath("message").isEmpty().jsonPath("exception").doesNotExist().jsonPath("trace").doesNotExist()
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
assertThat(output).contains("500 Server Error for HTTP GET \"/\"")
.contains("java.lang.IllegalStateException: Expected!");
});
@ -98,7 +99,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
@Test
void htmlError() {
this.contextRunner.run((context) -> {
this.contextRunner.withPropertyValues("server.error.include-details=always").run((context) -> {
WebTestClient client = getWebClient(context);
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8)
@ -114,6 +115,18 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
client.post().uri("/bind").contentType(MediaType.APPLICATION_JSON).bodyValue("{}").exchange().expectStatus()
.isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error")
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind"))
.jsonPath("exception").doesNotExist().jsonPath("errors").doesNotExist().jsonPath("message")
.isNotEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
});
}
@Test
void bindingResultErrorIncludeDetails() {
this.contextRunner.withPropertyValues("server.error.include-details=on-details-param").run((context) -> {
WebTestClient client = getWebClient(context);
client.post().uri("/bind?details=true").contentType(MediaType.APPLICATION_JSON).bodyValue("{}").exchange()
.expectStatus().isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error")
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind"))
.jsonPath("exception").doesNotExist().jsonPath("errors").isArray().jsonPath("message").isNotEmpty()
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
});
@ -163,6 +176,50 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
});
}
@Test
void includeDetailsOnParam() {
this.contextRunner.withPropertyValues("server.error.include-exception=true",
"server.error.include-details=on-details-param").run((context) -> {
WebTestClient client = getWebClient(context);
client.get().uri("/?details=true").exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
.isEqualTo("500").jsonPath("error")
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception")
.isEqualTo(IllegalStateException.class.getName()).jsonPath("message").isNotEmpty()
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
});
}
@Test
void alwaysIncludeDetails() {
this.contextRunner
.withPropertyValues("server.error.include-exception=true", "server.error.include-details=always")
.run((context) -> {
WebTestClient client = getWebClient(context);
client.get().uri("/?trace=false").exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
.isEqualTo("500").jsonPath("error")
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception")
.isEqualTo(IllegalStateException.class.getName()).jsonPath("message").isNotEmpty()
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
});
}
@Test
void neverIncludeDetails() {
this.contextRunner
.withPropertyValues("server.error.include-exception=true", "server.error.include-details=never")
.run((context) -> {
WebTestClient client = getWebClient(context);
client.get().uri("/?trace=true").exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
.isEqualTo("500").jsonPath("error")
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception")
.isEqualTo(IllegalStateException.class.getName()).jsonPath("message").isEmpty()
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
});
}
@Test
void statusException() {
this.contextRunner.withPropertyValues("server.error.include-exception=true").run((context) -> {
@ -176,8 +233,10 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
@Test
void defaultErrorView() {
this.contextRunner.withPropertyValues("spring.mustache.prefix=classpath:/unknown/",
"server.error.include-stacktrace=always").run((context) -> {
this.contextRunner
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/",
"server.error.include-stacktrace=always", "server.error.include-details=always")
.run((context) -> {
WebTestClient client = getWebClient(context);
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8)
@ -190,7 +249,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
@Test
void escapeHtmlInDefaultErrorView() {
this.contextRunner.withPropertyValues("spring.mustache.prefix=classpath:/unknown/").run((context) -> {
this.contextRunner
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/", "server.error.include-details=always")
.run((context) -> {
WebTestClient client = getWebClient(context);
String body = client.get().uri("/html").accept(MediaType.TEXT_HTML).exchange().expectStatus()
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8)

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -47,6 +47,7 @@ import static org.mockito.Mockito.mock;
*
* @author Phillip Webb
* @author Madhura Bhave
* @author Scott Frederick
*/
class DefaultErrorWebExceptionHandlerTests {
@ -65,7 +66,7 @@ class DefaultErrorWebExceptionHandlerTests {
ResourceProperties resourceProperties = new ResourceProperties();
ErrorProperties errorProperties = new ErrorProperties();
ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext();
given(errorAttributes.getErrorAttributes(any(), anyBoolean())).willReturn(getErrorAttributes());
given(errorAttributes.getErrorAttributes(any(), anyBoolean(), anyBoolean())).willReturn(getErrorAttributes());
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
resourceProperties, errorProperties, context);
setupViewResolver(exceptionHandler);

View File

@ -70,6 +70,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Phillip Webb
* @author Dave Syer
* @author Stephane Nicoll
* @author Scott Frederick
*/
class BasicErrorControllerIntegrationTests {
@ -84,59 +85,87 @@ class BasicErrorControllerIntegrationTests {
@Test
@SuppressWarnings("rawtypes")
void testErrorForMachineClient() {
void testErrorForMachineClientDefault() {
load();
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "Expected!", "/");
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null,
"An error occurred while processing the request", "/");
assertThat(entity.getBody().containsKey("exception")).isFalse();
assertThat(entity.getBody().containsKey("trace")).isFalse();
}
@Test
void testErrorForMachineClientTraceParamTrue() {
errorForMachineClientOnTraceParam("?trace=true", true);
void testErrorForMachineClientWithParamsTrue() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
"--server.error.include-details=on-details-param");
exceptionWithStackTraceAndDetails("?trace=true&details=true");
}
@Test
void testErrorForMachineClientTraceParamFalse() {
errorForMachineClientOnTraceParam("?trace=false", false);
void testErrorForMachineClientWithParamsFalse() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
"--server.error.include-details=on-details-param");
exceptionWithoutStackTraceAndDetails("?trace=false&details=false");
}
@Test
void testErrorForMachineClientTraceParamAbsent() {
errorForMachineClientOnTraceParam("", false);
void testErrorForMachineClientWithParamsAbsent() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
"--server.error.include-details=on-details-param");
exceptionWithoutStackTraceAndDetails("");
}
@Test
void testErrorForMachineClientNeverParams() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=never",
"--server.error.include-details=never");
exceptionWithoutStackTraceAndDetails("?trace=true&details=true");
}
@Test
void testErrorForMachineClientAlwaysParams() {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=always",
"--server.error.include-details=always");
exceptionWithStackTraceAndDetails("?trace=false&details=false");
}
@Test
void testErrorForMachineClientAlwaysParamsWithoutMessage() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/noMessage"), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
"No message available", "/noMessage");
}
@SuppressWarnings("rawtypes")
private void errorForMachineClientOnTraceParam(String path, boolean expectedTrace) {
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param");
private void exceptionWithStackTraceAndDetails(String path) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
"Expected!", "/");
assertThat(entity.getBody().containsKey("trace")).isEqualTo(expectedTrace);
assertThat(entity.getBody().containsKey("trace")).isTrue();
}
@Test
@SuppressWarnings("rawtypes")
void testErrorForMachineClientNoStacktrace() {
load("--server.error.include-stacktrace=never");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "Expected!", "/");
private void exceptionWithoutStackTraceAndDetails(String path) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
"An error occurred while processing the request", "/");
assertThat(entity.getBody().containsKey("trace")).isFalse();
}
@Test
@SuppressWarnings("rawtypes")
void testErrorForMachineClientAlwaysStacktrace() {
load("--server.error.include-stacktrace=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=false"), Map.class);
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "Expected!", "/");
assertThat(entity.getBody().containsKey("trace")).isTrue();
void testErrorForAnnotatedExceptionWithoutDetails() {
load("--server.error.include-exception=true");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
"An error occurred while processing the request", "/annotated");
}
@Test
@SuppressWarnings("rawtypes")
void testErrorForAnnotatedException() {
load("--server.error.include-exception=true");
void testErrorForAnnotatedExceptionWithDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
"Expected!", "/annotated");
@ -144,25 +173,77 @@ class BasicErrorControllerIntegrationTests {
@Test
@SuppressWarnings("rawtypes")
void testErrorForAnnotatedNoReasonException() {
void testErrorForAnnotatedNoReasonExceptionWithoutDetails() {
load("--server.error.include-exception=true");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class,
"An error occurred while processing the request", "/annotatedNoReason");
}
@Test
@SuppressWarnings("rawtypes")
void testErrorForAnnotatedNoReasonExceptionWithDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class, "Expected message", "/annotatedNoReason");
}
@Test
@SuppressWarnings("rawtypes")
void testBindingExceptionForMachineClient() {
load("--server.error.include-exception=true");
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind"))).accept(MediaType.APPLICATION_JSON)
.build();
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
String resp = entity.getBody().toString();
assertThat(resp).contains("Error count: 1");
assertThat(resp).contains("errors=[{");
assertThat(resp).contains("codes=[");
assertThat(resp).contains("org.springframework.validation.BindException");
void testErrorForAnnotatedNoMessageExceptionWithDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoMessage"), Map.class);
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
TestConfiguration.Errors.NoReasonExpectedException.class, "No message available",
"/annotatedNoMessage");
}
@Test
void testBindingExceptionForMachineClientWithDetailsParamTrue() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
bindingExceptionWithDetails("?details=true");
}
@Test
void testBindingExceptionForMachineClientWithDetailsParamFalse() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
bindingExceptionWithoutDetails("?details=false");
}
@Test
void testBindingExceptionForMachineClientWithDetailsParamAbsent() {
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
bindingExceptionWithoutDetails("");
}
@Test
void testBindingExceptionForMachineClientAlwaysDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=always");
bindingExceptionWithDetails("?details=false");
}
@Test
void testBindingExceptionForMachineClientNeverDetails() {
load("--server.error.include-exception=true", "--server.error.include-details=never");
bindingExceptionWithoutDetails("?details=true");
}
@SuppressWarnings({ "rawtypes" })
private void bindingExceptionWithDetails(String param) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class,
"Validation failed for object='test'. Error count: 1", "/bind");
assertThat(entity.getBody().containsKey("errors")).isTrue();
}
@SuppressWarnings({ "rawtypes" })
private void bindingExceptionWithoutDetails(String param) {
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "Validation failed",
"/bind");
assertThat(entity.getBody().containsKey("errors")).isFalse();
}
@Test
@ -172,22 +253,21 @@ class BasicErrorControllerIntegrationTests {
RequestEntity request = RequestEntity.post(URI.create(createUrl("/bodyValidation")))
.accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).body("{}");
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
String resp = entity.getBody().toString();
assertThat(resp).contains("Error count: 1");
assertThat(resp).contains("errors=[{");
assertThat(resp).contains("codes=[");
assertThat(resp).contains(MethodArgumentNotValidException.class.getName());
assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class,
"Validation failed", "/bodyValidation");
assertThat(entity.getBody().containsKey("errors")).isFalse();
}
@Test
@SuppressWarnings("rawtypes")
void testNoExceptionByDefaultForMachineClient() {
void testBindingExceptionForMachineClientDefault() {
load();
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind"))).accept(MediaType.APPLICATION_JSON)
.build();
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind?trace=true,details=true")))
.accept(MediaType.APPLICATION_JSON).build();
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
String resp = entity.getBody().toString();
assertThat(resp).doesNotContain("org.springframework.validation.BindException");
assertThat(entity.getBody().containsKey("exception")).isFalse();
assertThat(entity.getBody().containsKey("trace")).isFalse();
assertThat(entity.getBody().containsKey("errors")).isFalse();
}
@Test
@ -213,7 +293,7 @@ class BasicErrorControllerIntegrationTests {
private void assertErrorAttributes(Map<?, ?> content, String status, String error, Class<?> exception,
String message, String path) {
assertThat(content.get("status")).as("Wrong status").isEqualTo(status);
assertThat(content.get("status").toString()).as("Wrong status").isEqualTo(status);
assertThat(content.get("error")).as("Wrong error").isEqualTo(error);
if (exception != null) {
assertThat(content.get("exception")).as("Wrong exception").isEqualTo(exception.getName());
@ -282,6 +362,11 @@ class BasicErrorControllerIntegrationTests {
throw new IllegalStateException("Expected!");
}
@RequestMapping("/noMessage")
String noMessage() {
throw new IllegalStateException();
}
@RequestMapping("/annotated")
String annotated() {
throw new ExpectedException();
@ -292,6 +377,11 @@ class BasicErrorControllerIntegrationTests {
throw new NoReasonExpectedException("Expected message");
}
@RequestMapping("/annotatedNoMessage")
String annotatedNoMessage() {
throw new NoReasonExpectedException("");
}
@RequestMapping("/bind")
String bind() throws Exception {
BindException error = new BindException(this, "test");

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -67,8 +67,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* {@link SpringBootTest @SpringBootTest}.
*
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest
@SpringBootTest(properties = { "server.error.include-details=always" })
@DirtiesContext
class BasicErrorControllerMockMvcTests {
@ -117,7 +118,7 @@ class BasicErrorControllerMockMvcTests {
// And the rendered status code is always wrong (but would be 400 in a real
// system)
String content = response.getResponse().getContentAsString();
assertThat(content).contains("Error count: 1");
assertThat(content).contains("Validation failed");
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -50,8 +50,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* Integration tests for the default error view.
*
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest
@SpringBootTest(properties = { "server.error.include-details=always" })
@DirtiesContext
class DefaultErrorViewIntegrationTests {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -45,13 +45,13 @@ class ErrorMvcAutoConfigurationTests {
AutoConfigurations.of(DispatcherServletAutoConfiguration.class, ErrorMvcAutoConfiguration.class));
@Test
void renderContainsViewWithExceptionDetails() throws Exception {
void renderContainsViewWithExceptionDetails() {
this.contextRunner.run((context) -> {
View errorView = context.getBean("error", View.class);
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
false);
errorView.render(errorAttributes.getErrorAttributes(webRequest, true), webRequest.getRequest(),
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(),
webRequest.getResponse());
assertThat(webRequest.getResponse().getContentType()).isEqualTo("text/html;charset=UTF-8");
String responseString = ((MockHttpServletResponse) webRequest.getResponse()).getContentAsString();
@ -69,7 +69,7 @@ class ErrorMvcAutoConfigurationTests {
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
true);
errorView.render(errorAttributes.getErrorAttributes(webRequest, true), webRequest.getRequest(),
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(),
webRequest.getResponse());
assertThat(output).contains("Cannot render error page for request [/path] "
+ "and exception [Exception message] as the response has "

View File

@ -71,6 +71,7 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
properties.put("spring.resources.chain.cache", "false");
properties.put("spring.template.provider.cache", "false");
properties.put("spring.mvc.log-resolved-exception", "true");
properties.put("server.error.include-details", "ALWAYS");
properties.put("server.error.include-stacktrace", "ALWAYS");
properties.put("server.servlet.jsp.init-parameters.development", "true");
properties.put("spring.reactor.debug", "true");

View File

@ -106,8 +106,10 @@ class DevToolPropertiesIntegrationTests {
application.setWebApplicationType(WebApplicationType.NONE);
this.context = getContext(application::run);
ConfigurableEnvironment environment = this.context.getEnvironment();
String property = environment.getProperty("server.error.include-stacktrace");
assertThat(property).isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString());
String includeStackTrace = environment.getProperty("server.error.include-stacktrace");
assertThat(includeStackTrace).isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString());
String includeDetails = environment.getProperty("server.error.include-details");
assertThat(includeDetails).isEqualTo(ErrorProperties.IncludeDetails.ALWAYS.toString());
}
protected ConfigurableApplicationContext getContext(Supplier<ConfigurableApplicationContext> supplier)

View File

@ -53,6 +53,7 @@ import org.springframework.web.server.ServerWebExchange;
* @author Brian Clozel
* @author Stephane Nicoll
* @author Michele Mancioppi
* @author Scott Frederick
* @since 2.0.0
* @see ErrorAttributes
*/
@ -79,7 +80,14 @@ public class DefaultErrorAttributes implements ErrorAttributes {
}
@Override
@Deprecated
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return this.getErrorAttributes(request, includeStackTrace, false);
}
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
boolean includeDetails) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path());
@ -89,9 +97,9 @@ public class DefaultErrorAttributes implements ErrorAttributes {
HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation);
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", determineMessage(error, responseStatusAnnotation));
errorAttributes.put("message", determineMessage(error, responseStatusAnnotation, includeDetails));
errorAttributes.put("requestId", request.exchange().getRequest().getId());
handleException(errorAttributes, determineException(error), includeStackTrace);
handleException(errorAttributes, determineException(error), includeStackTrace, includeDetails);
return errorAttributes;
}
@ -102,9 +110,13 @@ public class DefaultErrorAttributes implements ErrorAttributes {
return responseStatusAnnotation.getValue("code", HttpStatus.class).orElse(HttpStatus.INTERNAL_SERVER_ERROR);
}
private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation,
boolean includeDetails) {
if (error instanceof WebExchangeBindException) {
return error.getMessage();
return includeDetails ? error.getMessage() : "Validation failed";
}
if (!includeDetails) {
return "";
}
if (error instanceof ResponseStatusException) {
return ((ResponseStatusException) error).getReason();
@ -130,14 +142,15 @@ public class DefaultErrorAttributes implements ErrorAttributes {
errorAttributes.put("trace", stackTrace.toString());
}
private void handleException(Map<String, Object> errorAttributes, Throwable error, boolean includeStackTrace) {
private void handleException(Map<String, Object> errorAttributes, Throwable error, boolean includeStackTrace,
boolean includeDetails) {
if (this.includeException) {
errorAttributes.put("exception", error.getClass().getName());
}
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
if (error instanceof BindingResult) {
if (includeDetails && (error instanceof BindingResult)) {
BindingResult result = (BindingResult) error;
if (result.hasErrors()) {
errorAttributes.put("errors", result.getAllErrors());

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import org.springframework.web.server.ServerWebExchange;
* Provides access to error attributes which can be logged or presented to the user.
*
* @author Brian Clozel
* @author Scott Frederick
* @since 2.0.0
* @see DefaultErrorAttributes
*/
@ -37,9 +38,21 @@ public interface ErrorAttributes {
* @param request the source request
* @param includeStackTrace if stack trace elements should be included
* @return a map of error attributes
* @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)}
*/
Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace);
/**
* Return a {@link Map} of the error attributes. The map can be used as the model of
* an error page, or returned as a {@link ServerResponse} body.
* @param request the source request
* @param includeStackTrace if stack trace elements should be included
* @param includeDetails if message and errors elements should be included
* @return a map of error attributes
*/
Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace, boolean includeDetails);
/**
* Return the underlying cause of the error or {@code null} if the error cannot be
* extracted.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -46,9 +46,10 @@ import org.springframework.web.servlet.ModelAndView;
* <li>status - The status code</li>
* <li>error - The error reason</li>
* <li>exception - The class name of the root exception (if configured)</li>
* <li>message - The exception message</li>
* <li>errors - Any {@link ObjectError}s from a {@link BindingResult} exception
* <li>trace - The exception stack trace</li>
* <li>message - The exception message (if configured)</li>
* <li>errors - Any {@link ObjectError}s from a {@link BindingResult} exception (if
* configured)</li>
* <li>trace - The exception stack trace (if configured)</li>
* <li>path - The URL path when the exception was raised</li>
* </ul>
*
@ -56,6 +57,7 @@ import org.springframework.web.servlet.ModelAndView;
* @author Dave Syer
* @author Stephane Nicoll
* @author Vedran Pavic
* @author Scott Frederick
* @since 2.0.0
* @see ErrorAttributes
*/
@ -99,11 +101,18 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
}
@Override
@Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
return this.getErrorAttributes(webRequest, includeStackTrace, false);
}
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace,
boolean includeDetails) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addErrorDetails(errorAttributes, webRequest, includeStackTrace, includeDetails);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
@ -125,8 +134,8 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
}
}
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
boolean includeStackTrace) {
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace,
boolean includeDetails) {
Throwable error = getError(webRequest);
if (error != null) {
while (error instanceof ServletException && error.getCause() != null) {
@ -135,32 +144,51 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
if (this.includeException) {
errorAttributes.put("exception", error.getClass().getName());
}
addErrorMessage(errorAttributes, error);
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
}
Object message = getAttribute(webRequest, "javax.servlet.error.message");
if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
&& !(error instanceof BindingResult)) {
errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);
addErrorMessage(errorAttributes, webRequest, error, includeDetails);
}
private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error,
boolean includeDetails) {
BindingResult result = extractBindingResult(error);
if (result == null) {
addExceptionErrorMessage(errorAttributes, webRequest, error, includeDetails);
}
else {
addBindingResultErrorMessage(errorAttributes, result, includeDetails);
}
}
private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
BindingResult result = extractBindingResult(error);
if (result == null) {
errorAttributes.put("message", error.getMessage());
private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error,
boolean includeDetails) {
if (!includeDetails) {
errorAttributes.put("message", "An error occurred while processing the request");
return;
}
Object message = getAttribute(webRequest, "javax.servlet.error.message");
if (StringUtils.isEmpty(message) && error != null) {
message = error.getMessage();
}
if (StringUtils.isEmpty(message)) {
message = "No message available";
}
errorAttributes.put("message", message);
}
private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result,
boolean includeDetails) {
if (!includeDetails) {
errorAttributes.put("message", "Validation failed");
return;
}
if (result.hasErrors()) {
errorAttributes.put("errors", result.getAllErrors());
errorAttributes.put("message", "Validation failed for object='" + result.getObjectName()
+ "'. Error count: " + result.getErrorCount());
}
else {
errorAttributes.put("message", "No errors");
}
errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. "
+ "Error count: " + result.getErrorCount());
}
private BindingResult extractBindingResult(Throwable error) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import org.springframework.web.servlet.ModelAndView;
* Provides access to error attributes which can be logged or presented to the user.
*
* @author Phillip Webb
* @author Scott Frederick
* @since 2.0.0
* @see DefaultErrorAttributes
*/
@ -38,9 +39,23 @@ public interface ErrorAttributes {
* @param webRequest the source request
* @param includeStackTrace if stack trace elements should be included
* @return a map of error attributes
* @deprecated since 2.3.0 in favor of
* {@link #getErrorAttributes(WebRequest, boolean, boolean)}
*/
@Deprecated
Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace);
/**
* Returns a {@link Map} of the error attributes. The map can be used as the model of
* an error page {@link ModelAndView}, or returned as a
* {@link ResponseBody @ResponseBody}.
* @param webRequest the source request
* @param includeStackTrace if stack trace elements should be included
* @param includeDetails if message and errors elements should be included
* @return a map of error attributes
*/
Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace, boolean includeDetails);
/**
* Return the underlying cause of the error or {@code null} if the error cannot be
* extracted.

View File

@ -47,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*
* @author Brian Clozel
* @author Stephane Nicoll
* @author Scott Frederick
*/
class DefaultErrorAttributesTests {
@ -60,7 +61,8 @@ class DefaultErrorAttributesTests {
void missingExceptionAttribute() {
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test").build());
ServerRequest request = ServerRequest.create(exchange, this.readers);
assertThatIllegalStateException().isThrownBy(() -> this.errorAttributes.getErrorAttributes(request, false))
assertThatIllegalStateException()
.isThrownBy(() -> this.errorAttributes.getErrorAttributes(request, false, false))
.withMessageContaining("Missing exception attribute in ServerWebExchange");
}
@ -68,7 +70,7 @@ class DefaultErrorAttributesTests {
void includeTimeStamp() {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND),
false);
false, false);
assertThat(attributes.get("timestamp")).isInstanceOf(Date.class);
}
@ -77,7 +79,7 @@ class DefaultErrorAttributesTests {
Error error = new OutOfMemoryError("Test error");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
false);
false, false);
assertThat(attributes.get("error")).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
assertThat(attributes.get("status")).isEqualTo(500);
}
@ -87,7 +89,7 @@ class DefaultErrorAttributesTests {
Exception error = new CustomException();
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
false);
false, false);
assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
assertThat(attributes.get("message")).isEqualTo("");
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
@ -98,7 +100,7 @@ class DefaultErrorAttributesTests {
Exception error = new CustomException("Test Message");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
false);
false, true);
assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
assertThat(attributes.get("message")).isEqualTo("Test Message");
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
@ -109,7 +111,7 @@ class DefaultErrorAttributesTests {
Exception error = new Custom2Exception();
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
false);
false, true);
assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
assertThat(attributes.get("message")).isEqualTo("Nope!");
@ -119,7 +121,7 @@ class DefaultErrorAttributesTests {
void includeStatusCode() {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND),
false);
false, false);
assertThat(attributes.get("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase());
assertThat(attributes.get("status")).isEqualTo(404);
}
@ -129,19 +131,29 @@ class DefaultErrorAttributesTests {
Error error = new OutOfMemoryError("Test error");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true);
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
assertThat(attributes.get("exception")).isNull();
assertThat(attributes.get("message")).isEqualTo("Test error");
}
@Test
void excludeDetails() {
Error error = new OutOfMemoryError("Test error");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false);
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
assertThat(attributes.get("message")).isEqualTo("");
}
@Test
void includeException() {
RuntimeException error = new RuntimeException("Test");
this.errorAttributes = new DefaultErrorAttributes(true);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true);
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
assertThat(attributes.get("message")).isEqualTo("Test");
@ -154,7 +166,7 @@ class DefaultErrorAttributesTests {
this.errorAttributes = new DefaultErrorAttributes(true);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true);
assertThat(attributes.get("status")).isEqualTo(400);
assertThat(attributes.get("message")).isEqualTo("invalid request");
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
@ -168,7 +180,7 @@ class DefaultErrorAttributesTests {
this.errorAttributes = new DefaultErrorAttributes(true);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true);
assertThat(attributes.get("status")).isEqualTo(406);
assertThat(attributes.get("message")).isEqualTo("could not process request");
assertThat(attributes.get("exception")).isEqualTo(ResponseStatusException.class.getName());
@ -179,7 +191,7 @@ class DefaultErrorAttributesTests {
void notIncludeTrace() {
RuntimeException ex = new RuntimeException("Test");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex),
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
false);
assertThat(attributes.get("trace")).isNull();
}
@ -188,7 +200,8 @@ class DefaultErrorAttributesTests {
void includeTrace() {
RuntimeException ex = new RuntimeException("Test");
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), true);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), true,
false);
assertThat(attributes.get("trace").toString()).startsWith("java.lang");
}
@ -196,7 +209,7 @@ class DefaultErrorAttributesTests {
void includePath() {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND),
false);
false, false);
assertThat(attributes.get("path")).isEqualTo("/test");
}
@ -204,7 +217,7 @@ class DefaultErrorAttributesTests {
void includeLogPrefix() {
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, NOT_FOUND);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false);
assertThat(attributes.get("requestId")).isEqualTo(serverRequest.exchange().getRequest().getId());
}
@ -216,8 +229,8 @@ class DefaultErrorAttributesTests {
bindingResult.addError(new ObjectError("c", "d"));
Exception ex = new WebExchangeBindException(stringParam, bindingResult);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex),
false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
true);
assertThat(attributes.get("message")).asString()
.startsWith("Validation failed for argument at index 0 in method: "
+ "int org.springframework.boot.web.reactive.error.DefaultErrorAttributesTests"
@ -225,6 +238,20 @@ class DefaultErrorAttributesTests {
assertThat(attributes.get("errors")).isEqualTo(bindingResult.getAllErrors());
}
@Test
void extractBindingResultErrorsExcludeDetails() throws Exception {
Method method = getClass().getDeclaredMethod("method", String.class);
MethodParameter stringParam = new MethodParameter(method, 0);
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
bindingResult.addError(new ObjectError("c", "d"));
Exception ex = new WebExchangeBindException(stringParam, bindingResult);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
false);
assertThat(attributes.get("message")).isEqualTo("Validation failed");
assertThat(attributes.containsKey("errors")).isFalse();
}
private ServerRequest buildServerRequest(MockServerHttpRequest request, Throwable error) {
ServerWebExchange exchange = MockServerWebExchange.from(request);
this.errorAttributes.storeErrorInformation(error, exchange);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -42,32 +42,33 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Phillip Webb
* @author Vedran Pavic
* @author Scott Frederick
*/
class DefaultErrorAttributesTests {
private DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes();
private final DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes();
private MockHttpServletRequest request = new MockHttpServletRequest();
private final MockHttpServletRequest request = new MockHttpServletRequest();
private WebRequest webRequest = new ServletWebRequest(this.request);
private final WebRequest webRequest = new ServletWebRequest(this.request);
@Test
void includeTimeStamp() {
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
assertThat(attributes.get("timestamp")).isInstanceOf(Date.class);
}
@Test
void specificStatusCode() {
this.request.setAttribute("javax.servlet.error.status_code", 404);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
assertThat(attributes.get("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase());
assertThat(attributes.get("status")).isEqualTo(404);
}
@Test
void missingStatusCode() {
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
assertThat(attributes.get("error")).isEqualTo("None");
assertThat(attributes.get("status")).isEqualTo(999);
}
@ -77,48 +78,74 @@ class DefaultErrorAttributesTests {
RuntimeException ex = new RuntimeException("Test");
ModelAndView modelAndView = this.errorAttributes.resolveException(this.request, null, null, ex);
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException("Ignored"));
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
assertThat(modelAndView).isNull();
assertThat(attributes.get("exception")).isNull();
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test");
}
@Test
void servletError() {
void servletErrorWithDetail() {
RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
assertThat(attributes.get("exception")).isNull();
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test");
}
@Test
void servletMessage() {
void servletErrorWithoutDetail() {
RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message").toString()).contains("An error occurred");
}
@Test
void servletMessageWithDetail() {
this.request.setAttribute("javax.servlet.error.message", "Test");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
assertThat(attributes.get("exception")).isNull();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test");
}
@Test
void nullMessage() {
void servletMessageWithoutDetail() {
this.request.setAttribute("javax.servlet.error.message", "Test");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).asString().contains("An error occurred");
}
@Test
void nullExceptionMessage() {
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException());
this.request.setAttribute("javax.servlet.error.message", "Test");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
assertThat(attributes.get("exception")).isNull();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test");
}
@Test
void nullExceptionMessageAndServletMessage() {
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException());
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("No message available");
}
@Test
void unwrapServletException() {
RuntimeException ex = new RuntimeException("Test");
ServletException wrapped = new ServletException(new ServletException(ex));
this.request.setAttribute("javax.servlet.error.exception", wrapped);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(wrapped);
assertThat(attributes.get("exception")).isNull();
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test");
}
@ -126,65 +153,81 @@ class DefaultErrorAttributesTests {
void getError() {
Error error = new OutOfMemoryError("Test error");
this.request.setAttribute("javax.servlet.error.exception", error);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(error);
assertThat(attributes.get("exception")).isNull();
assertThat(attributes.containsKey("exception")).isFalse();
assertThat(attributes.get("message")).isEqualTo("Test error");
}
@Test
void extractBindingResultErrors() {
void withBindingErrors() {
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
bindingResult.addError(new ObjectError("c", "d"));
Exception ex = new BindException(bindingResult);
testBindingResult(bindingResult, ex);
testBindingResult(bindingResult, ex, true);
}
@Test
void extractMethodArgumentNotValidExceptionBindingResultErrors() {
void withoutBindingErrors() {
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
bindingResult.addError(new ObjectError("c", "d"));
Exception ex = new BindException(bindingResult);
testBindingResult(bindingResult, ex, false);
}
@Test
void withMethodArgumentNotValidExceptionBindingErrors() {
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
bindingResult.addError(new ObjectError("c", "d"));
Exception ex = new MethodArgumentNotValidException(null, bindingResult);
testBindingResult(bindingResult, ex);
testBindingResult(bindingResult, ex, true);
}
private void testBindingResult(BindingResult bindingResult, Exception ex) {
private void testBindingResult(BindingResult bindingResult, Exception ex, boolean includeDetails) {
this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
assertThat(attributes.get("message")).isEqualTo("Validation failed for object='objectName'. Error count: 1");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false,
includeDetails);
if (includeDetails) {
assertThat(attributes.get("message"))
.isEqualTo("Validation failed for object='objectName'. Error count: 1");
assertThat(attributes.get("errors")).isEqualTo(bindingResult.getAllErrors());
}
else {
assertThat(attributes.get("message")).isEqualTo("Validation failed");
assertThat(attributes.containsKey("errors")).isFalse();
}
}
@Test
void withExceptionAttribute() {
DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes(true);
RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = errorAttributes.getErrorAttributes(this.webRequest, false, true);
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
assertThat(attributes.get("message")).isEqualTo("Test");
}
@Test
void trace() {
void withStackTraceAttribute() {
RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, true);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, true, false);
assertThat(attributes.get("trace").toString()).startsWith("java.lang");
}
@Test
void noTrace() {
void withoutStackTraceAttribute() {
RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
assertThat(attributes.get("trace")).isNull();
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
assertThat(attributes.containsKey("trace")).isFalse();
}
@Test
void path() {
this.request.setAttribute("javax.servlet.error.request_uri", "path");
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
assertThat(attributes.get("path")).isEqualTo("path");
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,8 +34,10 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Madhura Bhave
* @author Stephane Nicoll
* @author Scott Frederick
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "server.error.include-details=always" })
class SampleActuatorCustomSecurityApplicationTests extends AbstractSampleActuatorCustomSecurityTests {
@LocalServerPort

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Dave Syer
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "server.error.include-details=always" })
class SampleActuatorUiApplicationTests {
@Autowired