GNU/Linux >> Tutoriales Linux >  >> Cent OS

Cómo implementar la validación para servicios RESTful con Spring

Validación de Spring Bean

La validación de datos no es un tema nuevo en el desarrollo de aplicaciones web.

Echamos un breve vistazo a la validación de datos en el ecosistema Java en general y Spring Framework específicamente. La plataforma Java ha sido el estándar de facto para implementar la validación de datos esa es la especificación de Validación de Bean. La especificación Bean Validation tiene varias versiones:

  • 1.0 (JSR-303),
  • 1.1 (JSR-349),
  • 2.0 (JSR 380):la última versión

Esta especificación define un conjunto de componentes, interfaces y anotaciones. Esto proporciona una forma estándar de poner restricciones a los parámetros y devolver valores de métodos y parámetros de constructores, proporciona API para validar objetos y gráficos de objetos.

Un modelo declarativo se utiliza para poner restricciones en forma de anotaciones en los objetos y sus campos. Hay anotaciones predefinidas como @NotNull , @Digits , @Pattern , @Email , @CreditCard . Existe la posibilidad de crear nuevas restricciones personalizadas.

La validación puede ejecutarse manualmente o de forma más natural, cuando otras especificaciones y marcos validan los datos en el momento adecuado, por ejemplo, la entrada del usuario, la inserción o la actualización en JPA.

Ejemplo de validación en Java

Echemos un vistazo a cómo se puede hacer en la práctica en este ejemplo simple de Validación de Bean dentro de una aplicación Java normal.

Tenemos un objeto que queremos validar con todos los campos anotados con restricciones.

public class SimpleDto {

  @Min(value = 1, message = "Id can't be less than 1 or bigger than 999999")
  @Max(999999)
  private int id;

  @Size(max = 100)
  private String name;

  @NotNull
  private Boolean active;

  @NotNull
  private Date createdDatetime;

  @Pattern(regexp = "^asc|desc$")
  private String order = "asc";

  @ValidCategory(categoryType="simpleDto")
  private String category;
  …
  Constructor, getters and setters

Ahora podemos usarlo en una aplicación Java simple y validar manualmente el objeto.

public class SimpleApplication {

  public static void main(String[] args) {

    final SimpleDto simpleDto = new SimpleDto();
    simpleDto.setId(-1);
    simpleDto.setName("Test Name");
    simpleDto.setCategory("simple");
    simpleDto.setActive(true);
    simpleDto.setOrder("asc");
    simpleDto.setCreatedDatetime(new Date());

    ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
    Validator validator = validatorFactory.usingContext().getValidator();

    Set constrains = validator.validate(simpleDto);
    for (ConstraintViolation constrain : constrains) {

       System.out.println(
      "[" + constrain.getPropertyPath() + "][" + constrain.getMessage() + "]"
      );

    }

  }

}

Y el resultado en Consola será:

“[id] [Id can't be less than 1 or bigger than 999999]”

Restricción de validación y anotación

Cree una restricción de validación y una anotación personalizadas.


 @Retention(RUNTIME)
   @Target(FIELD)
   @Constraint(validatedBy = {ValidCategoryValidator.class})
   public @interface ValidCategory {

      String categoryType();

      String message() default "Category is not valid";

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

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

    }

And constraint validation implementation:

public class ValidCategoryValidator implements ConstraintValidator<ValidCategory, String> {

    private static final Map<String, List> availableCategories;

    static {

      availableCategories = new HashMap<>();
      availableCategories.put("simpleDto", Arrays.asList("simple", "advanced"));

    }

    private String categoryType;

    @Override
    public void initialize(ValidCategory constraintAnnotation) {

      this.setCategoryType(constraintAnnotation.categoryType());

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

      List categories = ValidCategoryValidator.availableCategories.get(categoryType);
      if (categories == null || categories.isEmpty()) {

         return false;

      }

      for (String category : categories) {

         if (category.equals(value)) {

             return true;

      }

    }

    return false;

  }

}

En el ejemplo anterior, las categorías disponibles provienen de un mapa hash simple. En casos de uso de aplicaciones reales, se pueden recuperar de la base de datos o de cualquier otro servicio.

Tenga en cuenta que las restricciones y validaciones se pueden especificar y realizar no solo a nivel de campo, sino también en un objeto completo.

Cuando necesitamos validar varias dependencias de campo, por ejemplo, fecha de inicio, no puede ser posterior a la fecha de finalización.

Las implementaciones más utilizadas de Bean Validation las especificaciones son Hibernate Validator y Apache BVal .

Validación con Spring

Spring Framework ofrece varias características para la validación.

  • La compatibilidad con Bean Validation API versiones 1.0, 1.1 (JSR-303, JSR-349) se introdujo en Spring Framework a partir de la versión 3.
  • Spring tiene su propia interfaz Validator que es muy básica y se puede configurar en una instancia específica de DataBinder. Esto podría ser útil para implementar la lógica de validación sin anotaciones.

Validación de Bean con Spring

Spring Boot proporciona una validación iniciada que se puede incluir en el proyecto:

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

Este iniciador proporciona una versión de Hibernate Validator compatible con el Spring Boot actual.

Usando Bean Validation, podríamos validar un cuerpo de solicitud, parámetros de consulta, variables dentro de la ruta (por ejemplo, / /simpledto/{id} ), o cualquier parámetro de método o constructor.

Solicitudes POST o PUT

En las solicitudes POST o PUT, por ejemplo, pasamos la carga útil de JSON, Spring lo convierte automáticamente en un objeto Java y ahora queremos validar el objeto resultante. Usemos SimpleDto objeto del ejemplo 1:

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

    @Autowired
    private SimpleDtoService simpleDtoService;

    @RequestMapping(path = "", method = RequestMethod.POST, produces = 
    "application/json")
    public SimpleDto createSimpleDto(

        @Valid @RequestBody SimpleDto simpleDto) {

      SimpleDto result = simpleDtoService.save(simpleDto);

      return result;

    }

}

Acabamos de agregar @Valid anotación a SimpleDto parámetro anotado con @RequestBody . Esto le indicará a Spring que procese la validación antes de realizar una llamada de método real. En caso de que falle la validación, Spring lanzará una MethodArgument NotValidException que, de forma predeterminada, devolverá una respuesta 400 (Solicitud incorrecta).

Validación de variables de ruta

La validación de variables de ruta funciona un poco diferente. El problema es que ahora tenemos que agregar anotaciones de restricciones directamente a los parámetros del método en lugar de dentro de los objetos.

Para que esto funcione, hay 2 opciones posibles:

Opción 1:@Anotación validada

Agregar @Validated anotación al controlador a nivel de clase para evaluar las anotaciones de restricción en los parámetros del método.

Opción 2:variable de ruta

Use un objeto que represente la variable de ruta como se ve en el siguiente ejemplo:

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

