[JAVA] Ich benötige eine Validierung der Federdaten für Pageable ~

Dieses Mal werde ich vorstellen, wie eine Validierung (Eingabeprüfung) für den Wert durchgeführt wird, der von der von Spring Data bereitgestellten "Pageable" -Schnittstelle gehalten wird. Unter Verwendung von "Pageable", das von Spring Data bereitgestellt wird, können zusätzlich zu den zu durchsuchenden Seiteninformationen (Anzahl der Seiten, Anzahl der auf der Seite angezeigten Elemente) Sortierbedingungen (Zieleigenschaft / Spalte, aufsteigende / absteigende Reihenfolge) in den Anforderungsparametern angegeben werden. Es ist funktional praktisch, die Sortierbedingung angeben zu können. Wenn Sie sie jedoch falsch verwenden, kann dies zu einer SQL-Injection usw. führen. Was benötigt wird, um diese Art der Injektion zu verhindern, ist ... natürlich ... Validierung.

Version zur Überprüfung des Betriebs

Realisierungsmethode

Überprüfen Sie die Argumente der im Controller implementierten Handler-Methode mithilfe der von Spring bereitgestellten Funktion Method Validation (unter Verwendung des Bean Validation-Mechanismus).

Insbesondere ... sieht es so aus.

@GetMapping
Page<Todo> search(@AllowedSortProperties({"id", "title"}) Pageable pageable) {
    // ...
    return page;
}

Die von Spring bereitgestellte Methodenvalidierung verwendet die Funktionalität von Spring AOP, um die Validierung von Methodenaufrufargumenten und Rückgabewerten zu unterstützen.

Lass es uns ausprobieren! !!

Wenden Sie hier eine Validierung an, die prüft, ob sich die Zieleigenschaft oder Spalte der Sortierbedingung in der Zulassungsliste befindet.

Entwicklungsprojekt erstellen

Erstellen Sie wie immer ein Spring Boot-Entwicklungsprojekt aus "SPRING INITIALIZR". Wählen Sie in diesem Fall "Web" als Abhängigkeitsartefakt.

Anwenden von Spring Data Commons

Fügen Sie den abhängigen Bibliotheken Spring Data Commons hinzu, das "Pageable" enthält.

pom.xml


<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-commons</artifactId>
</dependency>

Wenn Sie Spring Data Commons zu Ihren abhängigen Bibliotheken hinzufügen, aktiviert der AutoConfigure-Mechanismus von Spring Boot Funktionen, mit denen Sie "Pageable" als Argument für die Handler-Methode des Controllers angeben können (z. B. "PageableHandlerMethodArgumentResolver").

Erstellen eines ConstraintValidator

Erstellen Sie eine "Constraint Annotation" und eine "Implementierungsklasse für" ConstraintValidator ", um nach" Pageable "zu suchen.

Einschränkungsanmerkung für Pageable


