Bài viết gốc xem ở đây.

1. Tổng quát

Khi nói đến việc xác thực thông tin đầu vào của người dùng, Spring Boot cung cấp hỗ trợ mạnh mẽ cho nhiệm vụ phổ biến nhưng quan trọng là nó có thể dùng ngay lập tức.

Mặc dù Spring Boot hỗ trợ tích hợp xác thực tuỳ biến, tiêu chuẩn de-facto để thực hiện xác thực là Hibernate Validator, sử dụng framework Bean Validation để triển khai điều này.

Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách xác thực các đối tượng domain trong Spring Boot.

2. Các Dependency Maven

Trong phần này, chúng ta sẽ học cách xác thực các đối tượng domain trong Spring Boot bằng các xây dựng một controller REST cơ bản.

Controller đầu tiên sẽ lấy một đối tượng domain, sau đó nó sẽ xác thực nó với Hibernate Validator, và cuối cùng nó sẽ lưu nó (persist) vào bộ nhớ tạm H2 database.

Các dependency của dự án này khá là tiêu chuẩn:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> 

<dependency> 
    <groupId>com.h2database</groupId> 
    <artifactId>h2</artifactId>
    <version>1.4.197</version> 
    <scope>runtime</scope>
</dependency>

Như đã được trình bày ở bên trên, chúng ta đưa spring-boot-starter-web vào file pom.xml bởi vì chúng ta sẽ cần nó để tạo controller REST. Ngoài ra, cần đảm bảo đã dùng các phiên bản mới nhất của spring-boot-starter-jpaH2 database trong Maven Central.

Khởi động với Boot 2.3, chúng ta cần thêm dependency spring-boot-starter-validation:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-validation</artifactId> 
</dependency>

3. Class Domain cơ bản

Với các dependency đã được thêm vào, tiếp theo chúng ta cần định nghĩa class entity JPA, nó có vai trò chỉ định model của các user.

Hãy xem class này:

 @Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @NotBlank(message = "Name is mandatory")
    private String name;

    @NotBlank(message = "Email is mandatory")
    private String email;

    // standard constructors / setters / getters / toString
}

Đây là triển khai của class entity User khá sơ sài, nhưng nó cũng đã trình bày cách sử dụng các ràng buộc của Bean Validation để kiểm soát các trường name email.

Để đơn giản, chúng ta sẽ kiểm soát các trường chỉ với ràng buộc @NotBlank. Ngoài ra, chúng ta cũng thiết lập thông báo lỗi với thuộc tính message.

Do đó, khi Spring Boot xác thực các lớp, các trường được kiểm soát bắt buộc không được để trống và độ dài đã cắt của nó phải lớn hơn 0.

Ngoài ra, Bean Validation cung cấp nhiều công cụ kiểm soát  bên cạnh @NotBlank. Nó cho phép chúng ta kết hợp với các công cụ kiểm soát khác nhau để phù hợp với nhu cầu riêng của mỗi class. Để biết thêm chi tiết, hãy đọc tài liệu chính thức của bean validation.

Bây giờ chúng ta sẽ sử dụng Spring Data JPA để lớp các user vào trong bộ nhớ tạm của H2 database, chúng ta sẽ định nghĩa một interface repository đơn giản có các chức năng CRUD cơ bản trong đối tượng User:

@Repository
public interface UserRepository extends CrudRepository<User, Long> {}

4. Triển khai một Controller REST

Dĩ nhiên, chúng ta cần triển khai một lớp cho phép chúng ta lấy giá trị của các trường bị kiểm soát trong đối tượng User của trong ta.

Vì vậy, chúng ta có để xác thực chúng và thực hiện một vài nhiệm vụ khác, phụ thuộc vào kết quả xác thực.

Spring Boot làm cho quá trình có vẻ phức tạp này thực sự đơn giản thông qua việc triển khai một controller REST.

Hãy xem xét controller REST đã được triển khai sau đây:

@RestController
public class UserController {

    @PostMapping("/users")
    ResponseEntity<String> addUser(@Valid @RequestBody User user) {
        // persisting the user
        return ResponseEntity.ok("User is valid");
    }

    // standard constructors / other methods

}

 

Trong một Spring REST, việc triển khai phương thức addUser() khá là tiêu chuẩn.

Dĩ nhiên, phần liên quan nhất là việc sử dụng annotation @Valid.

Khi Spring Boot tìm một đối số annotation với @Valid, nó tự động khởi động triển khai JSR 380 mặc định - Hibernate Validator - và xác thực đối số.