    @Autowired
    private SimpleDtoService simpleDtoService;

    @RequestMapping(path = "/{simpleDtoId}", method = RequestMethod.GET, produces = 
    "application/json")
    public SimpleDto getSimpleDto(

      @Valid SimpleDtoIdParam simpleDtoIdParam) {

    SimpleDto result = simpleDtoService.findById(simpleDtoIdParam.getSimpleDtoId());

    if (result == null) {

      throw new NotFoundException();

    }

    return result;

  }

}

En este caso, tenemos SimpleDtoIdParam clase que contiene el simpleDtoId campo que será validado contra una anotación de restricción Bean estándar o personalizada. Ruta Nombre de variable (/{simpleDtoId} ) debe ser el mismo que el nombre del campo (para que Spring pueda encontrar el setter para este campo).

private static final long serialVersionUID = -8165488655725668928L;

    @Min(value = 1)
    @Max(999999)
    private int simpleDtoId;

    public int getSimpleDtoId() {
    return simpleDtoId;
    }

    public void setSimpleDtoId(int simpleDtoId) {
    this.simpleDtoId = simpleDtoId;
    }

}

A diferencia de Cuerpo de solicitud validación, Variable de ruta la validación arroja ConstraintViolationException en lugar de MethodArgumentNotValidException . Por lo tanto, necesitaremos crear un controlador de excepciones personalizado.

El marco de Java Spring también permite validar parámetros en un nivel de servicio con @Validated anotación (nivel de clase) y @Valid (nivel de parámetro).

Teniendo en cuenta que Spring JPA usa Hibernate debajo, también es compatible con Bean Validation para clases de entidad. Tenga en cuenta que probablemente no sea una buena idea en muchos casos confiar en este nivel de validación, ya que significa que toda la lógica anterior estaba tratando con objetos no válidos.

Interfaz de validación de primavera

Spring define su propia interfaz para la validación Validator (org.springframework.validation.Validator). Se puede configurar para una instancia específica de DataBinder e implementar la validación sin anotaciones (enfoque no declarativo).

Para implementar este enfoque necesitaríamos:

  1. Implementar la interfaz del validador
  2. Agregar validador

Implementar interfaz de validación

Implemente la interfaz Validator, por ejemplo, trabajemos con nuestro SimpleDto clase:

@Component
public class SpringSimpleDtoValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
    return SimpleDto.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {

      if (errors.getErrorCount() == 0) {

        SimpleDto param = (SimpleDto) target;
        Date now = new Date();
        if (param.getCreatedDatetime() == null) {

          errors.reject("100",

            "Create Date Time can't be null");

        } else if (now.before(param.getCreatedDatetime())) {

          errors.reject("101",

            "Create Date Time can't be after current date time");

        }

      }

    }

}

Compruebe aquí si el Datetime creado la marca de tiempo está en el futuro.

Añadir Validador

Agregue la implementación de Validator a DataBinder :

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

    @Autowired
    private SimpleDtoService simpleDtoService;

    @Autowired
    private SpringSimpleDtoValidator springSimpleDtoValidator;

    @InitBinder("simpleDto")
    public void initMerchantOnlyBinder(WebDataBinder binder) {
    binder.addValidators(springSimpleDtoValidator);
    }

    @RequestMapping(path = "", method = RequestMethod.POST, produces = 
    "application/json")
    public SimpleDto createSimpleDto(

      @Valid @RequestBody SimpleDto simpleDto) {

    SimpleDto result = simpleDtoService.save(simpleDto);
    return result;

  }

}

Ahora tenemos SimpleDto validado usando anotaciones de restricción y nuestra implementación personalizada de Spring Validation.


Cent OS
  1. Cómo administrar los servicios de Linux con el comando systemctl

  2. ¿Cómo crear un bucle for con un número variable de iteraciones?

  3. Cómo reescribir URL con mod_rewrite para Apache en Ubuntu 20.04

  4. Cómo volver a generar initramfs y vmlinuz para Rescue Kernel con el kernel actual en CentOS/RHEL 7

  5. Cómo configurar un subdominio para servicios de correo electrónico SMTP transaccional

Cómo integrar Grafana con Prometheus para monitoreo

Cómo buscar virus con ClamAV en Ubuntu 20.04

Cómo ejecutar pods como servicios systemd con Podman

Cómo configurar Nginx con SSL

Cómo configurar Redis para alta disponibilidad con Sentinel en CentOS 8 – Parte 2

Cómo automatizar las auditorías de seguridad de Docker con Docker Bench for Security