La sécurisation de votre application va au-delà de la simple autorisation ou interdiction d’accès à un niveau superficiel. En tant que développeur, vous devez implémenter une autorisation à grains fins
(FGA) pour gérer les permissions à un niveau plus détaillé et plus granulaire.
FGA vous permet de définir des contrôles d’accès détaillés qui spécifient qui peut faire quoi et sous quelles conditions.
Dans ce tutoriel, vous apprendrez comment implémenter une autorisation à grains fins
en Java et avec Spring Boot en utilisant Permit.io.
Voici le code source (n’oubliez pas de lui donner un ⭐).
J’espère que vous avez apprécié mon précédent blog sur la construction d’une application de conférence vidéo personnalisée avec Stream et Next.js. Ces blogs reflètent mon parcours dans la création de DevTools Academy, une plateforme conçue pour aider les développeurs à découvrir des outils de développement exceptionnels.
Ce tutoriel est un autre effort pour vous présenter un outil de développement très utile que j’ai découvert récemment.
Table des matières :
Qu’est-ce que Permit ?
Permit.io est une solution d’autorisation à l’échelle de l’application, plein écrin, prête à l’emploi qui vous permet de mettre en œuvre un layer d’
autorisation
sécurisé
,flexible
en quelques minutes, ce qui vous permet de vous concentrer sur ce qui compte le plus.
Prérequis
Pour comprendre complètement l’tutorial, vous devez avoir une connaissance de base de Java
et de Spring Boot
. Vous aurez également besoin des éléments suivants :
-
Permit.io : Un outil de développement qui simplifie la mise en œuvre de l’FGA.
-
Spring Boot Starter Web : Fournit les composants essentiels pour la construction d’applications web, y compris les API RESTful.
-
Gradle : Un outil de construction pour la gestion des dépendances.
-
JDK 11 ou ultérieur : La version du Kit de Développement Java (JDK) requise pour compiler et exécuter votre application Spring Boot.
-
Postman ou cURL : Outils pour tester vos points d’accès
API
.
Qu’est-ce que l’Autorisation Fine Grainée ?
L’Autorisation Fine Grainée offre un contrôle d’accès aux ressources en déterminant qui peut y accéder, dans quelle mesure et sous quelles conditions spécifiées.
Contrairement à l’autorisation à granularité grossière (qui traite de l’accès basé sur des catégories telles que les rôles d'utilisateurs
telles que « admin
» ou « utilisateur
« ), l’autorisation à granularité fine vous donne la flexibilité de définir l’accès à un niveau granulaire, pour des ressources ou actions spécifiques, et même des attributs.
Dans l'Autorisation à Granularité Fine
, il existe 3 types de modèles de politique pour gérer l’autorisation ; le Contrôle d’Accès Basé sur les Rôles (RBAC), le Contrôle d’Accès Basé sur les Attributs (ABAC), et le Contrôle d’Accès Basé sur les Relations (ReBAC).
Faisons un tour à travers chacune de ces approches et voyons comment vous pouvez les mettre en œuvre dans votre application.
le Contrôle d’Accès Basé sur les Rôles (RBAC)
RBAC est une approche de sécurité qui contrôle l’accès aux ressources en fonction des rôles des utilisateurs au sein d’une organisation. Ce modèle simplifie les autorisations en organisant les utilisateurs en rôles et en gérant le contrôle d’accès en fonction de ces rôles définis.
concepts clés dans RBAC :
Utilisateurs : Personnes qui utilisent le système, telles que les employés ou les clients.
Rôles : Un ensemble de permissions ou de privilèges d’accès attribué à un groupe d’utilisateurs en fonction de leurs responsabilités ou tâches, telles que admin, gestionnaire ou client.
Autorisations : Les droits accordés aux utilisateurs pour interagir avec les ressources, telles que lire, écrire ou supprimer.
le Contrôle d’Accès Basé sur les Attributs (ABAC)
ABAC est un modèle d’accès adaptatif et polyvalent qui décide qui peut ou ne peut pas accéder à des ressources en fonction d’attributs, comme les détails utilisateur. Le modèle ABAC permet de définir une autorisation granuleuse en fonction des attributs de l’utilisateur.
Concepts clés dans le ABAC :
Attributs : Caractéristiques ou propriétés utilisées pour prendre des décisions d’accès. Les attributs sont généralement catégorisés en :
-
Attributs utilisateur : Informations sur l’utilisateur (par exemple, rôle, département, titre de travail, âge, etc.).
-
Attributs de ressource : Caractéristiques de la ressource (par exemple, type de fichier, niveau de classification des données, date de création, propriétaire).
-
Attributs d’action : L’action que l’utilisateur essaye de réaliser (par exemple, lire, écrire, supprimer, approuver).
-
Attributs environnementaux : Informations contextuelles sur la demande d’accès (par exemple, heure du jour, emplacement, type de périphérique, adresse IP).
Contrôle d’accès basé sur les relations (ReBAC)
Le ReBAC est un système de contrôle d’accès qui octroie des permissions pour accéder aux ressources en fonction des relations entre les entités dans un système. Cette approche met l’accent sur la définition et la gestion du contrôle d’accès en cartographiant comment les utilisateurs se relient aux ressources et aux autres entités, telles que les organisations ou les groupes.
Concepts clés du ReBAC :
Entités : Les utilisateurs, les ressources (telles que les fichiers et les documents) et d’autres entités, telles que les groupes ou les unités organisationnelles.
Relations : Les connexions qui spécifient la relation entre deux entités. Un utilisateur pourrait être le « propriétaire » d’un document ou un « membre » d’une équipe, par exemple.
Politiques : Les règles qui utilisent les relations pour déterminer les droits d’accès. Un utilisateur peut accéder à une ressource ou exécuter une action sur elle s’il a une relation particulière avec elle.
Comment implémenter un autorisation granulaire
Maintenant que vous avez une bonne idée de RBAC
, ABAC
et ReBAC
, voyons comment nous pouvons implémenter ces modèles dans une application e-commerce.
Implémenter un contrôle d’accès basé sur les rôles
Étape 1 : Allez sur Permit.io, puis créez un compte et votre espace de travail.
Par défaut, vous devriez voir un projet incluant deux environnements : Développement
et Production
.
Étape 2 : Créez une ressource nommée Produits. Pour créer la ressource, ouvrez la politique dans le panneau de navigation de gauche, puis ouvrez la Ressources en haut. Par la suite, cliquez sur le Créer une ressource et créez une ressource appelée Produits avec les actions lire
, créer
, mettre à jour
et supprimer
.
Étape 3 : Créez une autre ressource appelée Avis avec les actions lire
, créer
, mettre à jour
et supprimer
.
Étape 4 : Ouvrez l’onglet Éditeur de politiques. Vous verrez que 3 rôles ont été créés nommés admin
, éditeur
et visionneur
.
-
Le rôle admin a le droit de
créer
,supprimer
,lire
oumettre à jour
un produit ou un avis. -
Le rôle
éditeur
a le droit decréer
,lire
oumettre à jour
unproduit
ou unavis
mais ne peut passupprimer
. -
Le rôle
viewer
a l’autorisation decréer
et delire
un produit ou unecritique
mais pas desupprimer
ou demettre à jour
quoi que ce soit.
Mise en œuvre du contrôle d’accès basé sur les attributs
Étape 1 : Ouvrez l’onglet Ressources, puis cliquez sur le bouton Ajouter des attributs.
-
Ajoutez un attribut appelé vendeur
-
Ajoutez un attribut appelé le client
Étape 2 : Ouvrez l’onglet Règles ABAC, puis créez un nouveau jeu de ressources ABAC appelé Mes produits qui dépend de la ressource Produits. Après cela, ajoutez une condition qui n’accorde des autorisations qu’à l’utilisateur qui a créé un produit en fonction de l’attribut vendeur.
Étape 3 : Créez un autre jeu de ressources ABAC appelé Mes critiques qui dépend de la ressource Critiques.
Mise en œuvre du contrôle d’accès basé sur les relations
Étape 1 : Ouvrez l’onglet Ressources et modifiez la ressource Produits. Ajoutez le rôle vendeur
dans la section des options ReBAC
. Ensuite, définissez les produits comme parent des critiques dans la section des relations.
Étape 2 : Modifiez la ressource des avis en ajoutant le rôle client dans la section des options ReBAC
, comme illustré ci-dessous :
Étape 3 : Allez à l’onglet Politique
Éditeur
et ajoutez :
-
rôle
vendeur
autorisé à mettre à jour et à supprimer ses propres produits. -
rôle
client
autorisé à mettre à jour et à supprimer leurs propres avis sur les produits.
Comment implémenter FGA en Java et SpringBoot
Maintenant que nous avons défini les politiques RBAC
, ABAC
et ReBAC
dans l’interface web Permit.io, apprenons comment les enforcer dans une application de gestion de commerce électronique en utilisant l’API Permit.io.
Il va y avoir beaucoup de code, donc assurez-vous de lire les commentaires étendus que je ai laissés à travers chaque bloc de code. Cela vous aidera à comprendre plus pleinement ce qui se passe dans ce code.
Étape 1 : Configuration de l’application de commerce électronique
Pour configurer l’application de commerce électronique et git clone du code source.
git clone https://github.com/tyaga001/java-spring-fine-grained-auth.git
Installation du SDK Package Permit
Pour installer le SDK Package Permit, vous ajoutez le SDK sous le bloc dépendances dans le fichier build.graddle
.
## Dependencies
To set up the necessary dependencies for your Spring Boot project, include the following in your `build.gradle` file:
```groovy
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// Ajoutez cette ligne pour installer le SDK Java Permit.io dans votre projet
implementation 'io.permit:permit-sdk-java:2.0.0'
}
Initialisation de la SDK Permit
Vous pouvez initialiser le client de la SDK
Permit en utilisant le code ci-dessous :
package com.boostmytool.store.config;
import io.permit.sdk.Permit;
import io.permit.sdk.PermitConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // Marque cette classe comme une classe de configuration pour Spring IoC
public class PermitClientConfig {
@Value("${permit.api-key}") // Injecte la clé API Permit depuis les propriétés de l'application
private String apiKey;
@Value("${permit.pdp-url}") // Injecte l'URL du PDP (Point de décision de politique) Permit depuis les propriétés de l'application
private String pdpUrl;
/**
* Crée une instance de client Permit avec une configuration personnalisée
* @return Une instance de client Permit
*/
@Bean
public Permit permit() {
return new Permit(
new PermitConfig.Builder(apiKey) // Initialise PermitConfig avec la clé API
.withPdpAddress(pdpUrl) // Définit l'adresse du PDP
.withDebugMode(true) // Active le mode débug pour des logs détaillés
.build() // Construit l'objet PermitConfig
);
}
}
Synchronisation des Utilisateurs avec la SDK
Pour commencer à appliquer les permissions, vous devez d’abord synchroniser un utilisateur avec Permit, puis leur assigner un rôle.
Dans le code ci-dessous, la classe UserService fournit des méthodes pour le login, l’inscription, l’attribution de rôles et l’autorisation des utilisateurs, avec une gestion des exceptions pour les erreurs possibles lors de l’interaction avec l’API Permit.
package com.boostmytool.store.service;
import com.boostmytool.store.exception.ForbiddenAccessException;
import com.boostmytool.store.exception.UnauthorizedException;
import io.permit.sdk.Permit;
import io.permit.sdk.api.PermitApiError;
import io.permit.sdk.api.PermitContextError;
import io.permit.sdk.enforcement.Resource;
import io.permit.sdk.enforcement.User;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service // Marque cette classe comme une service Spring, en faisant d'elle une candidate pour l'analyse de composants
public class UserService {
private final Permit permit;
// Injection de constructeur pour le SDK Permit
public UserService(Permit permit) {
this.permit = permit;
}
/**
* Simule l'authentification de l'utilisateur en créant et retournant un objet Utilisateur Permit.
*
* @param key Clé unique de l'utilisateur
* @return Objet Utilisateur
*/
public Object login(String key) {
return new User.Builder(key).build();
}
/**
* Gère l'inscription de l'utilisateur en créant et synchronisant un nouvel objet Utilisateur Permit.
*
* @param key Clé unique de l'utilisateur
* @return Objet Utilisateur créé et synchronisé
*/
public User signup(String key) {
var user = new User.Builder(key).build();
try {
permit.api.users.sync(user); // Synchronise le nouvel utilisateur avec le service Permit
} catch (PermitContextError | PermitApiError | IOException e) {
throw new RuntimeException("Failed to create user", e); // Gère les exceptions lors de la création de l'utilisateur
}
return user;
}
/**
* Attribue un rôle à l'utilisateur dans l'environnement "default".
*
* @param user Objet Utilisateur à attribuer le rôle
* @param role Rôle à attribuer
*/
public void assignRole(User user, String role) {
try {
permit.api.users.assignRole(user.getKey(), role, "default"); // Attribue le rôle dans l'environnement "default"
} catch (PermitApiError | PermitContextError | IOException e) {
throw new RuntimeException("Failed to assign role to user", e); // Gère les exceptions lors de l'attribution du rôle
}
}
/**
* Vérifie si l'utilisateur est autorisé à effectuer une action spécifique sur une ressource.
*
* @param user Objet Utilisateur demandant l'autorisation
* @param action Action à autoriser
* @param resource Ressource sur laquelle l'action sera effectuée
* @throws UnauthorizedException Si l'utilisateur n'est pas connecté
* @throws ForbiddenAccessException Si l'utilisateur est refusé l'accès
*/
public void authorize(User user, String action, Resource resource) {
if (user == null) {
throw new UnauthorizedException("Not logged in"); // Lance une exception si l'utilisateur n'est pas connecté
}
try {
var permitted = permit.check(user, action, resource); // Effectue la vérification d'autorisation
if (!permitted) {
throw new ForbiddenAccessException("Access denied"); // Lance une exception si l'accès est refusé
}
} catch (PermitApiError | IOException e) {
throw new RuntimeException("Failed to authorize user", e); // Gère les exceptions lors de l'autorisation
}
}
}
Dans le code ci-dessous, la classe UserController expose des points d’entrée REST API pour l’inscription des utilisateurs et l’attribution des rôles. Elle interagit avec la classe UserService pour gérer la logique métier liée aux utilisateurs et fournit des réponses HTTP appropriées.
package com.boostmytool.store.controllers;
import com.boostmytool.store.exception.UnauthorizedException;
import com.boostmytool.store.service.UserService;
import io.permit.sdk.enforcement.User;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // Indique que cette classe gère les demandes HTTP et retourne des réponses JSON
@RequestMapping("/api/users") // Chemin d'URL de base pour toutes les opérations liées aux utilisateurs
public class UserController {
private final UserService userService;
// Injection de constructeur de UserService, contenant la logique métier pour les opérations d'utilisateurs
public UserController(UserService userService) {
this.userService = userService;
}
/**
* Gère les demandes d'inscription des utilisateurs.
* Point d'entrée : POST /api/users/signup
*
* @param key Clé unique pour le nouvel utilisateur
* @return Objet User créé
*/
@PostMapping("/signup")
public User signup(@RequestBody String key) {
return userService.signup(key); // Appelle la méthode signup de UserService pour créer un nouvel utilisateur
}
/**
* Gère l'attribution d'un rôle à l'utilisateur connecté.
* Point d'entrée : POST /api/users/assign-role
*
* @param request Demande HTTP, utilisée pour récupérer l'utilisateur actuel
* @param role Rôle à assigner à l'utilisateur actuel
*/
@PostMapping("/assign-role")
public void assignRole(HttpServletRequest request, @RequestBody String role) {
// Récupère l'utilisateur actuel depuis les attributs de la demande
User currentUser = (User) request.getAttribute("user");
// Lève une exception si l'utilisateur n'est pas connecté
if (currentUser == null) {
throw new UnauthorizedException("Not logged in");
}
// Assigne le rôle spécifié à l'utilisateur actuel
userService.assignRole(currentUser, role);
}
}
Création d’un Point d’Exécution de Politiques pour RBAC, ABAC et ReBAC
Dans le code ci-dessous, la classe ProductService gère les opérations CRUD pour les produits et les avis, en traité les permissions et les rôles via l’API Permit.
Chaque opération inclut des vérifications d’autorisation
utilisateur, avec une gestion adéquate des exceptions pour les erreurs de l’API Permit et les scénarios de ressource non trouvée.
package com.boostmytool.store.service;
import com.boostmytool.store.exception.ResourceNotFoundException;
import com.boostmytool.store.model.Product;
import com.boostmytool.store.model.Review;
import io.permit.sdk.Permit;
import io.permit.sdk.api.PermitApiError;
import io.permit.sdk.api.PermitContextError;
import io.permit.sdk.enforcement.Resource;
import io.permit.sdk.enforcement.User;
import io.permit.sdk.openapi.models.RelationshipTupleCreate;
import io.permit.sdk.openapi.models.ResourceInstanceCreate;
import io.permit.sdk.openapi.models.RoleAssignmentCreate;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Service // Marque cette classe comme un service Spring
public class ProductService {
private final List<Product> products = new ArrayList<>(); // Liste en mémoire pour stocker les produits
private final AtomicInteger productIdCounter = new AtomicInteger(); // Compteur pour générer des ID de produits uniques
private final AtomicInteger reviewIdCounter = new AtomicInteger(); // Compteur pour générer des ID de commentaires uniques
// Constructeurs pour les instances de ressources Permit (produit et commentaire)
private final Resource.Builder productResourceBuilder = new Resource.Builder("product");
private final Resource.Builder reviewResourceBuilder = new Resource.Builder("review");
private final UserService userService; // Service pour gérer les opérations liées aux utilisateurs
private final Permit permit; // Instance du SDK Permit pour gérer l'autorisation et la gestion des ressources
// Constructeur pour injecter les dépendances
public ProductService(UserService userService, Permit permit) {
this.userService = userService;
this.permit = permit;
}
// Méthode pour autoriser un utilisateur pour une action donnée sur une ressource
private void authorize(User user, String action, Resource resource) {
userService.authorize(user, action, resource);
}
// Autorise un utilisateur à effectuer une action sur un produit spécifique
private void authorize(User user, String action, Product product) {
var attributes = new HashMap<String, Object>();
attributes.put("vendor", product.getVendor()); // Ajoute l'attribut vendeur au produit
userService.authorize(user, action, productResourceBuilder.withKey(product.getId().toString()).withAttributes(attributes).build());
}
// Autorise un utilisateur à effectuer une action sur un commentaire spécifique
private void authorize(User user, String action, Review review) {
var attributes = new HashMap<String, Object>();
attributes.put("customer", review.getCustomer()); // Ajoute l'attribut client au commentaire
userService.authorize(user, action, reviewResourceBuilder.withKey(review.getId().toString()).withAttributes(attributes).build());
}
// Récupère un produit par son ID, lance une exception si non trouvé
private Product getProductById(int id) {
return products.stream().filter(product -> product.getId().equals(id))
.findFirst().orElseThrow(() -> new ResourceNotFoundException("Product with id " + id + " not found"));
}
// Récupère tous les produits, vérifie si l'utilisateur est autorisé à "lire" les produits
public List<Product> getAllProducts(User user) {
authorize(user, "read", productResourceBuilder.build()); // L'utilisateur doit avoir la permission "lire"
return new ArrayList<>(products); // Renvoie une copie de la liste des produits
}
// Récupère un produit par son ID, vérifie si l'utilisateur est autorisé à "lire" le produit
public Product getProduct(User user, int id) {
authorize(user, "read", productResourceBuilder.build());
return getProductById(id);
}
// Ajoute un nouveau produit, autorise l'utilisateur et crée des instances de ressources et des affectations de rôles dans Permit
public Product addProduct(User user, String content) {
authorize(user, "create", productResourceBuilder.build()); // Vérifie si l'utilisateur peut créer un produit
Product product = new Product(productIdCounter.incrementAndGet(), user.getKey(), content); // Crée un nouveau produit
try {
// Crée une instance de ressource dans Permit et affecte le rôle "vendeur" à l'utilisateur pour ce produit
permit.api.resourceInstances.create(new ResourceInstanceCreate(product.getId().toString(), "product").withTenant("default"));
permit.api.roleAssignments.assign(new RoleAssignmentCreate("vendor", user.getKey()).withResourceInstance("product:" + product.getId()).withTenant("default"));
} catch (IOException | PermitApiError | PermitContextError e) {
throw new RuntimeException("Failed to create resource instance or role assignment: " + e.getMessage());
}
products.add(product); // Ajoute le produit à la liste en mémoire
return product;
}
// Met à jour le contenu d'un produit, vérifie si l'utilisateur est autorisé à "mettre à jour" le produit
public Product updateProduct(User user, int id, String content) {
Product product = getProductById(id); // Récupère le produit par son ID
authorize(user, "update", product); // Vérifie si l'utilisateur peut mettre à jour le produit
product.setContent(content); // Met à jour le contenu du produit
return product;
}
// Supprime un produit, vérifie si l'utilisateur est autorisé à "supprimer" le produit
public void deleteProduct(User user, int id) {
boolean isDeleted = products.removeIf(product -> {
if (product.getId().equals(id)) {
authorize(user, "delete", product); // Vérifie si l'utilisateur peut supprimer le produit
return true;
} else {
return false;
}
});
if (!isDeleted) {
throw new ResourceNotFoundException("Product with id " + id + " not found");
}
try {
permit.api.resourceInstances.delete("product:" + id); // Supprime l'instance de ressource du produit de Permit
} catch (IOException | PermitApiError | PermitContextError e) {
throw new RuntimeException(e);
}
}
// Ajoute un commentaire à un produit, crée une instance de ressource et une relation dans Permit
public Review addReview(User user, int productId, String content) {
authorize(user, "create", reviewResourceBuilder.build()); // Vérifie si l'utilisateur peut créer un commentaire
Product product = getProductById(productId); // Récupère le produit par son ID
Review review = new Review(reviewIdCounter.incrementAndGet(), user.getKey(), content); // Crée un nouveau commentaire
try {
// Crée une instance de ressource pour le commentaire et définit la relation avec le produit
permit.api.resourceInstances.create(new ResourceInstanceCreate(review.getId().toString(), "review").withTenant("default"));
permit.api.relationshipTuples.create(new RelationshipTupleCreate("product:" + productId, "parent", "review:" + review.getId()));
} catch (IOException | PermitApiError | PermitContextError e) {
throw new RuntimeException(e);
}
product.addReview(review); // Ajoute le commentaire au produit
return review;
}
// Met à jour le contenu d'un commentaire, vérifie si l'utilisateur est autorisé à "mettre à jour" le commentaire
public Review updateReview(User user, int productId, int reviewId, String content) {
Product product = getProductById(productId); // Récupère le produit par son ID
Review review = product.getReviews().stream().filter(c -> c.getId().equals(reviewId))
.findFirst().orElseThrow(() -> new ResourceNotFoundException("Review with id " + reviewId + " not found"));
authorize(user, "update", review); // Vérifie si l'utilisateur peut mettre à jour le commentaire
review.setContent(content); // Met à jour le contenu du commentaire
return review;
}
// Supprime un commentaire, vérifie si l'utilisateur est autorisé à "supprimer" le commentaire
public void deleteReview(User user, int productId, int reviewId) {
Product product = getProductById(productId); // Récupère le produit par son ID
boolean isDeleted = product.getReviews().removeIf(review -> {
if (review.getId().equals(reviewId)) {
authorize(user, "delete", review); // Vérifie si l'utilisateur peut supprimer le commentaire
return true;
} else {
return false;
}
});
if (!isDeleted) {
throw new ResourceNotFoundException("Review with id " + reviewId + " not found");
}
try {
permit.api.resourceInstances.delete("review:" + reviewId); // Supprime l'instance de ressource du commentaire de Permit
} catch (IOException | PermitApiError | PermitContextError e) {
throw new RuntimeException(e);
}
}
}
Dans le code ci-dessous, la classe ProductController gère les demandes HTTP liées aux produits et à leurs avis. Elle expose des points de terminaison pour gérer les produits (comme créer
, mettre à jour
, supprimer
et récupérer
) et pour gérer les avis sur les produits.
package com.boostmytool.store.controllers;
import com.boostmytool.store.model.Product;
import com.boostmytool.store.model.Review;
import com.boostmytool.store.service.ProductService;
import io.permit.sdk.enforcement.User;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController // Indique que cette classe est un contrôleur REST Spring
@RequestMapping("/api/products") // URL de base pour tous les points de terminaison dans ce contrôleur
public class ProductController {
private final ProductService productService; // Instance de ProductService pour gérer les opérations liées aux produits
@Autowired // Injecte automatiquement le bean ProductService
public ProductController(ProductService productService) {
this.productService = productService;
}
// Requête GET pour récupérer tous les produits
@GetMapping
public List<Product> getAllProducts(HttpServletRequest request) {
User currentUser = (User) request.getAttribute("user"); // Obtient l'utilisateur authentifié à partir de la requête
return productService.getAllProducts(currentUser); // Appelle ProductService pour obtenir tous les produits de l'utilisateur
}
// Requête GET pour récupérer un produit par son ID
@GetMapping("/{id}")
public Product getProductById(HttpServletRequest request, @PathVariable("id") int id) {
User currentUser = (User) request.getAttribute("user"); // Obtient l'utilisateur authentifié à partir de la requête
return productService.getProduct(currentUser, id); // Appelle ProductService pour obtenir le produit par ID pour l'utilisateur
}
// Requête POST pour ajouter un nouveau produit
@PostMapping
@ResponseStatus(HttpStatus.CREATED) // Définit le statut de la réponse à 201 (Créé)
public Product addProduct(HttpServletRequest request, @RequestBody String content) {
User currentUser = (User) request.getAttribute("user"); // Obtient l'utilisateur authentifié à partir de la requête
return productService.addProduct(currentUser, content); // Appelle ProductService pour ajouter un nouveau produit
}
// Requête PUT pour mettre à jour un produit existant par son ID
@PutMapping("/{id}")
public Product updateProduct(HttpServletRequest request, @PathVariable("id") int id, @RequestBody String content) {
User currentUser = (User) request.getAttribute("user"); // Obtient l'utilisateur authentifié à partir de la requête
return productService.updateProduct(currentUser, id, content); // Appelle ProductService pour mettre à jour le produit par ID
}
// Requête DELETE pour supprimer un produit par son ID
@DeleteMapping("/{id}")
public String deleteProduct(HttpServletRequest request, @PathVariable("id") int id) {
User currentUser = (User) request.getAttribute("user"); // Obtient l'utilisateur authentifié à partir de la requête
productService.deleteProduct(currentUser, id); // Appelle ProductService pour supprimer le produit par ID
return "Deleted product with id " + id; // Renvoie un message de succès après la suppression
}
// Requête POST pour ajouter un nouvel avis à un produit par l'ID du produit
@PostMapping("/{id}/review")
public Review addReview(HttpServletRequest request, @PathVariable("id") int id, @RequestBody String content) {
User currentUser = (User) request.getAttribute("user"); // Obtient l'utilisateur authentifié à partir de la requête
return productService.addReview(currentUser, id, content); // Appelle ProductService pour ajouter un avis au produit
}
// Requête PUT pour mettre à jour un avis existant par l'ID du produit et de l'avis
@PutMapping("/{id}/review/{reviewId}")
public Review updateReview(HttpServletRequest request, @PathVariable("id") int id, @PathVariable("reviewId") int reviewId, @RequestBody String content) {
User currentUser = (User) request.getAttribute("user"); // Obtient l'utilisateur authentifié à partir de la requête
return productService.updateReview(currentUser, id, reviewId, content); // Appelle ProductService pour mettre à jour l'avis
}
// Requête DELETE pour supprimer un avis par l'ID du produit et de l'avis
@DeleteMapping("/{id}/review/{reviewId}")
public String deleteReview(HttpServletRequest request, @PathVariable("id") int id, @PathVariable("reviewId") int reviewId) {
User currentUser = (User) request.getAttribute("user"); // Obtient l'utilisateur authentifié à partir de la requête
productService.deleteReview(currentUser, id, reviewId); // Appelle ProductService pour supprimer l'avis
return "Deleted review with id " + reviewId + " from product " + id; // Renvoie un message de succès après la suppression
}
}
Étape 2 : Obtenez votre clé API de l’environnement
Dans l’interface de tableau de bord, copiez la clé API
de l’environnement actif.
Ensuite, ajoutez la clé API
de l’environnement et l’URL PDP
dans le fichier application.yaml
.
permit:
pdpUrl: 'http://localhost:7766'
apiKey: "Your Permit environment API Key"
Étape 3 : Déployer le Point de décision de politique (PDP)
Le Point de décision de politique (PDP) est déployé dans votre VPC et est chargé d’évaluer vos demandes d’autorisation. Le PDP assure une latence nulle, une performance exceptionnelle, une haute disponibilité et une sécurité améliorée.
Utilisez la commande ci-dessous pour tirer le conteneur Permit.io PDP de Docker
Hub.
docker pull permitio/pdp-v2:latest
Ensuite, exécutez le conteneur.
docker run -it -p 7766:7000 --env PDP_DEBUG=True --env PDP_API_KEY=<YOUR_API_KEY> permitio/pdp-v2:latest
Étape 4 : Exécution de l’application
Vous pouvez exécuter l’application en utilisant la commande Gradle
suivante :
./gradlew bootRun
Affichage et création de produits
Maintenant, interagissez avec les extrémités de l’application en utilisant REQBIN.
Tout d’abord, créez un nouvel utilisateur en utilisant l’extrémité /api/users/signup
.
curl -X POST "http://localhost:8080/api/users/signup" -H "Content-Type: application/json" -d 'johndoe'
Vous devraitz être capable de visualiser l’utilisateur dans votre projet Permit, sous Répertoire > Tous les locataires.
Au départ, l’utilisateur n’a pas de rôles, il ne peut donc pas grand-chose. Par exemple, essayez de lister les produits et vous obtiendrez une réponse 403 Interdit, comme illustré ci-dessous. Le code d’erreur 403 signifie que l’utilisateur n’a pas les permissions nécessaires pour accéder au ressource demandée, les produits dans ce cas. Vous pouvez en apprendre davantage sur la différence entre les codes d’erreur 401 et 403 ici.
Pour que l’utilisateur puisse afficher une liste de produits, affectez-le le rôle d’observateur en utilisant la commande ci-dessous:
curl -X POST "http://localhost:8080/api/users/assign-role" \
-H "Authorization: Bearer johndoe" \
-H "Content-Type: application/json" \
-d 'viewer'
Vous devriez voir que l’utilisateur johndoe
a été affecté au rôle d’observateur, comme illustré ci-dessous:
Comme un observateur peut créer un produit, utilisez la commande ci-dessous pour créer un produit avec l’utilisateur johndoe
.
curl -X POST "http://localhost:8080/api/products" -H "Authorization: Bearer johndoe" -H "Content-Type: application/json" -d 'MacBook'
Vous devriez voir qu’un nouveau produit est créé avec l’ID 1 et que l’utilisateur johndoe
a été ajouté en tant que fournisseur.
Ajouter des avis aux produits
Pour ajouter des avis aux produits, créez un autre utilisateur nommé jane
.
curl -X POST "http://localhost:8080/api/users/signup" -H "Content-Type: application/json" -d 'jane'
Pour que l’utilisateur puisse ajouter un avis aux produits, affectez-le le rôle d’observateur
en utilisant la commande ci-dessous:
curl -X POST "http://localhost:8080/api/users/assign-role" \
-H "Authorization: Bearer jane" \
-H "Content-Type: application/json" \
-d 'viewer'
Ensuite, vous pouvez ajouter un avis au produit ajouté par johndoe
en utilisant la commande ci-dessous:
curl -X POST "http://localhost:8080/api/products/1/review" -H "Authorization: Bearer jane" -H "Content-Type: application/json" -d 'The product was in good quality'
Félicitations ! Vous avez terminé le projet de ce didacticiel.
Prochaines étapes
Maintenant que vous avez appris comment implémenter une autorisation fine dans vos applications Java et Spring Boot en utilisant Permit.io, vous souhaitez peut-être explorer davantage.
Voici quelques ressources précieuses :
Avant de terminer
J’espère que vous avez trouvé ce tutorielclairvoyant.
Voici quelques autres articles de blog récents que vous pourriez aimer :
Pour plus de tutoriels sur des outils de développement impressionnants, n’oubliez pas de consulter mon blog DTA.
Suivez-moi sur Twitter pour avoir des mises à jour en direct sur mes autres projets.
Bon code.
Source:
https://www.freecodecamp.org/news/fine-grained-authorization-in-java-and-springboot/