リクエストの本体とパラメータを有効にする


元来はhttps://blog.tericcabrel.com

Never trust user input


Webアプリケーションの典型的なワークフローは、いくつかの入力でリクエストを受け取り、受け取った入力で処理を行い、最終的にレスポンスを返します.我々のアプリケーションを開発するとき、我々は通常「ハッピーパス」だけをテストするか、しばしばエンドユーザーが悪い入力を提供することができないと思います.これを防ぐために、リクエストを処理する前に入力の検証を行うことができます.
春はそれをするエレガントな方法を提供します、そして、我々はそれをする方法を見ます.

ユースケース


ユーザーがホテルの部屋を予約できるシステムを構築する必要がある.登録時にユーザはアドレス情報を提供しなければならない.可能なアクションは以下の通りです.
  • ユーザに住所を登録する.
  • 既存のユーザーの予約を行います.
  • 下には、drawSQLで作られたシステムのエンティティ関係図があります.

    必要条件


    このチュートリアルでは、コンピュータ上でMySQLをインストールしたくない場合は、コンピュータにインストールされているJava 11とMySQLが必要です.実際には、MySQL DockerイメージからDockerコンテナを起動できます.目標を達成するには十分だろう.
    docker run -it -e MYSQL_ROOT_PASSWORD=secretpswd -e MYSQL_DATABASE=hotels --name hotels-mysql -p 3307:3306 mysql:8.0
    

    ドック プロジェクトのセットアップ


    から必要な依存関係を持つ新しいスプリングプロジェクトを作りましょう.
    start.spring.io
    入力検証の原因となる依存性は、Hibernateバリデータを使用したBean妥当性検査です.このライブラリにはHibernateの持続性の側面がリンクされていないことに注意してください.
    IDEでプロジェクトを開き、サーバーポートとデータベースの資格情報をapplication.propertiesファイルに設定します.
    server.port=4650
    
    spring.datasource.url=jdbc:mysql://localhost:3307/hotels?serverTimezone=UTC&useSSL=false
    spring.datasource.username=root
    spring.datasource.password=secretpswd
    
    ## Hibernate properties
    spring.jpa.hibernate.use-new-id-generator-mappings=false
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.show-sql=false
    spring.jpa.open-in-view=false
    

    エンティティとサービスの作成


    エンティティのユーザー、アドレス、および予約を作成する必要があります.各エンティティについて、関連するリポジトリとサービスを作成します.このチュートリアルの主要なトピックではないので、これらのファイルのコードを で見つけます.
  • 実体はパッケージモデルの中にあります.
  • エンティティリポジトリはパッケージリポジトリ内にあります.
  • サービスはパッケージサービスの中にあります.

    githubリポジトリ 登録する


    このアクションを処理するためにエンドポイントを作成し、ユーザーを作成するために必要な入力を受け取るオブジェクトを定義する必要があります.

    リクエスト本文のモデルを作成する


    新しいユーザーを登録するとき、我々はまた、体の彼のアドレスに関する情報を提供します.AddressdToというクラスを作成して、アドレスのすべてのプロパティを保持するクラスを作成することでオブジェクトを構造化する必要があります.また、クラスResistUserdtoクラス内にAddressdTo型のプロパティがあります.
    1つの層(コントローラ)から別の層(永続性)にデータを転送するので、クラス名は でサフィックスされます.彼の使用の一般的な使用例は、我々が他の層に彼らを渡す前に若干の変形データを適用する必要があるときです.あとでもっと詳しい記事を書きます.
    パッケージモデル内にDTOSというパッケージを作成し、2つのクラスaddressdtoを作成します.Javaと登録ユーザ.ジャバ.

    DTO (データ転送オブジェクト) 検証の追加


    Hibernate Validatorは、入力を検証するための組み込み制約を提供します.完全なリストを見るために、 をチェックしてください.
    アドレス.ジャバ
    package com.tericcabrel.hotel.models.dtos;
    
    import com.tericcabrel.hotel.models.Address;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.Pattern;
    import javax.validation.constraints.Pattern.Flag;
    import lombok.Data;
    
    @Data
    public class AddressDto {
      @NotBlank(message = "The country is required.")
      private String country;
    
      @NotBlank(message = "The city is required.")
      private String city;
    
      @NotBlank(message = "The Zip code is required.")
      @Pattern(regexp = "^\\d{1,5}$", flags = { Flag.CASE_INSENSITIVE, Flag.MULTILINE }, message = "The Zip code is invalid.")
      private String zipCode;
    
      @NotBlank(message = "The street name is required.")
      private String street;
    
      private String state;
    
      public Address toAddress() {
        return new Address()
            .setCountry(country)
            .setCity(city)
            .setZipCode(zipCode)
            .setStreet(street)
            .setState(state);
      }
    }
    
    RegisterUserdTo.ジャバ
    package com.tericcabrel.hotel.models.dtos;
    
    import com.tericcabrel.hotel.models.User;
    import java.util.Date;
    import javax.validation.Valid;
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotEmpty;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Past;
    import javax.validation.constraints.Pattern.Flag;
    import javax.validation.constraints.Size;
    import lombok.Data;
    
    @Data
    public class RegisterUserDto {
      @NotEmpty(message = "The full name is required.")
      @Size(min = 2, max = 100, message = "The length of full name must be between 2 and 100 characters.")
      private String fullName;
    
      @NotEmpty(message = "The email address is required.")
      @Email(message = "The email address is invalid.", flags = { Flag.CASE_INSENSITIVE })
      private String email;
    
      @NotNull(message = "The date of birth is required.")
      @Past(message = "The date of birth must be in the past.")
      private Date dateOfBirth;
    
      @NotEmpty(message = "The gender is required.")
      private String gender;
    
      @Valid
      @NotNull(message = "The address is required.")
      private AddressDto address;
    
      public User toUser() {
        return new User()
            .setName(fullName)
            .setEmail(email.toLowerCase())
            .setBirthDate(dateOfBirth)
            .setGender(gender)
            .setAddress(address.toAddress());
      }
    }
    

    リンク 登録テストのルートを作成する


    新しいユーザを登録する責任があるエンドポイントを作成しましょう.コントローラと呼ばれるパッケージを作成し、UserControllerというコントローラを作成します.ジャバ.以下のコードを追加します.
    package com.tericcabrel.hotel.controllers;
    
    /*      CLASSES IMPORT HERE        */
    
    @RequestMapping(value = "/user")
    @RestController
    public class UserController {
      private final UserService userService;
    
      public UserController(UserService userService) {
        this.userService = userService;
      }
    
      @PostMapping("/register")
      public ResponseEntity<User> registerUser(@Valid @RequestBody RegisterUserDto registerUserDto) {
        User createdUser = userService.create(registerUserDto.toUser());
    
        return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
      }
    }
    
    上のコードの最も重要な部分は@ valid注釈の使用です.
    springが@ validで注釈をつけたとき、自動的に引数を検証し、検証が失敗した場合に例外をスローします.
    アプリケーションを実行し、起動時にエラーが発生しないことを確認します.

    郵便配達人で試験する


    当社のアプリを起動;オープン郵便局は、nullにすべての入力を要求を送信し、結果を参照してください.

    私たちは、「悪い要求」というメッセージでHTTPステータス400を得ました🙁. 何が起こったかを確認するためにアプリケーションコンソールをチェックしましょう.

    ご覧のように、MethodArgumentNotValideXception型の例外がスローされましたが、例外がどこにも捕捉されていないため、応答は不良リクエストに対してフォールバックします.

    妥当性検査エラー例外


    Springは@ ResourceAdviderと呼ばれる@ componentの特殊な注釈を提供します.これは、1つのグローバルな単一のコンポーネントで@ request mappingで注釈されたメソッドによってスローされた例外を扱うことができます.
    例外と呼ばれるパッケージを作成し、GlobalExceptionHandlerというファイルを作成します.ジャバ.以下のコードを追加します.
    package com.tericcabrel.hotel.exceptions;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    import org.springframework.context.support.DefaultMessageSourceResolvable;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.context.request.WebRequest;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
    
    @ControllerAdvice
    public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
      @Override
      protected ResponseEntity<Object> handleMethodArgumentNotValid(
          MethodArgumentNotValidException ex, HttpHeaders headers,
          HttpStatus status, WebRequest request) {
    
        Map<String, List<String>> body = new HashMap<>();
    
        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(DefaultMessageSourceResolvable::getDefaultMessage)
            .collect(Collectors.toList());
    
        body.put("errors", errors);
    
        return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
      }
    }
    
    アプリを起動し、郵便配達人に電話をかける.

    アルファベット文字(郵便番号フランス語郵便番号)で機能と郵便番号のテスト誕生日.

    予約を作成する


    createreservationdtoを作成して同じようにしましょう.次に、次のコードを追加します.
    package com.tericcabrel.hotel.models.dtos;
    
    import com.tericcabrel.hotel.models.Reservation;
    import java.util.Date;
    import javax.validation.constraints.FutureOrPresent;
    import javax.validation.constraints.Max;
    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotEmpty;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Positive;
    import lombok.Data;
    
    @Data
    public class CreateReservationDto {
      @NotNull(message = "The number of bags is required.")
      @Min(value = 1, message = "The number of bags must be greater than 0")
      @Max(value = 3, message = "The number of bags must be greater than 3")
      private int bagsCount;
    
      @NotNull(message = "The departure date is required.")
      @FutureOrPresent(message = "The departure date must be today or in the future.")
      private Date departureDate;
    
      @NotNull(message = "The arrival date is required.")
      @FutureOrPresent(message = "The arrival date must be today or in the future.")
      private Date arrivalDate;
    
      @NotNull(message = "The room's number is required.")
      @Positive(message = "The room's number must be greater than 0")
      private int roomNumber;
    
      @NotNull(message = "The extras is required.")
      @NotEmpty(message = "The extras must have at least one item.")
      private String[] extras;
    
      @NotNull(message = "The user's Id is required.")
      @Positive(message = "The user's Id must be greater than 0")
      private int userId;
    
      private String note;
    
      public Reservation toReservation() {
        return new Reservation()
            .setBagsCount(bagsCount)
            .setDepartureDate(departureDate)
            .setArrivalDate(arrivalDate)
            .setRoomNumber(roomNumber)
            .setExtras(extras)
            .setNote(note);
      }
    }
    
    ReservationControllerのコードを見つけます.ソースコードリポジトリのJava.

    郵便配達人で試験する



    リクエストパラメータの検証


    さて、生成されたユニークなコードを通して予約を取得します.

    エンドポイントは次のようになります.予約コードがユーザーによって提供されるので、提供されたコードがこの良い形式でないなら、不必要なデータベース呼び出し原因を作るのを避けるために、我々はそれを検証する必要があります.
    Springでルートを作成するとき、入力を検証するために注釈規則を追加することが可能です.この場合、正規表現を適用して予約コードの形式を検証します.
    ReservationController内で.次のコードを追加します
    @GetMapping("/{code}")
    public ResponseEntity<Reservation> oneReservation(@Pattern(regexp = "^RSV(-\\d{4,}){2}$") @PathVariable String code)
          throws ResourceNotFoundException {
        Optional<Reservation> optionalReservation = reservationService.findByCode(code);
    
        if (optionalReservation.isEmpty()) {
          throw new ResourceNotFoundException("No reservation found with the code: " + code);
        }
    
        return new ResponseEntity<>(optionalReservation.get(), HttpStatus.OK);
    }
    
    アプリケーションを実行し、悪いコードを使用してルートを呼び出すと、コントローラが注釈規則を使用してパラメーターを検証するように指示する必要があります.これを実行するには、注釈付き@ validatedを使用します.
    @Validated
    @RequestMapping(value = "/reservations")
    @RestController
    public class ReservationController {
        // code here
    }
    
    今すぐアプリケーションを実行し、悪い予約コードをテストします.
    おお!アプリケーションは内部のサーバエラーをスローし、コンソールは以下のようになります.

    妥当性検査は予想通り失敗しましたが、型制約違反の例外はスローされました.アプリケーションがキャッチされていないので、内部サーバーエラーが返されます.GlobalExceptionHandlerを更新します.この例外をキャッチするJava
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<?> constraintViolationException(ConstraintViolationException ex, WebRequest request) {
        List<String> errors = new ArrayList<>();
    
        ex.getConstraintViolations().forEach(cv -> errors.add(cv.getMessage()));
    
        Map<String, List<String>> result = new HashMap<>();
        result.put("errors", errors);
    
        return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
    }
    
    アプリケーションとテストを起動します.今、我々は明確なメッセージでエラーを得た.

    結論


    私たちはこのチュートリアルの終わりに到達しました、そして、現在、我々は我々のバックエンドをユーザーから来ている不良入力により弾力的にした堅実な合法化を実装することができます.チュートリアル全体で定義済みの検証規則を使用しましたが、専用のユースケースのカスタム検証規則を作成することもできます.
    独自の妥当性検査ルールを作成する方法を参照するには、 をチェックします.
    このチュートリアルのコードをthis linkで見つけます.