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.

💡
Note: You need to define and test your policies in the development environment before deploying them to 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 ou mettre à jour un produit ou un avis.

  • Le rôle éditeur a le droit de créer, lire ou mettre à jour un produit ou un avis mais ne peut pas supprimer.

  • Le rôle viewer a l’autorisation de créer et de lire un produit ou une critique mais pas de supprimer ou de mettre à 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
💡
Then open the code in your Java IDE. I used JetBrains for all my work.

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.