入力チェック
********************************************************************************
.. only:: html
.. contents:: 目次
:depth: 4
:local:
|
Overview
================================================================================
| ユーザーが入力した値が不正かどうかを検証することは必須である。
| 入力値の検証は大きく分けて、
#. 長さや形式など、文脈によらず入力値だけを見て、それが妥当かどうかを判定できる検証
#. システムの状態によって入力値が妥当かどうかが変わる検証
がある。
1.の例としては必須チェックや、桁数チェックがあり、2.の例としては登録済みのE-mailかどうかのチェックや、注文数が在庫数以内であるかどうかのチェックが挙げられる。
本節では、基本的には前者のことを説明し、このチェックのことを「入力チェック」を呼ぶ。後者のチェックは「業務ロジックチェック」と呼ぶ。業務ロジックチェックについては\ :doc:`../../ImplementationAtEachLayer/DomainLayer`\ を参照されたい。
本ガイドラインでは、基本的に入力チェックをアプリケーション層で行い、業務ロジックチェックは、ドメイン層で行うことをポリシーとする。
Webアプリケーションの入力チェックには、サーバサイドで行うチェックと、クライアントサイド(JavaScript)で行うチェックがある。サーバーサイドのチェックは必須であるが、クライアントサイドでも同じチェックを実施すると、サーバー通信なしでチェック結果が分かるため、ユーザビリティが向上する。
.. warning::
JavaScriptによるクライアントサイドの処理は改ざん可能であるため、サーバーサイドのチェックは必ず行うこと。
クライアントサイドのみでチェックを行い、サーバーサイドでチェックを省略した場合は、システムが危険な状態に晒されていることになる。
|
入力チェックの分類
--------------------------------------------------------------------------------
入力チェックは、単項目チェック、相関項目チェックに分類される。
.. tabularcolumns:: |p{0.15\linewidth}|p{0.30\linewidth}|p{0.25\linewidth}|p{0.30\linewidth}|
.. list-table::
:header-rows: 1
:widths: 15 30 25 30
* - 種類
- 説明
- 例
- 実現方法
* - 単項目チェック
- | 単一のフィールドで完結するチェック
- | 入力必須チェック
| 桁チェック
| 型チェック
- | Bean Validation (実装ライブラリとしてHibernate Validatorを使用)
* - 相関項目チェック
- | 複数のフィールドを比較するチェック
- | パスワードと確認用パスワードの一致チェック
- | \ :url_spring_reference:`org.springframework.validation.Validator `\ インタフェースを実装したValidationクラス
| または Bean Validation
| Spring は、Java標準であるBean Validationをサポートしている。単項目チェックには、このBean Validationを利用する。
| 相関項目チェックの場合は、Bean ValidationまたはSpringが提供している\ ``org.springframework.validation.Validator``\ インタフェースを利用する。
|
.. _ValidationHowToUse:
How to use
================================================================================
.. _ValidationAddDependencyLibrary:
依存ライブラリの追加
--------------------------------------------------------------------------------
Bean Validation 3.0(Hibernate Validator 8.x)以上を使用する場合、Bean ValidationのAPI仕様クラス(\ ``jakarta.validation``\ パッケージのクラス)が格納されているjarファイルとHibernate Validatorのjarファイルに加えて、
* Expression Language 5.0以上のAPI仕様クラス (\ ``jakarta.el``\ パッケージのクラス)
* Expression Language 5.0以上のリファレンス実装クラス
が格納されているライブラリが必要となる。
アプリケーションサーバにデプロイして動かす場合は、これらのライブラリはアプリケーションサーバから提供されているため、依存ライブラリの追加は不要である。ただし、スタンドアロン環境(JUnitなど)で動かす場合は、これらのライブラリを依存ライブラリとして追加する必要がある。
スタンドアロン環境でBean Validation 3.0以上を動かす際に必要となるライブラリの追加例を以下に示す。
.. code-block:: xml
org.apache.tomcat.embed
tomcat-embed-el
test
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | スタンドアロン環境で動かすプロジェクトの\ :file:`pom.xml`\ ファイルに、Expression Language用のクラスが格納されているライブラリを追加する。
|
| 上記例では、組込み用のApache Tomcat向けに提供されているライブラリを指定している。
| \ ``tomcat-embed-el``\ のjarファイルには、Expression LanguageのAPI仕様クラスとリファレンス実装クラスの両方が格納されている。
* - | (2)
- | JUnitを実行するために依存ライブラリが必要になる場合は、スコープは \ ``test``\ が適切である。
|
.. _ValidationSingleCheck:
単項目チェック
--------------------------------------------------------------------------------
単項目チェックを実装するには、
* フォームクラスのフィールドに、Bean Validation用のアノテーションを付与する
* Controllerに、検証するための\ ``@Validated``\ アノテーションを付与する
* JSPまたはThymeleafのテンプレートHTMLに、検証エラーメッセージを表示するためのタグを追加する
が必要である。
.. note::
spring-mvc.xmlに\ ````\ の設定が行われていれば、Bean Validationは有効になる。
|
.. _ValidationInputRequiredCheck:
入力必須チェックについて
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| 入力必須チェックは基本的に、対象のフィールドに\ ``@NotNull``\ を付け、値が\ ``null``\ ではないことを確認することでチェックを行う。しかし、文字列の入力フィールドに未入力の状態でフォームを送信した場合、Spring MVCのデフォルト挙動ではフォームオブジェクトに\ **nullではなく、空文字がバインドされる**\ 。そのため、文字列の入力フィールドに対して入力必須チェックを行う場合は、\ ``@NotNull``\ ではなく、\ ``@NotEmpty``\ を使用して\ ``null``\ および空文字を許可しないことを推奨する。
| 入力フィールドが未入力の場合に、空文字ではなく\ ``null``\ にバインドする方法に関しては、\ :ref:`ValidationStringTrimmerEditor`\ を参照されたい。
文字列のフィールドに対して入力必須チェックだけではなく、\ ``@Size(min = 1, max = 20)``\ のように最小の文字数をチェックする場合は\ ``@NotNull``\ を使用した場合と\ ``@NotEmpty``\ を使用した場合では挙動が異なる点に注意されたい。
* \ ``@NotNull``\ を使用した場合は、未入力の場合\ ``@Size``\ のエラーメッセージのみが表示される。
* \ ``@NotEmpty``\ を使用した場合は、未入力の場合\ ``@NotEmpty``\ のエラーメッセージと\ ``@Size``\ のエラーメッセージが表示される。
\ ``@Size``\ のminを1以上に設定する場合は空文字を許容しないため、\ ``@NotEmpty``\ によるチェックは冗長となる。そのため、本ガイドラインで文字列フィールドに対して入力必須チェックと文字列長の最小値チェックを行う場合は、\ ``@NotNull``\ を使用する。
以上の説明より、本ガイドラインでは次の表の内容に従い入力必須チェックを行う。
.. tabularcolumns:: |p{0.20\linewidth}|p{0.40\linewidth}|p{0.30\linewidth}|p{0.50\linewidth}|
.. list-table::
:header-rows: 1
:widths: 20 30 30 50
* - 対象フィールド
- チェック内容
- 使用するアノテーション
- 説明
* - | 文字列以外
- | 入力必須チェック
- | \ ``@NotNull``\
- | \ ``null``\ を許可しない
* - | 文字列
- | 入力必須チェック
- | \ ``@NotEmpty``\
- | \ ``null``\ と空文字を許可しない
* - |
- | 入力必須チェック + 文字列長チェック
- | \ ``@NotNull``\ + \ ``@Size(min = 1, max = 20)``\
- | 未入力の場合は\ ``@Size``\ のエラーメッセージのみが表示される。
.. _ValidationBasicValidation:
基本的な単項目チェック
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
「新規ユーザー登録」処理を例に用いて、実装方法を説明する。ここでは「新規ユーザー登録」のフォームに、以下のチェックルールを設ける。
.. tabularcolumns:: |p{0.20\linewidth}|p{0.30\linewidth}|p{0.50\linewidth}|
.. list-table::
:header-rows: 1
:widths: 20 30 50
* - フィールド名
- 型
- ルール
* - | name
- | \ ``java.lang.String``\
- | 入力必須
| 1文字以上
| 20文字以下
* - | email
- | \ ``java.lang.String``\
- | 入力必須
| 1文字以上
| 50文字以下
| E-mail形式
* - | age
- | \ ``java.lang.Integer``\
- | 入力必須
| 1以上
| 200以下
* フォームクラス
フォームクラスの各フィールドに、Bean Validationのアノテーションを付ける。使用するアノテーションの詳細は\ :ref:`ValidationJsr380Doc`\ を参照されたい。
.. code-block:: java
package com.example.sample.app.validation;
import java.io.Serializable;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserForm implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull // (1)
@Size(min = 1, max = 20) // (2)
private String name;
@NotNull
@Size(min = 1, max = 50)
@Email // (3)
private String email;
@NotNull // (4)
@Min(0) // (5)
@Max(200) // (6)
private Integer age;
// omitted setter/getter
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | 対象のフィールドが\ ``null``\ でないことを示す\ ``jakarta.validation.constraints.NotNull``\ を付ける。
|
| Spring MVCでは、文字列の入力フィールドに未入力の状態でフォームを送信した場合、
| デフォルトではフォームオブジェクトに\ **nullではなく、空文字がバインドされる**\ 。
| この\ ``@NotNull``\ は、そもそもリクエストパラメータとして\ ``name``\ が存在することをチェックする。
* - | (2)
- | 対象のフィールドの文字列長(またはコレクションのサイズ)が指定したサイズの範囲内にあることを示す\ ``jakarta.validation.constraints.Size``\ を付ける。
|
| 上記の通り、Spring MVCではデフォルトで、未入力の文字列フィールドには、空文字がバインドされるため、
| 1文字以上というルールが入力必須を表す。
* - | (3)
- | 対象のフィールドがE-mail形式であることを示す\ ``jakarta.validation.constraints.Email``\ を付ける。
|
| E-mail形式の要件が\ ``@Email`` \ のチェックと合致しない場合は、\ ``jakarta.validation.constraints.Pattern``\ を用いて、正規表現を指定する必要がある。
| \ ``@Email`` \ については、\ :ref:`ValidationJsr380Doc`\ を参照されたい。
* - | (4)
- | 数値の入力フィールドに未入力の状態でフォームを送信した場合、フォームオブジェクトに\ ``null`` \ がバインドされるため、\ ``@NotNull``\ が\ ``age``\ の入力必須条件を表す。
* - | (5)
- | 対象のフィールドが指定した数値の以上であることを示す\ ``jakarta.validation.constraints.Min``\ を付ける。
* - | (6)
- | 対象のフィールドが指定した数値の以下であることを示す\ ``jakarta.validation.constraints.Max``\ を付ける。
.. tip::
Bean Validation標準のアノテーション、Hibernate Validationが用意しているアノテーションについては、\ :ref:`ValidationJsr380Doc`\ 、\ :ref:`ValidationValidatorList`\ を参照されたい。
* Controllerクラス
入力チェック対象のフォームクラスに、\ ``@Validated``\ を付ける。
.. code-block:: java
package com.example.sample.app.validation;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("user")
public class UserController {
@ModelAttribute
public UserForm setupForm() {
return new UserForm();
}
@GetMapping(value = "create", params = "form")
public String createForm() {
return "user/createForm"; // (1)
}
@PostMapping(value = "create", params = "confirm")
public String createConfirm(@Validated /* (2) */ UserForm form, BindingResult /* (3) */ result) {
if (result.hasErrors()) { // (4)
return "user/createForm";
}
return "user/createConfirm";
}
@PostMapping(value = "create")
public String create(@Validated UserForm form, BindingResult result) { // (5)
if (result.hasErrors()) {
return "user/createForm";
}
// omitted business logic
return "redirect:/user/create?complete";
}
@GetMapping(value = "create", params = "complete")
public String createComplete() {
return "user/createComplete";
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | 「新規ユーザー登録」フォーム画面を表示する。
* - | (2)
- | フォームにつけたアノテーションで入力チェックをするために、フォームの引数に \ ``org.springframework.validation.annotation.Validated``\ を付ける。
* - | (3)
- | (2)のチェック結果を格納する\ ``org.springframework.validation.BindingResult``\ を、引数に加える。
| この\ ``BindingResult``\ は、フォームの直後に記述する必要がある。
|
| 直後に指定されていない場合は、検証後に結果をバインドできず、\ ``org.springframework.validation.BindException``\ がスローされる。
* - | (4)
- | (2)のチェック結果は、\ ``BindingResult.hasErrors()``\ メソッドで判定できる。
| \ ``hasErrors()``\ の結果が\ ``true``\ の場合は、入力値に問題があるため、フォーム表示画面に戻す。
* - | (5)
- | 入力内容確認画面から新規作成処理にリクエストを送る際にも、\ **入力チェックを必ず再実行すること**\ 。
| 途中でデータを改ざんすることは可能であるため、必ず業務処理の直前で入力チェックは必要である。
\ ``@Validated``\ は、Bean Validation標準ではなくSpringの独自アノテーションである。
Bean Validation標準の\ ``jakarta.validation.Valid``\ アノテーションも使用できるが、\ ``@Validated``\ は\ ``@Valid``\ に比べてバリデーションのグループを指定できる点で優れているため、本ガイドラインではControllerの引数には\ ``@Validated``\ を使用することを推奨する。
.. _ValidationJspImplSample:
* View
.. tabs::
.. group-tab:: JSP
* JSP
\ ````\ タグで、入力エラーがある場合にエラーメッセージを表示できる。
.. code-block:: jsp
<%-- WEB-INF/views/user/createForm.jsp --%>
Name:
<%--(1) --%>
Email:
Age:
Confirm
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ````\ タグの\ ``path``\ 属性に、対象のフィールド名を指定する。
| この例では、フィールド毎に入力フィールドの横にエラーメッセージを表示する。
.. group-tab:: Thymeleaf
* HTML
\ ``th:errors``\ 属性で、入力エラーがある場合にエラーメッセージを表示できる。
.. code-block:: html
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ````\ タグの\ ``th:errors``\ 属性に、対象のフィールド名を指定する。
| この例では、フィールド毎に入力フィールドの横にエラーメッセージを表示する。
フォームは、以下のように表示される。
.. figure:: ./images_Validation/validations-first-sample1.png
:width: 60%
このフォームに対して、すべての入力フィールドを未入力のまま送信すると、以下のようにエラーメッセージが表示される。
.. figure:: ./images_Validation/validations-first-sample2.png
:width: 60%
NameとEmailが空文字であることに対するエラーメッセージと、Ageが\ ``null``\ であることに対するエラーメッセージが表示されている。
.. note::
Bean Validationでは、通常、入力値が\ ``null``\ の場合は正常な値とみなす。ただし、以下のアノテーションを除く。
* \ ``jakarta.validation.constraints.NotNull``\
* \ ``jakarta.validation.constraints.NotEmpty``\
* \ ``jakarta.validation.constraints.NotBlank``\
上記の例では、Ageの値は\ ``null``\ であるため、\ ``@Min``\ と\ ``@Max``\ によるチェックは正常とみなされ、エラーメッセージは出力されていない。
次に、フィールドに何らかの値を入力してフォームを送信する。
.. figure:: ./images_Validation/validations-first-sample3.png
:width: 60%
| Nameの入力値は、チェック条件を満たすため、エラーメッセージが表示されない。
| E-mailの入力値は文字列長に関する条件は満たすが、E-mail形式ではないため、エラーメッセージが表示される。
| Ageの入力値は最大値を超えているため、エラーメッセージが表示される。
エラー時にスタイルを変更したい場合は、前述のフォームを、以下のように変更する。
.. tabs::
.. group-tab:: JSP
.. code-block:: jsp
Name:<%-- (1) --%>
<%-- (2) --%>
<%-- (3) --%>
Email:
Age:
Confirm
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | エラー時に\ ``