Spring Boot 2.3 からデフォルトのエラーページに詳細なエラー情報が含まれなくなった

## 概要

- Spring Boot 2.3 から、デフォルトのエラーレスポンス (エラーページ HTML とエラー JSON)に、エラーメッセージやバインディングエラーが含まれなくなった。これは意図しないエラー情報をレスポンスで返さないための措置。
- プロパティ値を設定することでエラー情報を出力する・出力しないを制御できる。

## エラーページ HTML やエラー JSON に出力される項目

[DefaultErrorAttributes \(Spring Boot 2\.3\.0\.RELEASE API\)](https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/api/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.html) 等に出力される項目が記載されている。

- timestamp: エラーが抽出された時間
- status: ステータスコード
- error: エラーの理由
- exception: ルート例外のクラス名
- message: 例外メッセージ
- errors: BindingResult にセットされている複数の ObjectError (binding-errors)
- trace: 例外のスタックトレース
- path: 例外が発生したときの URL パス

## 設定用プロパティ

application.properties 等で server.error.include-message (message) や server.error.include-binding-errors の値を設定することによって出力の有無を制御できる。

[Spring Boot 2\.3 Release Notes · spring\-projects/spring\-boot Wiki · GitHub](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#changes-to-the-default-error-pages-content)

> Changes to the Default Error Page’s Content
> The error message and any binding errors are no longer included in the default error page by default. This reduces the risk of leaking information to a client. server.error.include-message and server.error.include-binding-errors can be used to control the inclusion of the message and binding errors respectively. Supported values are always, on-param, and never.

- server.error.include-message (message 項目の出力を制御)
- server.error.include-binding-errors (errors 項目の出力を制御)
- server.error.include-stacktrace (trace 項目の出力を制御)
- server.error.include-exception (exception 項目の出力を制御)

## エラー出力例

いくつかの設定パターンによるエラーレスポンスの例を挙げる。
挙げている HTML や JSON は見やすくするために整形したものを載せている。

### Spring Boot 2.2

application.properties に何も指定しない場合。
Spring Boot 2.2 では何を指定しなくても message や errors が出力されてしまう。

#### JSON

```json
{
  "timestamp": "2020-07-19T06:14:27.622+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": null,
      "arguments": null,
      "defaultMessage": "人為的に発生させたfooエラー",
      "objectName": "fooError",
      "code": null
    },
    {
      "codes": null,
      "arguments": null,
      "defaultMessage": "人為的に発生させたbarエラー",
      "objectName": "barError",
      "code": null
    }
  ],
  "message": "Validation failed for object='fooBarData'. Error count: 2",
  "path": "/json"
}
```

#### HTML

```html
<html><body>
<h1>Whitelabel Error Page</h1>
<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
<div id='created'>Sun Jul 19 15:14:30 JST 2020</div>
<div>There was an unexpected error (type=Bad Request, status=400).</div>
<div>Validation failed for object=&#39;fooBarData&#39;. Error count: 2</div>
</body></html>
```

### Spring Boot 2.3

application.properties に何も指定しない場合。
詳細なエラー情報を出力しない設定となる。

#### JSON

```json
{
  "timestamp": "2020-07-19T06:13:46.546+00:00",
  "status": 400,
  "error": "Bad Request",
  "message": "",
  "path": "/json"
}
```

#### HTML

```html
<html><body>
<h1>Whitelabel Error Page</h1>
<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
<div id='created'>Sun Jul 19 15:13:48 JST 2020</div>
<div>There was an unexpected error (type=Bad Request, status=400).</div>
<div></div>
</body></html>
```

### Spring Boot 2.3 + プロパティ設定に never, false 指定

application.properties に以下を指定する場合。
詳細なエラー情報を出力しない設定となる。

```properties
server.error.include-message=never
server.error.include-binding-errors=never
server.error.include-stacktrace=never
server.error.include-exception=false
```

#### JSON

```json
{
  "timestamp": "2020-07-19T06:13:18.720+00:00",
  "status": 400,
  "error": "Bad Request",
  "message": "",
  "path": "/json"
}
```

#### HTML

```html
<html><body>
<h1>Whitelabel Error Page</h1>
<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
<div id='created'>Sun Jul 19 15:13:22 JST 2020</div>
<div>There was an unexpected error (type=Bad Request, status=400).</div>
<div></div>
</body></html>
```

### Spring Boot 2.3 + プロパティ設定に always, true 指定

application.properties に以下を指定する場合。
詳細なエラー情報を出力する設定となる。

```properties
server.error.include-message=always
server.error.include-binding-errors=always
server.error.include-stacktrace=always
server.error.include-exception=true
```

#### JSON

```json
{
  "timestamp": "2020-07-19T06:11:54.165+00:00",
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.validation.BindException",
  "trace": "org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 2 errors\n
            Error in object 'fooError': codes []; arguments []; default message [人為的に発生させたfooエラー]\n
            Error in object 'barError': codes []; arguments []; default message [人為的に発生させたbarエラー]\n\t
            at example.FooBarRestController.json(FooBarRestController.java:20)
            (中略)
            java.base/java.lang.Thread.run(Thread.java:832)\n",
  "message": "Validation failed for object='fooBarData'. Error count: 2",
  "errors": [
    {
      "codes": null,
      "arguments": null,
      "defaultMessage": "人為的に発生させたfooエラー",
      "objectName": "fooError",
      "code": null
    },
    {
      "codes": null,
      "arguments": null,
      "defaultMessage": "人為的に発生させたbarエラー",
      "objectName": "barError",
      "code": null
    }
  ],
  "path": "/json"
}
```

#### HTML

```html
<html><body>
<h1>Whitelabel Error Page</h1>
<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
<div id='created'>Sun Jul 19 15:11:48 JST 2020</div>
<div>There was an unexpected error (type=Bad Request, status=400).</div>
<div>Validation failed for object=&#39;fooBarData&#39;. Error count: 2</div>
<div style='white-space:pre-wrap;'>
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 2 errors
Error in object &#39;fooError&#39;: codes []; arguments []; default message [人為的に発生させたfooエラー]
Error in object &#39;barError&#39;: codes []; arguments []; default message [人為的に発生させたbarエラー]
	at example.FooBarController.page(FooBarController.java:20)
(中略)
	at java.base/java.lang.Thread.run(Thread.java:832)
</div>
</body></html>
```

## Spring Boot 2.3 のソースコードでエラー情報を構築している箇所

DefaultErrorAttributes クラスで JSON や HTML で出力するエラー情報を構築している。

[spring\-boot/DefaultErrorAttributes\.java at v2\.3\.0\.RELEASE · spring\-projects/spring\-boot · GitHub](https://github.com/spring-projects/spring-boot/blob/v2.3.0.RELEASE/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java#L131-L138)

```java
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  Map<String, Object> errorAttributes = new LinkedHashMap<>();
  errorAttributes.put("timestamp", new Date());
  addStatus(errorAttributes, webRequest);
  addErrorDetails(errorAttributes, webRequest, includeStackTrace);
  addPath(errorAttributes, webRequest);
  return errorAttributes;
}
```

ErrorMvcAutoConfiguration クラスでエラーページの HTML を構築している。

[spring\-boot/ErrorMvcAutoConfiguration\.java at v2\.3\.0\.RELEASE · spring\-projects/spring\-boot · GitHub](https://github.com/spring-projects/spring-boot/blob/v2.3.0.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java#L209-L227)

```java
StringBuilder builder = new StringBuilder();
Date timestamp = (Date) model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
  response.setContentType(getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
    "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
    .append("<div id='created'>").append(timestamp).append("</div>")
    .append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
    .append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
  builder.append("<div>").append(htmlEscape(message)).append("</div>");
}
if (trace != null) {
  builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
```

## 参考資料

- [Spring Boot 2\.3 Release Notes · spring\-projects/spring\-boot Wiki · GitHub](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#changes-to-the-default-error-pages-content)
- [DefaultErrorAttributes \(Spring Boot 2\.3\.0\.RELEASE API\)](https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/api/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.html)
- [DefaultErrorAttributes \(Spring Boot 2\.3\.0\.RELEASE API\) \- Javadoc 日本語訳](https://spring.pleiades.io/spring-boot/docs/2.3.0.RELEASE/api/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.html)
- [Spring Boot アプリケーションプロパティ一覧 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/2.3.0.RELEASE/reference/html/appendix-application-properties.html)