package com.example;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Constraint(validatedBy = {AllowedSortPropertiesValidator.class})
@Target({ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface AllowedSortProperties {
    String message() default "{com.example.AllowedSortProperties.message}";

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

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

    String[] value();

    @Target({ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        AllowedSortProperties[] value();
    }

}

Implementierungsklasse von ConstraintValidator for Pageable


package com.example;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class AllowedSortPropertiesValidator implements ConstraintValidator<AllowedSortProperties, Pageable> {

    private Set<String> allowedSortProperties;

    @Override
    public void initialize(AllowedSortProperties constraintAnnotation) {
        this.allowedSortProperties = new HashSet<>(Arrays.asList(constraintAnnotation.value()));
    }

    @Override
    public boolean isValid(Pageable value, ConstraintValidatorContext context) {
        if (value == null || value.getSort() == null) {
            return true;
        }
        if (allowedSortProperties.isEmpty()) {
            return true;
        }
        for (Sort.Order order : value.getSort()) {
            if (!allowedSortProperties.contains(order.getProperty())) {
                return false;
            }
        }
        return true;
    }

}

Methodenvalidierung anwenden

Wenden Sie die Methodenvalidierung auf den Handler-Methodenaufruf des Controllers an.

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@SpringBootApplication
public class BeanValidationDemoApplication {

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

    @Bean //Bean-Definition der Komponente, die den Validator der Bean-Validierung generiert
    static LocalValidatorFactoryBean localValidatorFactoryBean() {
        return new LocalValidatorFactoryBean();
    }

    @Bean // Method Validation(AOP)Bean Definition der Komponente, zu der
    static MethodValidationPostProcessor methodValidationPostProcessor(LocalValidatorFactoryBean localValidatorFactoryBean) {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(localValidatorFactoryBean);
        return processor;
    }

}

Erstellen einer Handler-Methode

Erstellen Sie eine Handler-Methode, die "Pageable" empfängt, und geben Sie die in ↑ erstellte Einschränkungsanmerkung an. Zu diesem Zeitpunkt muss der Klassenebene "@ Validated" hinzugefügt werden, um das Ziel der Methodenvalidierung zu sein.

package com.example;

import org.springframework.data.domain.Pageable;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Validated //Wenn Sie dies nicht hinzufügen, wird es nicht der Methodenvalidierung unterzogen
@RestController
@RequestMapping("/todos")
public class TodoRestController {

    @GetMapping
    Pageable search(@AllowedSortProperties({"id", "title"}) Pageable pageable) {
        return pageable;
    }

}

Wenn dies der Fall ist, wird der Suchvorgang ausgeführt und die Liste der Domänenobjekte zurückgegeben. Hier erfolgt die Implementierung jedoch so, dass das empfangene "Pageable" so zurückgegeben wird, wie es ist. (Da es sich nur um eine Verifizierungsanwendung handelt ...: heat_smile :)

Versuche dich zu bewegen

Lassen Sie uns die Spring Boot-Anwendung starten und tatsächlich darauf zugreifen.

Verhalten, wenn ein Eigenschaftsspaltenname in der Zulassungsliste angegeben wird


$ curl -D - http://localhost:8080/todos?sort=id
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 17 Dec 2016 15:51:51 GMT

{"sort":[{"direction":"ASC","property":"id","ignoreCase":false,"nullHandling":"NATIVE","ascending":true}],"offset":0,"pageNumber":0,"pageSize":20}

Verhalten, wenn ein Eigenschaftsspaltenname angegeben wird, der nicht in der Zulassungsliste enthalten ist


$ curl -D - http://localhost:8080/todos?sort=createdAt
HTTP/1.1 500 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 17 Dec 2016 15:53:47 GMT
Connection: close

{"timestamp":1481990027135,"status":500,"error":"Internal Server Error","exception":"javax.validation.ConstraintViolationException","message":"No message available","path":"/todos"}

Es scheint gut zu funktionieren, aber der Fehler beim Angeben eines Eigenschaftsspaltennamens, der nicht in der Zulassungsliste enthalten ist, wird als 500 Internal Server Error behandelt. Normalerweise sollte es 400 Bad Request sein.

Lassen Sie uns außergewöhnliches Handling machen! !!

Im Standardzustand ist es 500 Internal Server Error. Fügen wir also die Ausnahmebehandlung hinzu, wenn bei der Methodenüberprüfung ein Fehler auftritt, der 400 Bad Request ergibt.

package com.example;

import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.ConstraintViolationException;

@Validated
@RestController
@RequestMapping("/todos")
public class TodoRestController {

    @GetMapping
    Pageable search(@AllowedSortProperties({"id", "title"}) Pageable pageable) {
        return pageable;
    }

    @ExceptionHandler //Methode zur Behandlung der ConstraintViolationException hinzugefügt
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    String handleConstraintViolationException(ConstraintViolationException e) {
        return "Detect invalid parameter.";
    }

}

Ursprünglich sollte es im globalen Ausnahmebehandler implementiert sein, aber hier ist es im Controller implementiert. Außerdem wird die Fehlerantwort so implementiert, dass nur eine einfache Fehlermeldung ausgegeben wird. Implementieren Sie sie daher gemäß Ihren Anforderungen.

Fehlerantwort nach Hinzufügen der Ausnahmebehandlung


$ curl -D - http://localhost:8080/todos?sort=createdAt
HTTP/1.1 400 
Content-Type: text/plain;charset=UTF-8
Content-Length: 25
Date: Sat, 17 Dec 2016 16:02:29 GMT
Connection: close

Detect invalid parameter.

Note:

Wenn die Anwendung gut gestaltet ist, gibt der Benutzer die von "Pageable" verwalteten Informationen nicht manuell ein. Daher halte ich es nicht für erforderlich, eine freundliche Fehlerantwort zurückzugeben, wenn ein Fehler erkannt wird. (Diese Art von Fehler sollte nicht auftreten, wenn Sie die von der Anwendung bereitgestellte Funktion verwenden ...)

Zusammenfassung

Dieses Mal habe ich vorgestellt, wie die von Spring Data bereitgestellte "Pageable" -Schnittstelle validiert wird. Ich weiß nicht, ob es die beste Vorgehensweise ist, die Methodenvalidierung zu verwenden, aber ich denke, diese Methode wird derzeit empfohlen, da sie auf die gleiche Weise wie die Validierungsfunktion von Spring MVC überprüft werden kann.

Referenzseite

Recommended Posts

Ich benötige eine Validierung der Federdaten für Pageable ~
WebMvcConfigurer-Memorandum von Spring Boot 2.0 (Spring 5)
Ich habe Spring Data JDBC 1.0.0.BUILD-SNAPSHOT ausprobiert (-> 1.0.0.RELEASE)
Beispielcode für die Suche mit QBE (Query by Example) von Spring Data JPA
Zusammenfassung dessen, was ich über Spring Boot gelernt habe
Kollektive Behandlung von Spring-Validierungsfehlern mit @ControllerAdvice
Zusammenfassung dessen, was ich in Spring Batch gelernt habe
Anpassung der Validierung
Ich habe es mit Spring versucht.
Ich habe ein Docker-Image für die japanische Version von SDAPS erstellt
Untersuchte asynchrone Ausführung von Abfragen in Spring Boot 1.5.9
[Für Anfänger] DI ~ Die Grundlagen von DI und DI im Frühjahr ~
05. Ich habe versucht, die Quelle von Spring Boot zu löschen
Ich habe versucht, die Kapazität von Spring Boot zu reduzieren
Ich habe versucht, mit Spring Data JPA zu beginnen
Bis zur Verwendung von Spring Data und JPA Part 2
Erklärung von Ruby auf Schienen für Anfänger ⑥ ~ Erstellung der Validierung ~