Skip to content

Commit a30740f

Browse files
Separate server properties for message and errors
Prior to this commit, there was a property server.error.include-details that allowed configuration of the message and errors attributes in a server error response. This commit separates the control of the message and errors attributes into two separate properties named server.error.include-message and server.error.include-binding-errors. When the message attribute is excluded from a servlet response, the value is changed from a hard-coded text value to an empty value. Fixes gh-20505
1 parent 18a9a22 commit a30740f

File tree

26 files changed

+378
-245
lines changed

26 files changed

+378
-245
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,26 +53,38 @@ public ManagementErrorEndpoint(ErrorAttributes errorAttributes, ErrorProperties
5353
@RequestMapping("${server.error.path:${error.path:/error}}")
5454
@ResponseBody
5555
public Map<String, Object> invoke(ServletWebRequest request) {
56-
return this.errorAttributes.getErrorAttributes(request, includeStackTrace(request), includeDetails(request));
56+
return this.errorAttributes.getErrorAttributes(request, includeStackTrace(request), includeMessage(request),
57+
includeBindingErrors(request));
5758
}
5859

5960
private boolean includeStackTrace(ServletWebRequest request) {
6061
switch (this.errorProperties.getIncludeStacktrace()) {
6162
case ALWAYS:
6263
return true;
63-
case ON_TRACE_PARAM:
64+
case ON_PARAM:
6465
return getBooleanParameter(request, "trace");
6566
default:
6667
return false;
6768
}
6869
}
6970

70-
private boolean includeDetails(ServletWebRequest request) {
71-
switch (this.errorProperties.getIncludeDetails()) {
71+
private boolean includeMessage(ServletWebRequest request) {
72+
switch (this.errorProperties.getIncludeMessage()) {
7273
case ALWAYS:
7374
return true;
74-
case ON_DETAILS_PARAM:
75-
return getBooleanParameter(request, "details");
75+
case ON_PARAM:
76+
return getBooleanParameter(request, "message");
77+
default:
78+
return false;
79+
}
80+
}
81+
82+
private boolean includeBindingErrors(ServletWebRequest request) {
83+
switch (this.errorProperties.getIncludeBindingErrors()) {
84+
case ALWAYS:
85+
return true;
86+
case ON_PARAM:
87+
return getBooleanParameter(request, "errors");
7688
default:
7789
return false;
7890
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpointTests.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,16 @@ void setUp() {
5151
void errorResponseNeverDetails() {
5252
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
5353
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest()));
54-
assertThat(response).containsEntry("message", "An error occurred while processing the request");
54+
assertThat(response).containsEntry("message", "");
5555
assertThat(response).doesNotContainKey("trace");
5656
}
5757

5858
@Test
5959
void errorResponseAlwaysDetails() {
6060
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ALWAYS);
61-
this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ALWAYS);
61+
this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ALWAYS);
6262
this.request.addParameter("trace", "false");
63-
this.request.addParameter("details", "false");
63+
this.request.addParameter("message", "false");
6464
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
6565
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request));
6666
assertThat(response).containsEntry("message", "test exception");
@@ -70,20 +70,20 @@ void errorResponseAlwaysDetails() {
7070

7171
@Test
7272
void errorResponseParamsAbsent() {
73-
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM);
74-
this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM);
73+
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM);
74+
this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM);
7575
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
7676
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request));
77-
assertThat(response).containsEntry("message", "An error occurred while processing the request");
77+
assertThat(response).containsEntry("message", "");
7878
assertThat(response).doesNotContainKey("trace");
7979
}
8080

8181
@Test
8282
void errorResponseParamsTrue() {
83-
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM);
84-
this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM);
83+
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM);
84+
this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM);
8585
this.request.addParameter("trace", "true");
86-
this.request.addParameter("details", "true");
86+
this.request.addParameter("message", "true");
8787
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
8888
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request));
8989
assertThat(response).containsEntry("message", "test exception");
@@ -93,13 +93,13 @@ void errorResponseParamsTrue() {
9393

9494
@Test
9595
void errorResponseParamsFalse() {
96-
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM);
97-
this.errorProperties.setIncludeDetails(ErrorProperties.IncludeDetails.ON_DETAILS_PARAM);
96+
this.errorProperties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.ON_PARAM);
97+
this.errorProperties.setIncludeMessage(ErrorProperties.IncludeAttribute.ON_PARAM);
9898
this.request.addParameter("trace", "false");
99-
this.request.addParameter("details", "false");
99+
this.request.addParameter("message", "false");
100100
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(this.errorAttributes, this.errorProperties);
101101
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(this.request));
102-
assertThat(response).containsEntry("message", "An error occurred while processing the request");
102+
assertThat(response).containsEntry("message", "");
103103
assertThat(response).doesNotContainKey("trace");
104104
}
105105

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ void errorPageAndErrorControllerAreUsed() {
6767
ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON).exchange()
6868
.block();
6969
Map<Object, Object> body = response.bodyToMono(Map.class).block();
70-
assertThat(body).containsEntry("message", "An error occurred while processing the request");
70+
assertThat(body).containsEntry("message", "");
7171
assertThat(body).doesNotContainKey("trace");
7272
});
7373
}
7474

7575
@Test
7676
void errorPageAndErrorControllerIncludeDetails() {
77-
this.runner.withPropertyValues("server.error.include-stacktrace=always", "server.error.include-details=always")
77+
this.runner.withPropertyValues("server.error.include-stacktrace=always", "server.error.include-message=always")
7878
.run((context) -> {
7979
String port = context.getEnvironment().getProperty("local.management.port");
8080
WebClient client = WebClient.create("http://localhost:" + port);

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ private void load(Consumer<T> contextCustomizer, String endpointPath,
412412
contextCustomizer.accept(applicationContext);
413413
Map<String, Object> properties = new HashMap<>();
414414
properties.put("endpointPath", endpointPath);
415-
properties.put("server.error.include-details", "always");
415+
properties.put("server.error.include-message", "always");
416416
applicationContext.getEnvironment().getPropertySources().addLast(new MapPropertySource("test", properties));
417417
applicationContext.refresh();
418418
try {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,14 @@ public class ErrorProperties {
4646
private IncludeStacktrace includeStacktrace = IncludeStacktrace.NEVER;
4747

4848
/**
49-
* When to include "message" and "errors" attributes.
49+
* When to include "message" attribute.
5050
*/
51-
private IncludeDetails includeDetails = IncludeDetails.NEVER;
51+
private IncludeAttribute includeMessage = IncludeAttribute.NEVER;
52+
53+
/**
54+
* When to include "errors" attribute.
55+
*/
56+
private IncludeAttribute includeBindingErrors = IncludeAttribute.NEVER;
5257

5358
private final Whitelabel whitelabel = new Whitelabel();
5459

@@ -76,12 +81,20 @@ public void setIncludeStacktrace(IncludeStacktrace includeStacktrace) {
7681
this.includeStacktrace = includeStacktrace;
7782
}
7883

79-
public IncludeDetails getIncludeDetails() {
80-
return this.includeDetails;
84+
public IncludeAttribute getIncludeMessage() {
85+
return this.includeMessage;
86+
}
87+
88+
public void setIncludeMessage(IncludeAttribute includeMessage) {
89+
this.includeMessage = includeMessage;
8190
}
8291

83-
public void setIncludeDetails(IncludeDetails includeDetails) {
84-
this.includeDetails = includeDetails;
92+
public IncludeAttribute getIncludeBindingErrors() {
93+
return this.includeMessage;
94+
}
95+
96+
public void setIncludeBindingErrors(IncludeAttribute includeMessage) {
97+
this.includeMessage = includeMessage;
8598
}
8699

87100
public Whitelabel getWhitelabel() {
@@ -96,39 +109,56 @@ public enum IncludeStacktrace {
96109
/**
97110
* Never add stacktrace information.
98111
*/
99-
NEVER,
112+
NEVER(IncludeAttribute.NEVER),
100113

101114
/**
102115
* Always add stacktrace information.
103116
*/
104-
ALWAYS,
117+
ALWAYS(IncludeAttribute.ALWAYS),
118+
119+
/**
120+
* Add error attribute when the appropriate request parameter is "true".
121+
*/
122+
ON_PARAM(IncludeAttribute.ON_PARAM),
105123

106124
/**
107125
* Add stacktrace information when the "trace" request parameter is "true".
126+
* @deprecated since 2.3.0 in favor of {@link #ON_PARAM}
108127
*/
109-
ON_TRACE_PARAM
128+
@Deprecated
129+
ON_TRACE_PARAM(IncludeAttribute.ON_PARAM);
130+
131+
private final IncludeAttribute include;
132+
133+
IncludeStacktrace(IncludeAttribute include) {
134+
this.include = include;
135+
}
136+
137+
public IncludeAttribute getInclude() {
138+
return this.include;
139+
}
110140

111141
}
112142

113143
/**
114-
* Include error details attributes options.
144+
* Include error attributes options.
115145
*/
116-
public enum IncludeDetails {
146+
public enum IncludeAttribute {
117147

118148
/**
119-
* Never add error detail information.
149+
* Never add error attribute.
120150
*/
121151
NEVER,
122152

123153
/**
124-
* Always add error detail information.
154+
* Always add error attribute.
125155
*/
126156
ALWAYS,
127157

128158
/**
129-
* Add error details information when the "details" request parameter is "true".
159+
* Add error attribute when the appropriate request parameter is "true".
130160
*/
131-
ON_DETAILS_PARAM
161+
ON_PARAM
132162

133163
}
134164

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,24 +134,26 @@ public void setViewResolvers(List<ViewResolver> viewResolvers) {
134134
* @param includeStackTrace whether to include the error stacktrace information
135135
* @return the error attributes as a Map
136136
* @deprecated since 2.3.0 in favor of
137-
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)}
137+
* {@link #getErrorAttributes(ServerRequest, boolean, boolean, boolean)}
138138
*/
139139
@Deprecated
140140
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
141-
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, false);
141+
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, false, false);
142142
}
143143

144144
/**
145145
* Extract the error attributes from the current request, to be used to populate error
146146
* views or JSON payloads.
147147
* @param request the source request
148-
* @param includeStackTrace whether to include the error stacktrace information
149-
* @param includeDetails whether to include message and errors attributes
148+
* @param includeStackTrace whether to include the stacktrace attribute
149+
* @param includeMessage whether to include the message attribute
150+
* @param includeBindingErrors whether to include the errors attribute
150151
* @return the error attributes as a Map
151152
*/
152153
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
153-
boolean includeDetails) {
154-
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeDetails);
154+
boolean includeMessage, boolean includeBindingErrors) {
155+
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeMessage,
156+
includeBindingErrors);
155157
}
156158

157159
/**
@@ -173,13 +175,23 @@ protected boolean isTraceEnabled(ServerRequest request) {
173175
}
174176

175177
/**
176-
* Check whether the details attribute has been set on the given request.
178+
* Check whether the message attribute has been set on the given request.
179+
* @param request the source request
180+
* @return {@code true} if the message attribute has been requested, {@code false}
181+
* otherwise
182+
*/
183+
protected boolean isMessageEnabled(ServerRequest request) {
184+
return getBooleanParameter(request, "message");
185+
}
186+
187+
/**
188+
* Check whether the errors attribute has been set on the given request.
177189
* @param request the source request
178-
* @return {@code true} if the error details have been requested, {@code false}
190+
* @return {@code true} if the errors attribute has been requested, {@code false}
179191
* otherwise
180192
*/
181-
protected boolean isDetailsEnabled(ServerRequest request) {
182-
return getBooleanParameter(request, "details");
193+
protected boolean isBindingErrorsEnabled(ServerRequest request) {
194+
return getBooleanParameter(request, "errors");
183195
}
184196

185197
private boolean getBooleanParameter(ServerRequest request, String parameterName) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,10 @@ protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes erro
114114
*/
115115
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
116116
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
117-
boolean includeDetails = isIncludeDetails(request, MediaType.TEXT_HTML);
118-
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
117+
boolean includeMessage = isIncludeMessage(request, MediaType.TEXT_HTML);
118+
boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.TEXT_HTML);
119+
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
120+
includeBindingErrors);
119121
int errorStatus = getHttpStatus(error);
120122
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
121123
return Flux.just(getData(errorStatus).toArray(new String[] {}))
@@ -143,8 +145,10 @@ private List<String> getData(int errorStatus) {
143145
*/
144146
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
145147
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
146-
boolean includeDetails = isIncludeDetails(request, MediaType.ALL);
147-
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
148+
boolean includeMessage = isIncludeMessage(request, MediaType.ALL);
149+
boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.ALL);
150+
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
151+
includeBindingErrors);
148152
return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
149153
.body(BodyInserters.fromValue(error));
150154
}
@@ -155,10 +159,12 @@ protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
155159
* @param produces the media type produced (or {@code MediaType.ALL})
156160
* @return if the stacktrace attribute should be included
157161
*/
162+
@SuppressWarnings("deprecation")
158163
protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) {
159164
switch (this.errorProperties.getIncludeStacktrace()) {
160165
case ALWAYS:
161166
return true;
167+
case ON_PARAM:
162168
case ON_TRACE_PARAM:
163169
return isTraceEnabled(request);
164170
default:
@@ -167,17 +173,34 @@ protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces)
167173
}
168174

