[Spring] 10 habilidades de verificação de parâmetros do SpringBoot

Adicione uma descrição da imagem

prefácio

A verificação dos parâmetros é muito importante e é uma parte indispensável do processo normal de desenvolvimento, mas acho que muitos colegas de back-end serão preguiçosos e simplesmente nada mal, o que pode trazer sérios danos à estabilidade e segurança do sistema. Então, como fazer um bom trabalho de verificação de parâmetros em aplicações Spring Boot, este artigo traz 10 dicas, quantas você conhece?

1. Use anotações de validação

Spring Boot fornece anotações de validação integradas que ajudam na validação simples e rápida de campos de entrada, como verificação de campos nulos ou vazios, aplicação de limites de comprimento, validação de padrões usando expressões regulares e validação de endereços de e-mail.

Algumas das anotações de validação mais comumente usadas incluem:

@NotNull: O campo especificado não pode estar vazio.

@NotEmpty: especifica que o campo da lista não pode ficar vazio.

@NotBlank: especifica que o campo string não deve estar vazio ou conter apenas espaços em branco.

@Min e @Max: especifica os valores mínimo e máximo para campos numéricos.

@Pattern: especifica o padrão de expressão regular ao qual o campo de string deve corresponder.

@Email: especifica que o campo de string deve ser um endereço de e-mail válido.

    具体用法参考下面例子:

classe pública Usuário { @NotNull private ID longo;

@NotBlank
@Size(min = 2, max = 50)
private String firstName;

@NotBlank
@Size(min = 2, max = 50)
private String lastName;

@Email
private String email;

@NotNull
@Min(18)
@Max(99)
private Integer age;

@NotEmpty
private List<String> hobbies;

@Pattern(regexp = "[A-Z]{2}\d{4}")
private String employeeId;

2 Use anotações de validação personalizadas

Embora as anotações de validação integradas do Spring Boot sejam úteis, elas podem não cobrir todos os casos. Se você tiver cenários especiais de validação de parâmetros, poderá usar a estrutura de validação JSR 303 do Spring para criar anotações de validação personalizadas. As anotações personalizadas podem tornar sua lógica de validação mais reutilizável e fácil de manter.

Digamos que temos um aplicativo onde os usuários podem criar postagens. Cada postagem deve ter um título e um corpo, e os títulos devem ser exclusivos em todas as postagens. Embora o Spring Boot forneça anotações de validação integradas para verificar se os campos estão vazios, ele não fornece anotações de validação integradas para verificar a exclusividade. Neste caso, podemos criar uma anotação de validação personalizada para lidar com este caso.

Primeiro, criamos a anotação de restrição personalizada UniqueTitle:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueTitleValidator.class)
public @interface UniqueTitle { String message() default “O título deve ser único”;

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}
A seguir, criamos uma interface PostRepository com o objetivo de recuperar posts do banco de dados:

public interface PostRepository extends JpaRepository<Post, Long> { Post findByTitle(String title); } Então precisamos definir a classe validadora UniqueTitleValidator da seguinte forma:


@Component
classe pública UniqueTitleValidator implementa ConstraintValidator<UniqueTitle, String> {

@Autowired
private PostRepository postRepository;

@Override
public boolean isValid(String title, ConstraintValidatorContext context) {
    if (title == null) {
        return true;
    }
    return Objects.isNull(postRepository.findByTitle(title));
}

}
UniqueTitleValidator implementa a interface ConstraintValidator, que possui dois tipos genéricos: o primeiro é a anotação personalizada UniqueTitle e o segundo é o tipo de campo que está sendo validado (String neste caso). Também conectamos automaticamente a classe PostRepository para recuperar postagens do banco de dados.

O método isValid() verifica se o título é nulo ou único consultando o PostRepository. Se o título for nulo ou exclusivo, a validação será bem-sucedida e verdadeiro será retornado.

Com nossa anotação de validação personalizada e classe validadora definidas, agora podemos usá-la para validar títulos de postagens em nosso aplicativo Spring Boot:

public class Post { @UniqueTitle private String título;

@NotNull
private String body;

}
Aplicamos a anotação @UniqueTitle à variável title na classe Post. Isso aciona a lógica de validação definida na classe UniqueTitleValidator quando este campo é validado.

