Validation in Spring
Trong bài viết này, chúng ta sẽ đề cập tới chi tiết việc xác thực sử dụng Spring. Điều quan trọng là xác thực dữ liệu đầu vào của REST web service điều này có thể dẫn đến vấn đề trong quá trình triển khai và xử lý logic.

Giới thiệu.

Người viết: Ngô Công Tuyền - Học viên lớp Java 09
Email: tuyenemotion@gmail.com

Khi xây dựng một REST API, chúng ta mong đợi RESTful service có cấu trúc hoặc định dạng nhất định. Chúng ta kỳ vọng rằng REST API request sẽ theo cấu trúc và ràng buộc đó. Chúng ta không thể đảm bảo API đó sẽ luôn tuân theo kiến trúc và ràng buộc. Và làm sao để xử lý nếu API không theo những ràng buộc đó?
Chúng ta cần xem xét những câu hỏi dưới đây khi thiết kế REST API:

  • Làm sao để xác thực dữ liệu REST API?
  • Gửi thông báo gì để phản hồi nếu thông tin xác thực sai?
    Một trong những nguyên tắc cơ bản của RESTful service là nghĩ về người dùng khi thiết kế REST web services. Chúng ta cần lưu ý những điều dưới đây để xác thực REST API trong Spring.
  • REST API nên trả về một thông báo rõ ràng rằng người dùng đã sai ở đâu.
  • API nên cố gắng cung cấp thông tin về việc người dùng có thể làm gì để sửa lỗi.
  • Trạng thái phản hồi được đưa ra rõ ràng.
    Spring MVC sẽ cung cấp một số lựa chọn cho việc xác thực REST API. Chúng ta sẽ xem xét một số lựa chọn đó trong bài viết này. Tùy thuộc vào những yêu cầu cụ thể để chúng ta xác định đâu là lựa chọn phù hợp với những trường hợp khác nhau.

1. Xác thực đơn giản bằng Spring MVC.

Nếu chúng ta sử dụng @RequestParam hoặc @PathVariable, Spring sẽ cung cấp một hỗ trợ vượt trội để xác thực nó. Dưới đây là ví dụ đơn giản về cách xác thực REST data sử dụng Spring:

@GetMapping("/greeting")
public Greeting sayHello(@RequestParam(value = "name") String name){
    return new Greeting(1, "Hello "+ name);
}

Nó là sự xác thực đơn giản nhất được cung cấp bởi Spring MVC. Nó sẽ xác thực yêu cầu đầu vào. Sau đó Spring sẽ gửi tin nhắn chi tiết cho trở lại cho người dùng trong trường hợp thiếu dữ liệu cần thiết trong REST API.
Nếu chúng ta gọi API thiếu trường name trong request: “/greeting”. Spring sẽ gửi phản hồi chi tiết lỗi trở lại người dùng.

{
"timestamp": 1517983914615,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter 'name' is not present",
"path": "/greeting"
}

2. Sử dụng Bean Validation (JSR303).

Trên thực tế, REST API sẽ không chỉ là những phương thức đơn giản với một vài trường. REST API doanh nghiệp bao gồm những mô hình phức tạp và yêu cầu có năng lực xác thực dữ liệu REST API.
Spring MVC cung cấp bản dựng hỗ trợ cho JSR303 (Bean Validation). Bean Validation đi kèm với một danh sách validation có sẵn đi kèm với khả năng tạo thêm custom validation.
Với sự hỗ trợ của Spring Bean Validation, chúng ta cần đánh đấu các DTO với annotations bắt buộc và cấu trúc hỗ trợ xác thực để xác thực dữ liệu đầu vào trước khi đi đến business layer. (tầng xử lý logic).
Để hiểu thêm về danh sách tổ hợp trong quá trình xác thực, hãy tìm hiểu về Hibernate Validator.
Dưới đây là ví dụ về lớp DTO:

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CustomerDTO {
    @NotNull(message = "Please provide first Name")
    private String firstName;

    @NotNull(message = "Please provide last Name")
    private String lastName;

    @NotNull
    @Min(value = 18, message = "Age should be equal or more than 18")
    private int age;

    @Email(message = "Please provide valid email address")
    private String email;
    
}

