2 votes

Spring Data Rest n'utilise que le chemin avec le parent

Description brève : Je veux que spring-data-rest ne génère que des points de terminaison comme "/users/{userId}/settings", "/users/{userId}/values" et non pas "/settings" ou "/values".

Plus de détails :

J'ai commencé à être ennuyé de vérifier dans chaque fonction du contrôleur si l'utilisateur connecté a accès à la ressource demandée et j'ai eu l'idée suivante pour automatiser cela (si quelqu'un a une meilleure idée qui ne cause pas les problèmes ci-dessous, je serais heureux de l'entendre)

Dans mon application, l'utilisateur n'a accès qu'à sa propre ressource et ne peut pas voir les données des autres utilisateurs.

Ainsi, j'ai pensé à restreindre (avec la configuration de Websecurity) tous les chemins commençant par "/users/{id}**" pour permettre uniquement l'accès lorsque l'utilisateur connecté possède l'identifiant donné. De cette manière, si mes points de terminaison sont par exemple "/users/{userId}/userValues" ou "/users/{userId}/settings", etc., chaque utilisateur ne peut accéder qu'à ses propres données en raison de la règle de permission

Pour automatiser davantage le processus et pour m'assurer que je n'oublie pas de vérifier l'userId dans le contrôleur, j'ai essayé d'utiliser spring-data-rest qui est génial sauf un problème :

spring-data-rest génère un point d'API pour chaque objet. Ils ressemblent à ceci :
"/settings", "/users", "/values".

Les chemins que je souhaite sont également générés : "/users/{userId}/values" et "/users/{userId}/values" et il vérifie correctement l'userId, donc mon idée semble fonctionner en principe.

Cependant, je n'arrive pas à comprendre comment désactiver les points de terminaison où le chemin commence par l'enfant ("/settings", "/values".) puisque je veux seulement que les points de terminaison commençant par "/users/{userId}" soient disponibles.

Est-ce possible ? Ou y a-t-il un meilleur moyen d'assurer que les utilisateurs ne peuvent voir et modifier que leurs propres données ?

Voici les classes de mon projet pour tester ce scénario (Je sais que le nommage est horrible, je vais mettre en place un nouveau projet si je sais que je peux faire fonctionner cela, c'est juste pour tester) :

Dépendances Gradle :

dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    //web frontend
    compile("org.springframework.boot:spring-boot-starter-web")
    //rest endpoint generator
    compile("org.springframework.boot:spring-boot-starter-data-rest")
    //database connection
    compile("mysql:mysql-connector-java")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    //Swagger (/v2/api-docs)
    compile("io.springfox:springfox-swagger2:2.9.2")
    //pretty swagger (/swagger-ui.html)
    compile("io.springfox:springfox-swagger-ui:2.9.2")
    //Lombok (automatic getter / setter)
    compile("org.projectlombok:lombok:1.18.6")
}

Utilisateur :

@Entity
@Getter
@Setter
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Integer id;

  private String firstName;

  private String lastName;

  @OneToMany(mappedBy="user", cascade= CascadeType.ALL)
  private List values;

  @OneToOne
  @RestResource(path = "settings", rel = "SettingsRel")
  private Settings settings;
}

UserValue

@Entity
public class UserValue {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  Integer id;

  String value;

  @ManyToOne
  @JoinColumn(name = "user_id")
  @JsonIgnore
  private User user;

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getValue() {
    return value;
  }

  public void setValue(String value) {
    this.value = value;
  }

  public User getUser() {
    return user;
  }

  public void setUser(User user) {
    this.user = user;
  }
}

Paramètres :

@Entity
@Getter
@Setter
public class Settings {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  Integer id;

  private HashMap settings;
}

UserValueRepository :

public interface UserValueRepository extends CrudRepository {
}

UserRepository :

public interface UserRepository extends CrudRepository {
}

SettingsRepository :

public interface SettingsRepository extends CrudRepository {
}

Deux problèmes supplémentaires que j'ai avec spring-data-rest sont qu'il semble ne pas fonctionner avec Swagger et Lombok. Je suis donc encore plus ouvert à des moyens alternatifs pour atteindre mon objectif.

0voto

Juliette Points 606

Donc, en ce qui concerne ma question initiale : Il existe deux façons d'atteindre la désactivation des chemins qui ne commencent pas par "/utilisateurs/{idUtilisateur}" : Soit en le désactivant directement dans le WebSecurityConfigurerAdapter, soit en utilisant un filtre. J'ai utilisé un filtre qui redirigeait par exemple la requête "/paramètres" vers "/utilisateurs/{idDeLUtilisateurConnecté}/paramètres" et tout fonctionnait parfaitement pour les requêtes GET.

Cependant, il semble que spring-data-rest ne traite que les requêtes POST et PUT pour le chemin racine d'un objet, donc alors que GET("/utilisateurs/{idUtilisateur}/paramètres") fonctionne sans problème, si vous souhaitez faire un POST, cela doit être "/paramètres" et ne peut pas être automatiquement lié à un utilisateur.

Il semble donc que je doive écrire le contrôleur manuellement après tout.

Cependant, j'ai quand même trouvé une solution plus élégante au problème d'authentification sous-jacent que de sélectionner manuellement le bon utilisateur dans la base de données et de définir cet utilisateur sur l'objet donné avec @PreAuthorize et @PostAuthorize dans Spring. Les deux concepts sont un peu délicats pour moi, mais ils peuvent faire le travail et seront ultérieurement extensibles de manière très bonne pour des règles comme "les amis peuvent aussi voir les données si vous leur avez donné accès" ou des choses similaires.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X