169175
/**
170-
* Determine if the message and errors attributes should be included.
176+
* Determine if the message attribute should be included.
171177
* @param request the source request
172178
* @param produces the media type produced (or {@code MediaType.ALL})
173-
* @return if the message and errors attributes should be included
179+
* @return if the message attribute should be included
174180
*/
175-
protected boolean isIncludeDetails(ServerRequest request, MediaType produces) {
176-
switch (this.errorProperties.getIncludeDetails()) {
181+
protected boolean isIncludeMessage(ServerRequest request, MediaType produces) {
182+
switch (this.errorProperties.getIncludeMessage()) {
177183
case ALWAYS:
178184
return true;
179-
case ON_DETAILS_PARAM:
180-
return isDetailsEnabled(request);
185+
case ON_PARAM:
186+
return isMessageEnabled(request);
187+
default:
188+
return false;
189+
}
190+
}
191+
192+
/**
193+
* Determine if the errors attribute should be included.
194+
* @param request the source request
195+
* @param produces the media type produced (or {@code MediaType.ALL})
196+
* @return if the errors attribute should be included
197+
*/
198+
protected boolean isIncludeBindingErrors(ServerRequest request, MediaType produces) {
199+
switch (this.errorProperties.getIncludeBindingErrors()) {
200+
case ALWAYS:
201+
return true;
202+
case ON_PARAM:
203+
return isBindingErrorsEnabled(request);
181204
default:
182205
return false;
183206
}

0 commit comments

Comments
 (0)