2015-10-11 10 views
7

sto usando Primavera di avvio dei dati REST di persistere miei User entità@HandleBeforeCreate dopo la convalida entità Running in primavera Boot dati REST

@Entity 
public class User { 

    @Id 
    @GeneratedValue 
    private long id; 

    @NotEmpty 
    private String firstName; 

    @NotEmpty 
    private String lastName; 

    @NotEmpty 
    private String email; 

    @Size(min = 5, max = 20) 
    private String password; 

    // getters and setters 
} 

utilizzando il repository:

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

Quello che voglio fare è validare gli utenti POST eD prima:

@Configuration 
public class CustomRestConfiguration extends SpringBootRepositoryRestMvcConfiguration { 

    @Autowired 
    private Validator validator; 

    @Override 
    protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) { 
     validatingListener.addValidator("beforeCreate", validator); 
    } 

} 

e solo successivamente hash della password dell'utente prima di riporla nel DB:

@Component 
@RepositoryEventHandler(User.class) 
public class UserRepositoryEventHandler { 

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 

    @HandleBeforeCreate 
    public void handleUserCreate(User user) { 
     user.setPassword(passwordEncoder.encode(user.getPassword())); 
    } 
} 

Come si è visto, però, la convalida viene eseguita dopo l'hashing della password e di conseguenza non riesce a causa della hashing password di essere troppo lungo.

C'è un modo per indicare a Spring di eseguire prima la convalida e solo dopo aver cancellato la password? So che potrei scrivere un controller da solo e specificare tutto in maniera raffinata, ma preferirei lasciarlo come ultima risorsa.

+1

Quale versione di Spring Boot/Data REST stai usando? Ho appena provato questo nella mia app e quando ho inserito il breakpoint nel gestore di Repo e 'SizeValidatorForCharSequence', quello nel validatore viene colpito prima del gestore, quindi per me funziona come previsto. Sto usando Spring Boot 1.2.5 –

+0

@BohuslavBurghardt Sono su Spring Boot '1.2.6.RELEASE' e gestisce tutte le altre versioni di dipendenza. Non so davvero come mettere un punto di interruzione sul validatore, dato che autowire quello (suppongo) creato da JPA. Succede da qualche parte dietro le quinte. Cercheremo di farlo anche se – Wojtek

+0

@Wojtek, c'è qualche altro hook con il quale posso ottenere il controllo dopo la convalida del bean con successo (prima che rimanga un'entità)? – masT

risposta

2

Come ho studiato nel debugger si è scoperto che un'entità arrivo viene elaborato nel seguente ordine:

  1. Spring esegue la convalida fagiolo quando deserializzazione JSON in SpringValidatorAdapter::validate. La password qui è in chiaro.
  2. @HandleBeforeCreate viene richiamato e la password è sottoposta a hash.
  3. JPA esegue la convalida dell'entità prima di salvarla nel DB. La password qui è già sottoposta a hash e la convalida non riesce. Nel mio caso (implementazione Hibernate di JPA) la validazione è stata eseguita in BeanValidationEventListener::validate.

Soluzione 1 (validazione completo in entrambe le fasi)

Una soluzione che ho trovato è stato quello di rilassare il vincolo sul campo password da solo utilizzando @NotEmpty (in modo che entrambe le fasi di validazione passavano e ancora l'entrata JSON è stato controllato per vuoto/nullità) ed eseguire la convalida della dimensione della password non elaborata in @HandleBeforeCreate (e lanciare l'eccezione appropriata da lì se necessario).

Il problema con questa soluzione è che mi ha richiesto di scrivere il mio gestore di eccezioni. Per stare al passo con gli standard elevati stabiliti da Spring Data REST in relazione al corpo della risposta dell'errore, dovrei scrivere molto codice per questo semplice caso. Il modo per farlo è descritto here.

Solution (convalida bean Spring senza convalida entità JPA) 2

Come accennato da Bohuslav Burghardt, è possibile disabilitare la seconda fase di validazione svolto da APP. In questo modo puoi mantenere i vincoli min e max e allo stesso tempo evitare di scrivere codice aggiuntivo. Come sempre è un compromesso tra semplicità e sicurezza. Il modo per disabilitare JPA è descritto con here.

Soluzione 3 (conservando soltanto min vincolo lunghezza della password)

Un'altra soluzione, almeno valida nel mio caso, è stato quello di lasciare la lunghezza massima della password illimitata. In questo modo, nella prima fase di validazione, la password è stata controllata se non era troppo breve e nella seconda fase è stata validata in modo efficace ogni volta (poiché la password crittografata era già abbastanza lunga).

L'unica avvertenza a questa soluzione è che @Size(min = 5) non sembra controllare la nullità, quindi ho dovuto aggiungere @NotNull per gestire questo caso. Tutto sommato il campo è annotato come:

@NotNull 
@Size(min = 5) 
private String password; 
+1

Lettura interessante. Il motivo per cui ha funzionato per me è probabilmente perché ho disabilitato la convalida di Hibernate tramite application.properties ('spring.jpa.properties.javax.persistence.validation.mode = none'). Puoi farlo anche tu, quindi verrà eseguita solo la validazione durante la deserializzazione al punto 1. –

+0

Sì, ho letto su di esso ma non volevo davvero dimettermi da tutta la bontà della convalida JPA :) La aggiungerò come un'altra possibile soluzione. Grazie per il tuo aiuto Bohuslav! – Wojtek

+1

su come validare una stringa con Regex? – Kenji