3 Valide no lado do servidor

Além da validação front-end ou do lado do cliente, a validação de entrada do lado do servidor é crucial. Ele garante que quaisquer dados maliciosos ou malformados sejam capturados antes de serem processados ​​ou armazenados, o que é fundamental para a segurança e estabilidade do aplicativo.

Digamos que temos um endpoint REST que permite aos usuários criar novas contas. O endpoint espera um corpo de solicitação JSON contendo o nome de usuário e a senha do usuário. Para garantir que a entrada seja válida, podemos criar uma classe DTO (Data Transfer Object) e aplicar anotações de validação aos seus campos:

classe pública UserDTO {

@NotBlank
private String username;

@NotBlank
private String password;

}
Usamos a anotação @NotBlank para garantir que os campos de nome de usuário e senha não estejam vazios ou nulos.

A seguir, podemos criar um método controlador para lidar com a solicitação HTTP POST e validar a entrada antes de criar um novo usuário:

@RestController
@RequestMapping(“/users”)
@Validated
public class UserController { @Autowired private UserService userService;

@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO userDto) {
    userService.createUser(userDto);
    return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
}

}
Usamos a anotação @Validated do Spring para ativar a validação em nível de método e também aplicamos a anotação @Valid ao parâmetro userDto para acionar o processo de validação.

4 Forneça mensagens de erro significativas

Quando a validação falha, uma mensagem de erro clara e concisa deve ser fornecida descrevendo o que deu errado e como corrigi-lo.

Aqui está um exemplo, se tivermos uma API RESTful que permite aos usuários criar novos usuários. Queremos ter certeza de que os campos de nome e endereço de e-mail não estão vazios, a idade está entre 18 e 99 anos e, além desses campos, também fornecemos uma mensagem de erro clara ou “email mail”.

Para fazer isso, podemos definir uma classe de modelo User com as anotações de validação necessárias como segue:

classe pública Usuário {

@NotBlank(message = "用户名不能为空")
private String name;

@NotBlank(message = "Email不能为空")
@Email(message = "无效的Emaild地址")
private String email;

@NotNull(message = "年龄不能为空")
@Min(value = 18, message = "年龄必须大于18")
@Max(value = 99, message = "年龄必须小于99")
private Integer age;

}
Fornecemos mensagens de erro personalizadas para cada anotação de validação usando o atributo message.

A seguir, em nosso controlador Spring, podemos lidar com o envio do formulário e validar a entrada do usuário usando a anotação @Valid:

@RestController
@RequestMapping(“/usuários”)
public class UserController { @Autowired private UserService userService;

@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody User user, BindingResult result) {
    if (result.hasErrors()) {
        List<String> errorMessages = result.getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        return ResponseEntity.badRequest().body(errorMessages.toString());
    }

    // save the user to the database using UserService
    userService.saveUser(user);

    return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
}

}

Usamos a anotação @Valid para acionar a validação do objeto User e um objeto BindingResult para capturar quaisquer erros de validação.

5 Usando i18n para mensagens de erro

Se seu aplicativo suportar vários idiomas, você deverá usar a internacionalização (i18n) para exibir mensagens de erro no idioma preferencial do usuário.

Aqui está um exemplo de uso de i18n para lidar com mensagens de erro em um aplicativo Spring Boot

Primeiro, crie um arquivo messages.properties contendo mensagens de erro padrão no diretório de recursos

mensagens.properties

user.name.required=O nome é obrigatório.
user.email.invalid=Formato de e-mail inválido.
user.age.invalid=A idade deve ser um número entre 18 e 99.
Em seguida, crie um messages_xx.properties para cada arquivo de idioma suportado, por por exemplo, messages_zh_CN.properties em chinês.

user.name.required=O nome não pode ficar vazio
user.email.invalid=Formato de e-mail inválido
user.age.invalid=A idade deve estar entre 18 e 99 anos.
Em seguida, atualize seus comentários de validação para usar informações erradas localizadas

classe pública Usuário { @NotNull(message = “{user.id.required}”) private Long id;

@NotBlank(message = "{user.name.required}")
private String name;

@Email(message = "{user.email.invalid}")
private String email;

@NotNull(message = "{user.age.required}")
@Min(value = 18, message = "{user.age.invalid}")
@Max(value = 99, message = "{user.age.invalid}")
private Integer age;

}