Khi đối số mục tiêu bị lỗi khi xác thực, Spring Boot sẽ ném ra exception MethodArgumentNotValidException

5. Annotation @ExceptionHandler

Mặc dù việc Spring Boot xác thực tự động đối tượng User được truyền vào phương thức addUser() thực sự tiện dụng, điều còn thiếu của quá trình này là cách chúng ta xử lý kết quả xác thực.

Annotation @ExceptionHandler cho phép chúng ta xử lý các loại ngoại lệ được chỉ định thông qua một phương thức duy nhất.

Do đó, chúng ta có thể sử dụng nó để xử lý khi xác thực bị lỗi:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
  MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getAllErrors().forEach((error) -> {
        String fieldName = ((FieldError) error).getField();
        String errorMessage = error.getDefaultMessage();
        errors.put(fieldName, errorMessage);
    });
    return errors;
}

Chúng ta chỉ định ngoại lệ MethodArgumentNotValidExceptionngoại lệ được xử lý. Kết quả là, Spring Boot sẽ gọi phương thức này khi đối tượng User được chỉ định không hợp lệ.

Phương thức lưu trữ tên và thông báo lỗi sau khi xác thực của từng trường không hợp lệ trong Map. Tiếp theo, nó gửi Map trở về client dưới dạng JSON để xử lý thêm.

Nói một cách đơn giản, controller REST cho phép chúng ta dễ dàng xử lý các yêu cầu tới các đầu cuối khác nhau, xác thức đối tượng User và gửi phản hồi dưới dạng JSON.

Thiết kế này linh hoạt đủ để xử lý phản hồi của controller thông qua một số tầng web, từ Thymeleaf đến các framework đầy đủ các tính năng như Angular.

6. Test controller REST

Chúng ta có thể dễ dàng kiểm tra chức năng của controller REST với integration test.

Bắt đầu mocking/autowiring triển khai interface UserRepository cùng với UserController và đối tượng MockMvc:

RunWith(SpringRunner.class) 
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {

    @MockBean
    private UserRepository userRepository;

    @Autowired
    UserController userController;

    @Autowired
    private MockMvc mockMvc;

    //...
 
}

 

Vì chúng ta chỉ kiểm tra lớp web, chúng ta sử dụng annotation @WebMvcTest. Nó cho phép chúng ta dễ kiểm tra yêu cầu và phản hồi sử dụng các phương thức tĩnh được triển khai bởi các class MockMvcRequestBuilders MockMvcResultMatchers.

Bây giờ hãy cùng kiểm tra phương thức addUser() với đối tượng User một hợp lệ và một không hợp lệ được thông qua request body:
 

@Test
public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
    MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));
    String user = "{\"name\": \"bob\", \"email\" : \"bob@domain.com\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/users")
      .content(user)
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.content()
        .contentType(textPlainUtf8));
}

@Test
public void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception {
    String user = "{\"name\": \"\", \"email\" : \"bob@domain.com\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/users")
      .content(user)
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isBadRequest())
      .andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("Name is mandatory")))
      .andExpect(MockMvcResultMatchers.content()
        .contentType(MediaType.APPLICATION_JSON_UTF8));
    }
}

Ngoài ra, chúng ta có thể kiểm tra API controller REST sử dụng một chương trình kiểm tra life cycle API miễn phí như Postman.

7. Chạy chương trình ví dụ

Cuối cùng, chúng ta có thể chạy project ví dụ của chúng ta với phương thức tiêu chuẩn main():

@SpringBootApplication
public class Application {    

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner run(UserRepository userRepository) throws Exception {
        return (String[] args) -> {
            User user1 = new User("Bob", "bob@domain.com");
            User user2 = new User("Jenny", "jenny@domain.com");
            userRepository.save(user1);
            userRepository.save(user2);
            userRepository.findAll().forEach(System.out::println);
        };
    }
}

Như mong đợi, chúng ta sẽ thấy vào đối tượng User được in ra trong console.

 

Một yêu cầu POST tới http://localhost:8080/users với một đối tượng User hợp lệ sẽ trả về chuỗi “User is valid”.

Tương tự như vậy, một yêu cầu POST với một đối tượng User không có giá trị tênemail sẽ trả về phản hồi như sau:
 

{
  "name":"Name is mandatory",
  "email":"Email is mandatory"
}

8. Tổng kết

Trong bài viết này, chúng ta đã học các xác thực cơ bản trong Spring Boot.

Như thường lệ, tất cả code mẫu sẽ có trong GitHub này.