入力値のバリデーション
この章の目的とゴール
この章では、Webアプリケーションにおいて 入力値のバリデーション(検証) を行う方法を学ぶ。 ユーザーが送信した値をそのまま受け入れると、システムの誤動作やセキュリティ事故につながるため、 入力値が正しいかをチェックすることは必須である。
この章では、Spring MVCで入力値のバリデーション(検証)を行う方法を学ぶ。 フォーム入力のチェックを自動化し、安全で信頼性の高いアプリケーションを作る基礎を身につける。
学習のゴール
- バリデーションの目的と種類(単項目チェック / 相関項目チェック)を理解できる
- フォームオブジェクトのフィールドにアノテーションを付けて自動検証を実装できる
@AssertTrueを使って複数項目の関係(相関チェック)を実装できる- コントローラで
@ValidatedとBindingResultを用いて検証結果を処理できる th:object/th:fieldを使ってフォーム入力とエラーメッセージを連携できる- フロントエンドだけではなく、サーバサイドでもバリデーションが必要な理由を理解できる
バリデーションとは
バリデーションとは、入力された値が正しいかどうかを検証する仕組み である。
- 数字が入るべきところに文字列が入力されていないか
- 必須項目が空になっていないか
- 2つの項目(パスワードと確認用パスワード)が一致しているか
などをチェックする。
単項目チェックと相関項目チェック
単項目チェック
1つの入力値が正しいかどうかを確認する。 例: 名前が空でないか、年齢が0以上か、メールアドレス形式か。
相関項目チェック
複数の入力値の関係が正しいかどうかを確認する。 例: パスワードと確認用パスワードが一致しているか、開始日が終了日より前か。
単項目チェックの実装
単項目チェックは、フォームオブジェクトのフィールドにアノテーションを付与することで実現できる。
public class UserForm {@NotNull(message = "名前は必須です")private String name;@Min(value = 0, message = "年齢は0以上で入力してください")@Max(value = 120, message = "年齢は120以下で入力してください")private Integer age;@Email(message = "メールアドレスの形式が正しくありません")private String email;// getter / setter}
@NotNull: 値が null でないことを保証する@Min,@Max: 数値の範囲を指定する@Email: メールアドレス形式かどうかを確認する
| アノテーション | チェック内容 |
|---|---|
@NotNull | null でなければOK(空文字は通過してしまう) |
@NotEmpty | null でも空文字でもなければOK(空白のみはNG) |
@NotBlank | null でも空文字でも空白のみでもなければOK |
文字列の入力必須チェックには @NotBlank を使うのが最も適切である。
👉 独自アノテーションを作ることも可能だが、本研修では扱わない。
相関項目チェックの実装
複数項目の関係をチェックするには @AssertTrue を用いる。
public class PasswordForm {private String password;private String confirmPassword;@AssertTrue(message = "パスワードが一致しません")public boolean isPasswordConfirmed() {return password != null && password.equals(confirmPassword);}// (省略) getter / setter}
@AssertTrueが付いたメソッドは、trueを返すと検証成功、falseを返すと失敗- この例では、
passwordとconfirmPasswordが一致しなければエラーとなる
👉 より柔軟に実装するには Validator インターフェースを実装する方法もあるが、本研修では扱わない。
コントローラでのバリデーション
フォームオブジェクトを受け取るコントローラのメソッドに @Validated を付与する。
忘れるとバリデーションが実行されないので注意。
バリデーション結果は BindingResult から取得できる。
@Controllerpublic class RegisterController {@PostMapping("/register")public String register(@Validated UserForm form, // @Validated を付与するとバリデーションが実行されるBindingResult bindingResult, // バリデーション結果を受け取るModel model) {if (bindingResult.hasErrors()) {return "registerForm"; // エラー時はフォーム画面に戻す}// 正常処理model.addAttribute("msg", "登録完了");return "result";}}
@Validated: このオブジェクトに対してバリデーションを行うBindingResult: 検証結果(エラーの有無・内容)を保持する
@Validated を付け忘れると、フォームオブジェクトにどんなアノテーションを書いても
バリデーションは一切実行されず、常に通過してしまう。
また、BindingResult は 必ず @Validated を付けたフォームオブジェクトの直後 に書く必要がある。
順番が違うと正しく動作しない。
// 正しい順番
public String register(@Validated UserForm form, BindingResult bindingResult, Model model)
// 間違い(BindingResult が離れている)
public String register(@Validated UserForm form, Model model, BindingResult bindingResult)
フォーム定義とエラーメッセージ表示
Spring MVCでは、コントローラでフォームオブジェクトを受け取り、ビューに返す ことでバリデーションとエラーメッセージ表示を行う。
フォーム側では th:object / th:field を使うことで、フォームオブジェクトとHTMLを簡潔にバインドできる。
@Controllerpublic class RegisterController {@GetMapping("/register")public String showForm(UserForm form) {// 空のフォームをビューに渡す// model.addAttribute("userForm", form); // ← これは不要return "registerForm";}@PostMapping("/register")public String register(@Validated UserForm form,BindingResult bindingResult,Model model) {if (bindingResult.hasErrors()) {// バリデーション失敗 → 入力画面に戻すreturn "registerForm";}// 正常処理model.addAttribute("msg", "登録完了");return "result";}}
@GetMapping("/register"): 入力フォームを表示する@PostMapping("/register"): 入力送信を受け取る@Validated: バリデーションを有効化BindingResult: バリデーション結果を保持。エラーがあればフォームに戻す
👉 ポイント
showForm(UserForm form)の引数は、明示的にmodel.addAttribute("userForm", form)と書かなくてもビューに渡される。- Spring MVCの仕様として、フォームオブジェクトはクラス名の先頭を小文字にした名前で自動的にModelへ追加される。
UserForm→userForm
- そのため、ビュー側で
${userForm}としてアクセスできる。 - もし別名を使いたい場合は
@ModelAttribute("form")のように指定する。
th:object と th:field を使う場合
<!-- registerForm.html -->
<form th:action="@{/register}" th:object="${userForm}" method="post">
<p>名前: <input type="text" th:field="*{name}" /></p>
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>
<p>年齢: <input type="number" th:field="*{age}" /></p>
<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>
<p>メール: <input type="email" th:field="*{email}" /></p>
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>
<button type="submit">登録</button>
</form>
th:object="${userForm}": このフォームがUserFormにバインドされることを示すth:field="*{name}":userForm.nameに対応する。フォーム送信時に値が自動的にセットされるth:errors="*{name}": name フィールドにエラーがあればメッセージを表示する
th:object / th:field を使わない場合
<!-- registerForm.html -->
<form action="/register" method="post">
<p>名前: <input type="text" name="name" value="[[${userForm.name}]]" /></p>
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>
<p>年齢: <input type="number" name="age" value="[[${userForm.age}]]" /></p>
<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>
<p>メール: <input type="email" name="email" value="[[${userForm.email}]]" /></p>
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>
<button type="submit">登録</button>
</form>
- フィールド名 (
name,age,email) を手動で合わせる必要がある - 入力値の保持(再表示)を
${userForm.xxx}と書かねばならず、記述が冗長になる - エラーメッセージ表示は
th:errorsを使う点は同じ
th:object / th:field を使うメリット
- 入力フォームとフォームオブジェクトのフィールドが自動的にバインドされる
- 入力エラーがあっても、入力値を保持して再表示できる(自動)
- フィールド名のズレを防げる(Java側で
nameを変更すれば、自動的にフォームも連動する)
👉 実務では 必ず th:object と th:field を使うのが基本。
なぜフロントエンドのバリデーションだけでは不十分なのか
- フロントエンド(JavaScriptやHTML5)のバリデーションは、ブラウザ上で無効化することが可能
- 攻撃者が意図的にリクエストを送れば、チェックを回避できてしまう
- セキュリティやデータの整合性を守るため、必ずサーバサイドでもバリデーションを行う必要がある
HTML5の required 属性や type="email" などは、開発者ツールから簡単に削除・改ざんできる。
また、curlコマンドなどで直接HTTPリクエストを送れば、ブラウザのバリデーションは完全にスキップされる。
フロントエンドのバリデーションは「ユーザーに優しい補助機能」であり、最後の砦はサーバ側である。
よくある質問
Q. @NotNull と @NotBlank はどちらを使えばよいですか?
A. 文字列フィールドの入力必須チェックには @NotBlank を使うのが適切である。
@NotNull は null チェックのみのため、空文字 "" や空白のみの文字列 " " は通過してしまう。
@NotBlank は null・空文字・空白のみすべてをエラーとする。
Q. バリデーションのエラーメッセージを日本語にしたい場合はどうすればよいですか?
A. アノテーションの message 属性に日本語を設定するか、ValidationMessages.properties ファイルを使って一元管理できる。
@NotBlank(message = "名前は必須です")
private String name;
Q. 入力エラーが発生してもフォームに前の入力値が残るのはなぜですか?
A. th:field="*{name}" を使っているためである。
th:field はフォームオブジェクトのフィールドの値を value 属性に自動セットする。
バリデーション失敗時にコントローラはフォームオブジェクトをそのままビューに返すため、
入力値が保持されて再表示される。
Q. 相関項目チェックで @AssertTrue を使うとき、メソッド名はなぜ is で始めるのですか?
A. Springのバリデーション機能はJavaBeans仕様に基づいており、@AssertTrue を付けたメソッドは
「boolean型のgetter」として扱われる必要がある。
Javaのgetterの命名規則として、boolean 型の場合は isXxx() の形式にする必要がある。
本章のまとめ
- バリデーションは入力値の正しさを保証するために必要
- 単項目チェック(
@NotBlank,@Max,@Emailなど)と相関項目チェック(@AssertTrue)がある - コントローラでは
@Validatedを付与し、BindingResultで結果を受け取る BindingResultは@Validatedを付けたフォームオブジェクトの 直後 に書く- フォームは
th:objectとth:fieldで定義し、#fieldsを使ってエラーメッセージを表示する - フロントエンドのバリデーションだけでは不十分。サーバサイドで必ず検証を行うこと
- 次章では、SpringのDI(依存性注入)の仕組みを学ぶ