Por fim, configure o bean MessageSource no arquivo de configuração do Spring para carregar o arquivo de mensagens i18n

@Configuration
public class AppConfig { @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename(“mensagens”); mensagemSource.setDefaultEncoding(“UTF-8”); retornar mensagemFonte; }






@Bean
public LocalValidatorFactoryBean validator() {
    LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
    validatorFactoryBean.setValidationMessageSource(messageSource());
    return validatorFactoryBean;
}

}

Quando ocorre um erro de validação, a mensagem de erro será exibida no idioma preferido do usuário com base no cabeçalho "Accept-Language" enviado com a solicitação.

6 Use autenticação de grupo

Os grupos de validação são um recurso poderoso da estrutura de validação Spring Boot que permite aplicar regras de validação condicional com base em outros valores de entrada ou estado do aplicativo.

Agora no caso de uma classe User com três campos: firstName, lastName e email. Queremos ter certeza de que, se o campo email estiver vazio, os campos firstName ou lastName não devem estar vazios. Caso contrário, todos os três campos deverão ser validados normalmente.

Para fazer isso, definiremos dois grupos de validação: EmailNotEmpty e Default. O grupo EmailNotEmpty conterá regras de validação para quando o campo de email não estiver vazio, enquanto o grupo Padrão conterá regras de validação normais para todos os três campos.

Crie uma classe User com um grupo de autenticação

public class Usuário { @NotBlank(groups = Default.class) private String firstName;

@NotBlank(groups = Default.class)
private String lastName;

@Email(groups = EmailNotEmpty.class)
private String email;

// getters and setters omitted for brevity
public interface EmailNotEmpty {}
public interface Default {}

}
Observe que definimos duas interfaces na classe User, EmailNotEmpty e Default. Eles servirão como nossos grupos de validação.

A seguir, atualizamos o Controller para usar esses grupos de autenticação

@RestController
@RequestMapping(“/users”)
@Validated
public class UserController { public ResponseEntity createUser( @Validated({org.example.model.ex6.User.EmailNotEmpty.class}) @RequestBody User userWithEmail, @Validated({User. Default.class}) @RequestBody User userWithoutEmail) { // Cria o usuário e retorna uma resposta de sucesso




}

}
Adicionamos a anotação @Validated ao nosso controlador, indicando que queremos usar o grupo de validação. Também atualizamos o método createUser para receber dois objetos User como entrada, um para usar quando o campo email não estiver vazio e outro para usar quando estiver.

A anotação @Validated é usada para especificar qual grupo de validação aplicar a cada objeto Usuário. Para o parâmetro userWithEmail, especificamos o grupo EmailNotEmpty e para o parâmetro userWithoutEmail, especificamos o grupo Default.

Com essas alterações, a classe ‘Usuário’ passará a ser validada de forma diferente dependendo se o campo ‘E-mail’ estiver vazio ou não. Se estiverem vazios, os campos firstName ou lastName não deverão estar vazios. Caso contrário, todos os três campos serão validados normalmente.

7 Use validação entre domínios para lógica complexa

Se precisar validar regras de entrada complexas que abrangem vários campos, você poderá usar a validação entre campos para manter sua lógica de validação organizada e de fácil manutenção. A validação entre campos garante que todos os valores de entrada sejam válidos e consistentes entre si, evitando comportamentos inesperados.

Digamos que temos um formulário onde os usuários podem inserir uma data de início e término para uma tarefa e queremos garantir que a data de término não seja anterior à data de início. Podemos conseguir isso usando validação entre domínios.

Primeiro, definimos uma anotação de validação personalizada EndDateAfterStartDate:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EndDateAfterStartDateValidator.class)
public @interface EndDateAfterStartDate { String message() padrão “A data de término deve ser posterior à data de início”; Classe<?>[] grupos() padrão {}; Classe<? estende Payload>[] payload() padrão {}; } EndDateAfterStartDateValidator:




classe pública EndDateAfterStartDateValidator implements ConstraintValidator<EndDateAfterStartDate, TaskForm> { @Override public boolean isValid(TaskForm taskForm, ConstraintValidatorContext context) { if (taskForm.getStartDate() == null || taskForm.getEndDate() == null) { return true; }




    return taskForm.getEndDate().isAfter(taskForm.getStartDate());
}

}
Finalmente, aplicamos a anotação EndDateAfterStartDate ao nosso objeto de formulário TaskForm:

@EndDateAfterStartDate
public class TaskForm { @NotNull @DateTimeFormat(pattern = “yyyy-MM-dd”) private LocalDate startDate;


@NotNull
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate endDate;

}
Agora, quando o usuário enviar o formulário, a estrutura de validação verificará automaticamente se a data de término é posterior à data de início e, caso contrário, fornecerá uma mensagem de erro significativa.

8 Use tratamento de exceções para erros de validação

Erros de validação podem ser detectados e tratados uniformemente usando ExceptionHandler.

Aqui está um exemplo de como usar o tratamento de exceções no Spring Boot para lidar com erros de validação:

@RestControllerAdvice
classe pública RestExceptionHandler estende ResponseEntityExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                              HttpHeaders headers, HttpStatus status,
                                                              WebRequest request) {
    Map<String, Object> body = new LinkedHashMap<>();
    body.put("timestamp", LocalDateTime.now());
    body.put("status", status.value());

    // Get all errors
    List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(x -> x.getDefaultMessage())
            .collect(Collectors.toList());

    body.put("errors", errors);

    return new ResponseEntity<>(body, headers, status);
}

}

    在这里,我们创建了一个用 @RestControllerAdvice 注解的 RestExceptionHandler 类来处理我们的 REST API 抛出的异常。然后我们创建一个用@ExceptionHandler注解的方法来处理在验证失败时抛出的 MethodArgumentNotValidException。

    在处理程序方法中,我们创建了一个 Map 对象来保存错误响应的详细信息,包括时间戳、HTTP 状态代码和错误消息列表。我们使用 MethodArgumentNotValidException 对象的 getBindingResult() 方法获取所有验证错误并将它们添加到错误消息列表中。

    最后,我们返回一个包含错误响应详细信息的ResponseEntity对象,包括作为响应主体的错误消息列表、HTTP 标头和 HTTP 状态代码。

    有了这个异常处理代码,我们的 REST API 抛出的任何验证错误都将被捕获并以结构化和有意义的格式返回给用户,从而更容易理解和解决问题。

9 Teste sua lógica de validação

Você precisa escrever testes unitários para sua lógica de validação para ajudar a garantir que ela funcione corretamente.

@DataJpaTest
classe pública UserValidationTest {

@Autowired
private TestEntityManager entityManager;

@Autowired
private Validator validator;

@Test
public void testValidation() {
    User user = new User();
    user.setFirstName("John");
    user.setLastName("Doe");
    user.setEmail("invalid email");

    Set<ConstraintViolation<User>> violations = validator.validate(user);

    assertEquals(1, violations.size());
    assertEquals("must be a well-formed email address", violations.iterator().next().getMessage());
}

}

    我们使用 JUnit 5 编写一个测试来验证具有无效电子邮件地址的“用户”对象。然后我们使用 Validator 接口来验证 User 对象并检查是否返回了预期的验证错误。

10 Considere a validação do lado do cliente

A validação do lado do cliente pode melhorar a experiência do usuário, fornecendo feedback instantâneo ao usuário e reduzindo o número de solicitações ao servidor. No entanto, não deve ser considerado o único meio de validar a entrada. A validação do lado do cliente é facilmente contornada ou manipulada, portanto a entrada deve ser validada no lado do servidor para segurança e integridade dos dados.

Resumir

A autenticação eficaz é essencial para a estabilidade e segurança de qualquer aplicação web. Spring Boot fornece um conjunto de ferramentas e bibliotecas para simplificar a lógica de validação e facilitar sua manutenção. Seguindo as práticas recomendadas discutidas neste artigo, você pode garantir que seus componentes de autenticação sejam eficazes e forneçam uma ótima experiência ao usuário.

Acho que você gosta

Origin blog.csdn.net/2202_75623950/article/details/132588622
Recomendado
Clasificación