🛡️ Spring Framework 講座【第17回】システムを不正入力から守る!〜データのバリデーションとエラーハンドリング〜

docs

前回、Controllerでユーザーからのリクエストデータ(@RequestBody や @RequestParam)を受け取る方法を学びました。

しかし、送られてくるデータは信用できません。空欄だったり、文字数が多すぎたり、不正な形式だったりする可能性があります。これらの不正な入力を防ぎ、システムを安定稼働させるために不可欠なのが「バリデーション(Validation:入力値検証)」と「エラーハンドリング」です。

今回は、Spring Bootで標準的に使われるBean Validationの仕組みと、エラーが発生した際の適切な処理方法について学びます。


1. Bean Validationの基本:制約アノテーション

Javaには、データの検証ルールを定義するための標準仕様(Bean Validation)があります。Spring Bootはこれをサポートしており、DTO(データ転送オブジェクト)のフィールドに制約アノテーションを付けるだけで、検証ルールを設定できます。

1-1. DTOと制約アノテーションの定義

前回使用した LoginForm DTOに、検証ルールを追加してみましょう。

Java

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;

@Data 
public class LoginForm {
    
    // @NotBlank: null、空文字列、空白文字のみで構成された文字列を許可しない
    @NotBlank(message = "ユーザー名は必須です")
    @Size(max = 20, message = "ユーザー名は20文字以内にしてください")
    private String username;
    
    // @Size: 文字列の長さを8文字以上30文字以下に制限
    @Size(min = 8, max = 30, message = "パスワードは8文字以上30文字以下で入力してください")
    private String password;

    // @Email: メールアドレス形式であることを要求
    @Email(message = "有効なメールアドレス形式ではありません")
    private String email;
}
主な制約アノテーション意味
@NotNull値が null でないこと。
@NotEmpty文字列やコレクションが空でないこと。
@NotBlank文字列が null でなく、空白でもないこと。
@Size文字列やコレクションのサイズを指定範囲内にすること。
@Min / @Max数値の最小値/最大値を指定すること。
@Pattern正規表現に一致すること。

2. Controllerでの検証実行:@Valid

DTOに制約アノテーションを定義しただけでは、検証は実行されません。Controllerのメソッドで、検証をトリガーする必要があります。

Controllerのメソッドの引数としてDTOを受け取る際に、@Valid アノテーションを付けます。

Java

import jakarta.validation.Valid;
// ... その他のimport

@RestController
@RequestMapping("/auth")
public class AuthController {

    // ① @Valid を付けることで、LoginFormの制約をチェックする
    @PostMapping("/login")
    public String login(@RequestBody @Valid LoginForm form) {
        
        // 検証エラーがなければ、このコードが実行される
        return "ログイン処理を実行します: " + form.getUsername();
    }
}

2-1. 検証エラーが発生した場合

上記のコードで LoginForm の検証エラーが発生した場合、Springは自動的に MethodArgumentNotValidException という例外を発生させます。

この例外を適切に処理しないと、ユーザーには**「HTTP 500 Internal Server Error」**といった不親切なエラーメッセージが表示されてしまいます。


3. エラーハンドリングの標準的な方法

業務システムでは、エラー発生時にユーザーにわかりやすいメッセージを返したり、ログを記録したりする必要があります。Springでは、@ExceptionHandler アノテーションを使った集中管理が標準です。

3-1. @ExceptionHandler の利用

@RestControllerAdvice が付いたクラス(または @ControllerAdvice)を作成し、アプリケーション全体で発生した例外を一元的に捕捉して処理します。

Java

import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.http.HttpStatus;
import java.util.Map;
import java.util.stream.Collectors;

// アプリケーション全体のエラーを捕捉する設定クラス
@RestControllerAdvice 
public class GlobalExceptionHandler {

    // MethodArgumentNotValidException が発生した場合にこのメソッドを呼び出す
    @ExceptionHandler(MethodArgumentNotValidException.class)
    // レスポンスのHTTPステータスコードを 400 Bad Request に設定
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        
        // 発生したエラーメッセージを収集し、Map形式で返す
        Map<String, String> errors = ex.getBindingResult().getFieldErrors().stream()
                .collect(Collectors.toMap(
                    error -> error.getField(),    // エラーが発生したフィールド名
                    error -> error.getDefaultMessage() // 定義したエラーメッセージ
                ));

        return errors; // 例: {"username": "ユーザー名は必須です", "password": "..."}
    }
}

この処理により、不正なリクエストがあった場合、サーバーは以下のようなJSON形式のエラーレスポンスを返します。

【レスポンス例】

JSON

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
    "username": "ユーザー名は必須です",
    "password": "パスワードは8文字以上30文字以下で入力してください"
}

これにより、クライアント側(フロントエンド)は、HTTP 400 ステータスと具体的なエラーメッセージを受け取り、ユーザーに親切なフィードバックを表示できるようになります。

✅ 本日のまとめ

  • バリデーションは、システムを不正な入力から守るために必須の処理である。
  • Bean Validation の制約アノテーション(@NotBlank、@Size など)をDTOに付けることで検証ルールを定義する。
  • Controllerの引数に @Valid を付けることで、検証をトリガーする。
  • @RestControllerAdvice クラスと @ExceptionHandler メソッドを使い、MethodArgumentNotValidException を捕捉して、統一されたエラーレスポンス(HTTP 400 など)を返す。

🔔 次回予告

これで、ユーザーからのリクエストを受け付け、検証するまでの一連の流れをマスターしました。次は、Webアプリケーションの最も重要な部分である「業務ロジック」と「データアクセス」に進みます。

次回は、Controllerから処理を委譲されるService層の役割と、**DI(依存性の注入)**を使ってController、Service、Repositoryの三層構造を連携させる具体的な方法を学びます。

次回:【第18回】三層構造の連携とService層の役割 にご期待ください!

コメント

タイトルとURLをコピーしました