Hãy tìm hiểu về những thứ chúng ta đang cố gắng xác thực trong lớp DTO này

  • Không chấp nhận giá trị null cho trường fisrtName và lastName (chúng ta cũng có thể sử dụng @NotEmty cho trường này)
  • Yêu cầu giá trị tối thiểu là 18 cho trường age (>= 18 tuổi).
  • Xác thực email của người dùng.
    Bean Validation không chỉ xác thực thông tin người dùng mà còn gửi phản hồi lại khi thông tin xác thực thất bại.
    Chúng ta sử dụng thông báo được tạo sẵn trong class, nhưng Spring còn cung cấp các thuộc tính cục bộ cho các thông báo cục bộ.
    Tạo ra một POST API để kiểm thử:
@PostMapping("/customer")
public ResponseEntity<?> createCustomer(@Valid @RequestBody CustomerDTO customer){

    HttpHeaders responseHeader = new HttpHeaders();
    return new ResponseEntity<>(customer, responseHeader, HttpStatus.CREATED);
}

Chúng ta goi POST API với boby dưới đây:
{“firstName”:“Ngo”,“lastName”:null,“age”:“24”,“email”:“tuyen@gmail.comcom”}
Kết quả trả về là:

{
    "timestamp": "2022-11-27T14:39:25.902+00:00",
    "status": 400,
    "error": "Bad Request",
    "trace": "org.springframework.web.bind.MethodArgumentNotValidException: “
    "message": "Validation failed for object='customerDTO'. Error count: 1",
    "errors": [{"codes": [
                "NotNull.customerDTO.lastName",
                "NotNull.lastName",
                "NotNull.java.lang.String",
                "NotNull"],
            "arguments": [],
            "defaultMessage": "Please provide last Name",
            "objectName": "customerDTO",
            "field": "lastName",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        }
    ],
    "path": "/customer"
}

Khi mà nhập đúng tất cả các trường, kết quả trả về là:

{
    "firstName": "Ngo",
    "lastName": "TuyenTuyen",
    "age": 24,
    "email": "tuyen@gmail.comcom"
}

3. Custom Spring Validator.

JSR 303 bean validation cung cấp một số trình xác thực có sẵn. Với những ứng dụng doanh nghiệp, chúng ta sẽ rơi vào tình huống các trình xác thực có sẵn không đáp ứng hết các yêu cầu. Spring cung cấp cho chúng ta những công cụ cần thiết cho việc tạo ra những trình xác thực riêng và sử dụng chúng. Bạn có thể tìm hiểu thêm về Spring MVC Custom Validator.
Vấn đề thường xuyên xảy ra khi sử dụng cùng một loại đối tượng, nhưng mục đích sử dụng chúng là khác nhau, vị dụ:

  • Nếu cần tạo 1 khách hàng mới, trường id của họ có thể là null: id = null;
  • Khi cập nhật tài khoản của họ, chúng ta cần thông tin về id của họ: id <> null ;
    Trong những trường hợp như vậy, ta không cần tạo 2 đối tượng khác nhau mà có thể sử dụng tính năng Grouping constraints được cung cấp bởi JSR 303.

Tổng kết:

Trong bài viết này, chúng ta thảo luận về các lựa chọn khác nhau trong việc xác thực REST API bằng Spring. Xác thực dữ liệu là cần thiết cho Spring REST API. Việc xác thực dữ liệu sẽ trả về những dữ liệu giá trị và cung cấp thêm sự bảo mật cho dữ liệu.
Link tham khảo:
Link bài viết
Link code tham khảo: