La sicurezza dell’applicazione va ben oltre il semplice permesso o negazione di accesso a livello superficiale. Come sviluppatore, devi implementare l'autorizzazione granulare (FGA) per gestire i permessi a un livello più dettagliato e granulare.

FGA ti consente di impostare controlli di accesso dettagliati che specificano chi può fare cosa e sotto quali condizioni.

In questo tutorial, imparerai come implementare l'autorizzazione granulare in Java e Spring Boot usando Permit.io.

Ecco il codice sorgente (ricorda di darglielo un like ⭐).

Spero che ti sia piaciuto il mio precedente blog sulla costruzione di un’applicazione video conferenza personalizzata con Stream e Next.js. questi blog riflettono il mio percorso nella creazione di DevTools Academy, una piattaforma progettata per aiutare i developer a scoprire strumenti developer incredibili.

Questo tutorial è un altro sforzo per presentarti a un fantastico strumento developer che ho scoperto di recente.

Indice:

Cos’è Permit?

Permit.io è una soluzione full stack, ready to use, a livello di applicazione, che consente di implementare in pochi minuti una piattaforma sicura, flessibile, per il controllo dell'autorizzazione, cosicché puoi concentrarti sul ciò che è più importante.

Prerequisiti

Per comprendere appieno il tutorial, è necessario avere un’introduzione base alle Java e Spring Boot. Avrai anche bisogno di:

  • Permit.io: Uno strumento di sviluppo che semplifica l’implementazione dell’FGA.

  • Spring Boot Starter Web: Fornisce componenti essenziali per la costruzione di applicazioni web, inclusi API RESTful.

  • Gradle: Un strumento di compilazione per la gestione delle dipendenze.

  • JDK 11 o successiva: La versione del Java Development Kit richiesta per compilare e eseguire la tua app Spring Boot.

  • Postman o cURL: Strumenti per testare i tuoi endpoint API.

Cosa è l’autorizzazione fine-graffa?

L’autorizzazione fine-graffa offre un controllo d’accesso ai risorse determinando chi può accedervi, quanto e sotto quali condizioni specifiche.

Contrariamente all’autorizzazione a grana coarse (che gestisce l’accesso basandosi su categorie come ruoli utenti come “amministratore” o “utente“), l’autorizzazione a grana fine offre la flessibilità per definire l’accesso a un livello granulare, per risorse o azioni specifiche e persino attributi.

In Autorizzazione a Grana Fine esistono 3 tipi di modelli di politica per la gestione dell’autorizzazione; Controlo di Accesso Basato su Ruoli (RBAC), Controlo di Accesso Basato su Attributi (ABAC), e Controlo di Accesso Basato sulle Relazioni (ReBAC).

Consentiamoci di osservare ognuno di questi approcchi e vedere come li puoi implementare nella tua applicazione.

Controlo di Accesso Basato su Ruoli (RBAC)

RBAC è un approcio alla sicurezza che gestisce l’accesso ai risorse in base ai ruoli degli utenti all’interno di un’organizzazione. Questo modello semplifica i permessi organizzando gli utenti in ruoli e gestendo il controllo dell’accesso in base a questi ruoli definiti.

Concetti Chiave in RBAC:

Utenti: Persone che utilizzano il sistema come dipendenti o clienti.

Ruoli: Un insieme di permessi o privilegi di accesso assegnati a un gruppo di utenti in base alle loro responsabilità o compiti come amministratore, responsabile o cliente.

Permessi: I diritti concessi agli utenti per interagire con le risorse, come leggere, scrivere o eliminare.

Controlo di Accesso Basato su Attributi (ABAC)

ABAC è un modello di controllo degli accessi versatile e adattivo che decide chi può o non può accedere alle risorse in base agli attributi, come i dettagli dell’utente. Il modello ABAC consente di definire autorizzazioni dettagliate basate sugli attributi dell’utente.

Concetti chiave in ABAC:

Attributi: Caratteristiche o proprietà utilizzate per prendere decisioni sul controllo degli accessi. Gli attributi sono tipicamente categorizzati in:

  • Attributi dell’utente: Informazioni sull’utente (ad esempio, ruolo, dipartimento, titolo di lavoro, età e così via).

  • Attributi della risorsa: Caratteristiche della risorsa (ad esempio, tipo di file, livello di classificazione dei dati, data di creazione, proprietario).

  • Attributi dell’azione: L’azione che l’utente sta cercando di eseguire (ad esempio, leggere, scrivere, eliminare, approvare).

  • Attributi ambientali: Informazioni contestuali sulla richiesta di accesso (ad esempio, ora del giorno, posizione, tipo di dispositivo, indirizzo IP).

Controllo d’Accesso Basato sulle Relazioni (ReBAC)

ReBAC è un sistema di controllo d’accesso che concede permessi per accedere alle risorse in base alle relazioni tra le entità all’interno del sistema. L’approcchio enfatizza la definizione e la gestione del controllo d’accesso tramite la mappatura delle relazioni tra utenti e risorse e altre entità, come organizzazioni o gruppi.

Concezioni Chiave di ReBAC:

Entità: Utenti, risorse (come file e documenti) e altre entità, come gruppi o unità organizzative.

Relazioni: Le connessioni che specificano la relazione tra due entità. Ad esempio, un utente può essere il “proprietario” di un documento o un “membro” di un team.

Politiche: Regole che usano le relazioni per determinare i diritti d’accesso. Un utente può accedere a una risorsa o eseguire un’azione su di essa se ha una particolare relazione con essa.

Come Implementare un Autorizzazione Granulare

Ora che hai una base di conoscenza di RBAC, ABAC e ReBAC, vediamo come possiamo implementare questi modelli in una applicazione e-commerce.

Implementazione del Controllo d’Accesso Basato sui Ruoli

Step 1: Vai a Permit.io, crea un account e il tuo spazio di lavoro.

Per default, dovreste vedere un progetto che include due ambienti: Sviluppo e Produzione.

💡
Note: You need to define and test your policies in the development environment before deploying them to production.

Step 2: Crea una risorsa denominata Prodotti. Per creare la risorsa, apri la scheda Politiche nella barra laterale sinistra e poi apri la scheda Risorse in alto. Successivamente, clicca sul pulsante Crea una Risorsa e crea una risorsa chiamata Prodotti con le azioni read, create, update, e delete.

Step 3: Crea un’altra risorsa chiamata Recensioni con le azioni read, create, update, e delete.

Step 4: Apri la scheda Editor Politiche. Vedere che sono state create 3 ruoli chiamati admin, editor, e viewer.

  • Il ruolo admin ha il permesso di create, delete, read, o update un prodotto o una recensione.

  • Il ruolo editor ha il permesso di create, read, o update un prodotto o una recensione ma non di delete alcuno.

  • Ruolo viewer ha il permesso di creare e leggere un prodotto o una recensione, ma non di eliminare o aggiornare alcuno.

Implementazione del Controllo di Accesso Basato su Attributi

Passo 1: Apri la scheda Risorse, poi clicca il pulsante Aggiungi Attributi

  • Aggiungi un attributo chiamato fornitore

  • Aggiungi un attributo chiamato il cliente

Passo 2: Apri la scheda Regole ABAC, poi crea un nuovo Set di Risorse ABAC chiamato Prodotti Propri che dipende dalla risorsa Prodotti. Dopo di ciò, aggiungi una condizione che concede i permessi solo all’utente che ha creato un prodotto in base all’attributo fornitore.

Passo 3: Crea un altro Set di Risorse ABAC chiamato Recensioni Proprie che dipende dalla risorsa Recensioni.

Implementazione del Controllo di Accesso Basato sulle Relazioni

Passo 1: Apri la scheda Risorse e modifica la risorsa Prodotti. Aggiungi il ruolo fornitore nella sezione Opzioni ReBAC. Poi imposta i prodotti come genitori delle recensioni nella sezione relazioni.

Passo 2: Modifica il risorsa Recensioni aggiungendo il ruolo cliente nelle opzioni ReBAC, come mostrato qui sotto:

Passo 3: Vai alla scheda Policy Editor e aggiungi:

  • ruolo venditore permesso di aggiornare e eliminare i propri prodotti.

  • ruolo cliente permesso di aggiornare e eliminare le proprie recensioni sui prodotti.

Come implementare FGA in Java e SpringBoot

Adesso che abbiamo definito le politiche RBAC, ABAC e ReBAC nella interfaccia web Permit.io, impariamo come enforcerle in un’applicazione di gestione del sistema di e-commerce utilizzando l’API Permit.io.

Arriverà un sacco di codice, quindi assicurati di leggere le estese note che ho lasciato in ogni blocco di codice. queste vi aiuteranno a comprendere appieno cosa sta succedendo in questo codice.

Passo 1: Configurazione dell’applicazione e-commerce

Per configurare l’applicazione e-commerce e git clone il codice sorgente.

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.

Installazione del pacchetto SDK Permit

Per installare il pacchetto SDK Permit, aggiungi l’SDK sotto il blocco dipendenze nel file 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'

    // Aggiungi questa riga per installare il SDK Java Permit.io nel tuo progetto
    implementation 'io.permit:permit-sdk-java:2.0.0'
}

Inizializzazione dell’SDK Permit

Puoi inizializzare il client SDK Permit usando il codice qui sotto:

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  // Marca questa classe come una classe di configurazione per Spring IoC
public class PermitClientConfig {

    @Value("${permit.api-key}")  // Inject chiave API Permit da proprietà dell'applicazione
    private String apiKey;

    @Value("${permit.pdp-url}")  // Inject URL PDP (Policy Decision Point) Permit da proprietà dell'applicazione
    private String pdpUrl;

    /**
     * Crea una istanza del client Permit con configurazione personalizzata
     * @return Istanza del client Permit
     */
    @Bean
    public Permit permit() {
        return new Permit(
                new PermitConfig.Builder(apiKey)  // Inizializza PermitConfig con chiave API
                        .withPdpAddress(pdpUrl)   // Imposta l'indirizzo del PDP
                        .withDebugMode(true)      // Abilita la modalità debug per log di dettaglio
                        .build()                  // Costruisci l'oggetto PermitConfig
        );
    }
}

Sincronizzazione Utenti con l’SDK

Per iniziare ad applicare i permessi, devi prima sincronizzare un utente con Permit, e poi assegnargli un ruolo.

Nel codice qui sotto, la classe UserService fornisce metodi per il login utente, l’iscrizione, l’assegnazione del ruolo e l’autorizzazione, con il handling di eccezioni per eventuali errori durante l’interazione con 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  // Marca questa classe come servizio Spring, rendendola un candidato per lo scansione componenti
public class UserService {
    private final Permit permit;

    // Iniezione costruttore per il SDK Permit
    public UserService(Permit permit) {
        this.permit = permit;
    }

    /**
     * Simula il login utente creando e restituendo un oggetto utente Permit.
     * 
     * @param key Chiave unica utente
     * @return Oggetto utente
     */
    public Object login(String key) {
        return new User.Builder(key).build();
    }

    /**
     * Gestisce l'iscrizione utente creando e sincronizzando un nuovo utente Permit.
     * 
     * @param key Chiave unica utente
     * @return Creato e sincronizzato Oggetto utente
     */
    public User signup(String key) {
        var user = new User.Builder(key).build();
        try {
            permit.api.users.sync(user);  // Sincronizza il nuovo utente con il servizio Permit
        } catch (PermitContextError | PermitApiError | IOException e) {
            throw new RuntimeException("Failed to create user", e);  // Gestisce eccezioni durante la creazione utente
        }
        return user;
    }

    /**
     * Assegna un ruolo all'utente all'interno dell'ambiente "default".
     * 
     * @param user Oggetto utente da assegnare il ruolo
     * @param role Ruolo da assegnare
     */
    public void assignRole(User user, String role) {
        try {
            permit.api.users.assignRole(user.getKey(), role, "default");  // Assegna ruolo nell'ambiente "default"
        } catch (PermitApiError | PermitContextError | IOException e) {
            throw new RuntimeException("Failed to assign role to user", e);  // Gestisce eccezioni durante l'assegnazione ruoli
        }
    }

    /**
     * Verifica se l'utente è autorizzato a eseguire una specifica azione su un risorsa.
     * 
     * @param user Oggetto utente richiedente autorizzazione
     * @param action Azione da autorizzare
     * @param resource Risorsa sulla quale verrà eseguita l'azione
     * @throws UnauthorizedException se l'utente non è loggato
     * @throws ForbiddenAccessException se l'utente è negato l'accesso
     */
    public void authorize(User user, String action, Resource resource) {
        if (user == null) {
            throw new UnauthorizedException("Not logged in");  // Lancia eccezione se l'utente non è loggato
        }
        try {
            var permitted = permit.check(user, action, resource);  // Effettua il controllo autorizzazione
            if (!permitted) {
                throw new ForbiddenAccessException("Access denied");  // Lancia eccezione se l'accesso è negato
            }
        } catch (PermitApiError | IOException e) {
            throw new RuntimeException("Failed to authorize user", e);  // Gestisce eccezioni durante l'autorizzazione
        }
    }
}

Nel codice seguente, la classe UserController espone endpoint REST API per l’iscrizione utente e l’assegnazione di ruoli. Interagisce con la classe UserService per gestire il business logico utente e fornisce risposte HTTP appropriate.

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  // Indica che questa classe gestisce richieste HTTP e restituisce risposte JSON
@RequestMapping("/api/users")  // Percorso base URL per tutte le operazioni relative agli utenti
public class UserController {
    private final UserService userService;

    // Iniezione di costruttore di UserService, contenente il business logico per le operazioni utente
    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * Gestisce le richieste di iscrizione utente.
     * Endpoint: POST /api/users/signup
     * 
     * @param key Chiave univoca per il nuovo utente
     * @return Oggetto Utente creato
     */
    @PostMapping("/signup")
    public User signup(@RequestBody String key) {
        return userService.signup(key);  // Chiama il metodo signup in UserService per creare un nuovo utente
    }

    /**
     * Gestisce l'assegnazione di un ruolo all'utente loggato.
     * Endpoint: POST /api/users/assign-role
     * 
     * @param request Richiesta HTTP, usata per recuperare l'utente corrente
     * @param role Ruolo da assegnare all'utente corrente
     */
    @PostMapping("/assign-role")
    public void assignRole(HttpServletRequest request, @RequestBody String role) {
        // Recupera l'utente corrente dai dati della richiesta
        User currentUser = (User) request.getAttribute("user");

        // Lancia un'eccezione se l'utente non è loggato
        if (currentUser == null) {
            throw new UnauthorizedException("Not logged in");
        }

        // Assegna il ruolo specificato all'utente corrente
        userService.assignRole(currentUser, role);
    }
}

Creazione di un Punto di Enforceamento di Politiche di RBAC, ABAC e ReBAC

Nel codice seguente, la classe ProductService gestisce le operazioni CRUD per i prodotti e le recensioni, affrontando i permessi e i ruoli tramite l’API Permit.

Ogni operazione include controlli di autorizzazione utente, con la gestione appropriata di eccezioni per gli errori dell’API Permit e per i casi di risorse non trovate.

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  // Segna questa classe come un servizio Spring
public class ProductService {

    private final List<Product> products = new ArrayList<>();  // Lista in memoria per memorizzare i prodotti
    private final AtomicInteger productIdCounter = new AtomicInteger();  // Contatore per generare ID unici per i prodotti
    private final AtomicInteger reviewIdCounter = new AtomicInteger();   // Contatore per generare ID unici per le recensioni

    // Builder per istanze di risorse Permit (prodotto e recensione)
    private final Resource.Builder productResourceBuilder = new Resource.Builder("product");
    private final Resource.Builder reviewResourceBuilder = new Resource.Builder("review");

    private final UserService userService;  // Servizio per gestire le operazioni relative agli utenti
    private final Permit permit;  // Istanza SDK di Permit per gestire l'autorizzazione e la gestione delle risorse

    // Costruttore per iniettare le dipendenze
    public ProductService(UserService userService, Permit permit) {
        this.userService = userService;
        this.permit = permit;
    }

    // Metodo per autorizzare un utente per una determinata azione su una risorsa
    private void authorize(User user, String action, Resource resource) {
        userService.authorize(user, action, resource);
    }

    // Autorizza un utente a eseguire un'azione su un prodotto specifico
    private void authorize(User user, String action, Product product) {
        var attributes = new HashMap<String, Object>();
        attributes.put("vendor", product.getVendor());  // Aggiunge l'attributo del venditore al prodotto
        userService.authorize(user, action, productResourceBuilder.withKey(product.getId().toString()).withAttributes(attributes).build());
    }

    // Autorizza un utente a eseguire un'azione su una recensione specifica
    private void authorize(User user, String action, Review review) {
        var attributes = new HashMap<String, Object>();
        attributes.put("customer", review.getCustomer());  // Aggiunge l'attributo del cliente alla recensione
        userService.authorize(user, action, reviewResourceBuilder.withKey(review.getId().toString()).withAttributes(attributes).build());
    }

    // Recupera un prodotto tramite il suo ID, genera un'eccezione se non trovato
    private Product getProductById(int id) {
        return products.stream().filter(product -> product.getId().equals(id))
                .findFirst().orElseThrow(() -> new ResourceNotFoundException("Product with id " + id + " not found"));
    }

    // Recupera tutti i prodotti, verifica se l'utente è autorizzato a "leggere" i prodotti
    public List<Product> getAllProducts(User user) {
        authorize(user, "read", productResourceBuilder.build());  // L'utente deve avere il permesso di "lettura"
        return new ArrayList<>(products);  // Restituisce una copia della lista dei prodotti
    }

    // Recupera un prodotto tramite il suo ID, verifica se l'utente è autorizzato a "leggere" il prodotto
    public Product getProduct(User user, int id) {
        authorize(user, "read", productResourceBuilder.build());
        return getProductById(id);
    }

    // Aggiunge un nuovo prodotto, autorizza l'utente e crea istanze di risorse e assegnazioni di ruoli in Permit
    public Product addProduct(User user, String content) {
        authorize(user, "create", productResourceBuilder.build());  // Verifica se l'utente può creare un prodotto
        Product product = new Product(productIdCounter.incrementAndGet(), user.getKey(), content);  // Crea nuovo prodotto

        try {
            // Crea istanza di risorsa in Permit e assegna il ruolo di "venditore" all'utente per questo prodotto
            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);  // Aggiunge il prodotto alla lista in memoria
        return product;
    }

    // Aggiorna il contenuto di un prodotto, verifica se l'utente è autorizzato ad "aggiornare" il prodotto
    public Product updateProduct(User user, int id, String content) {
        Product product = getProductById(id);  // Recupera il prodotto tramite il suo ID
        authorize(user, "update", product);  // Verifica se l'utente può aggiornare il prodotto
        product.setContent(content);  // Aggiorna il contenuto del prodotto
        return product;
    }

    // Elimina un prodotto, verifica se l'utente è autorizzato ad "eliminare" il prodotto
    public void deleteProduct(User user, int id) {
        boolean isDeleted = products.removeIf(product -> {
            if (product.getId().equals(id)) {
                authorize(user, "delete", product);  // Verifica se l'utente può eliminare il prodotto
                return true;
            } else {
                return false;
            }
        });

        if (!isDeleted) {
            throw new ResourceNotFoundException("Product with id " + id + " not found");
        }

        try {
            permit.api.resourceInstances.delete("product:" + id);  // Rimuove l'istanza di risorsa del prodotto da Permit
        } catch (IOException | PermitApiError | PermitContextError e) {
            throw new RuntimeException(e);
        }
    }

    // Aggiunge una recensione a un prodotto, crea un'istanza di risorsa e una relazione in Permit
    public Review addReview(User user, int productId, String content) {
        authorize(user, "create", reviewResourceBuilder.build());  // Verifica se l'utente può creare una recensione
        Product product = getProductById(productId);  // Recupera il prodotto tramite il suo ID
        Review review = new Review(reviewIdCounter.incrementAndGet(), user.getKey(), content);  // Crea nuova recensione

        try {
            // Crea un'istanza di risorsa per la recensione e imposta la relazione con il prodotto
            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);  // Aggiunge la recensione al prodotto
        return review;
    }

    // Aggiorna il contenuto di una recensione, verifica se l'utente è autorizzato ad "aggiornare" la recensione
    public Review updateReview(User user, int productId, int reviewId, String content) {
        Product product = getProductById(productId);  // Recupera il prodotto tramite il suo 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);  // Verifica se l'utente può aggiornare la recensione
        review.setContent(content);  // Aggiorna il contenuto della recensione
        return review;
    }

    // Elimina una recensione, verifica se l'utente è autorizzato ad "eliminare" la recensione
    public void deleteReview(User user, int productId, int reviewId) {
        Product product = getProductById(productId);  // Recupera il prodotto tramite il suo ID
        boolean isDeleted = product.getReviews().removeIf(review -> {
            if (review.getId().equals(reviewId)) {
                authorize(user, "delete", review);  // Verifica se l'utente può eliminare la recensione
                return true;
            } else {
                return false;
            }
        });

        if (!isDeleted) {
            throw new ResourceNotFoundException("Review with id " + reviewId + " not found");
        }

        try {
            permit.api.resourceInstances.delete("review:" + reviewId);  // Rimuove l'istanza di risorsa della recensione da Permit
        } catch (IOException | PermitApiError | PermitContextError e) {
            throw new RuntimeException(e);
        }
    }
}

Nel codice seguente, la classe ProductController si occupa di gestire le richieste HTTP relative ai prodotti e ai loro recensimenti. Esposizione degli endpoint per la gestione dei prodotti (come creando, aggiornando, eliminando, e recuperando) e per la gestione delle recensioni dei prodotti.

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  // Indica che questa classe è un controller REST Spring
@RequestMapping("/api/products")  // URL di base per tutti gli endpoint in questo controller
public class ProductController {

    private final ProductService productService;  // Istanza di ProductService per gestire le operazioni relative ai prodotti

    @Autowired  // Autowires automaticamente il bean ProductService
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    // Richiesta GET per recuperare tutti i prodotti
    @GetMapping
    public List<Product> getAllProducts(HttpServletRequest request) {
        User currentUser = (User) request.getAttribute("user");  // Ottiene l'utente autenticato dalla richiesta
        return productService.getAllProducts(currentUser);  // Chiama ProductService per ottenere tutti i prodotti per l'utente
    }

    // Richiesta GET per recuperare un prodotto tramite il suo ID
    @GetMapping("/{id}")
    public Product getProductById(HttpServletRequest request, @PathVariable("id") int id) {
        User currentUser = (User) request.getAttribute("user");  // Ottiene l'utente autenticato dalla richiesta
        return productService.getProduct(currentUser, id);  // Chiama ProductService per ottenere il prodotto tramite ID per l'utente
    }

    // Richiesta POST per aggiungere un nuovo prodotto
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)  // Imposta lo stato della risposta su 201 (Creato)
    public Product addProduct(HttpServletRequest request, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // Ottiene l'utente autenticato dalla richiesta
        return productService.addProduct(currentUser, content);  // Chiama ProductService per aggiungere un nuovo prodotto
    }

    // Richiesta PUT per aggiornare un prodotto esistente tramite il suo ID
    @PutMapping("/{id}")
    public Product updateProduct(HttpServletRequest request, @PathVariable("id") int id, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // Ottiene l'utente autenticato dalla richiesta
        return productService.updateProduct(currentUser, id, content);  // Chiama ProductService per aggiornare il prodotto tramite ID
    }

    // Richiesta DELETE per eliminare un prodotto tramite il suo ID
    @DeleteMapping("/{id}")
    public String deleteProduct(HttpServletRequest request, @PathVariable("id") int id) {
        User currentUser = (User) request.getAttribute("user");  // Ottiene l'utente autenticato dalla richiesta
        productService.deleteProduct(currentUser, id);  // Chiama ProductService per eliminare il prodotto tramite ID
        return "Deleted product with id " + id;  // Restituisce un messaggio di successo dopo l'eliminazione
    }

    // Richiesta POST per aggiungere una nuova recensione a un prodotto tramite l'ID del prodotto
    @PostMapping("/{id}/review")
    public Review addReview(HttpServletRequest request, @PathVariable("id") int id, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // Ottiene l'utente autenticato dalla richiesta
        return productService.addReview(currentUser, id, content);  // Chiama ProductService per aggiungere una recensione al prodotto
    }

    // Richiesta PUT per aggiornare una recensione esistente tramite l'ID del prodotto e della recensione
    @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");  // Ottiene l'utente autenticato dalla richiesta
        return productService.updateReview(currentUser, id, reviewId, content);  // Chiama ProductService per aggiornare la recensione
    }

    // Richiesta DELETE per eliminare una recensione tramite l'ID del prodotto e della recensione
    @DeleteMapping("/{id}/review/{reviewId}")
    public String deleteReview(HttpServletRequest request, @PathVariable("id") int id, @PathVariable("reviewId") int reviewId) {
        User currentUser = (User) request.getAttribute("user");  // Ottiene l'utente autenticato dalla richiesta
        productService.deleteReview(currentUser, id, reviewId);  // Chiama ProductService per eliminare la recensione
        return "Deleted review with id " + reviewId + " from product " + id;  // Restituisce un messaggio di successo dopo l'eliminazione
    }
}

Step 2: Recupera la tua chiave API dell’ambiente

Nel pannello di controllo dell’interfaccia utente, copia la chiave API Environment dell’ambiente attivo.

Successivamente, aggiungi la chiave API e l’URL PDP dell’ambiente nel file application.yaml.

permit:
  pdpUrl: 'http://localhost:7766'
  apiKey: "Your Permit environment API Key"

Step 3: Distribuisci il Punto di Decisione Policy (PDP)

Il Punto di Decisione Policy (PDP) è distribuito nel tuo VPC ed è responsabile dell’evaluazione delle richieste di autorizzazione. Il PDP garantirà una latenza zero, un ottimo rendimento, una elevata disponibilità e una migliore sicurezza.

Usa il comando seguente per estrarre il contenitore Permit.io PDP da Docker Hub.

docker pull permitio/pdp-v2:latest

Poi esegui il contenitore.

docker run -it -p 7766:7000 --env PDP_DEBUG=True --env PDP_API_KEY=<YOUR_API_KEY> permitio/pdp-v2:latest

Step 4: Esecuzione dell’applicazione

Puoi eseguire l’applicazione usando il seguente comando Gradle:

./gradlew bootRun

Visualizzazione e creazione di prodotti

Ora interagiamo con gli endpoint dell’applicazione usando REQBIN.

Prima di tutto, crea un nuovo utente usando l’endpoint /api/users/signup.

curl -X POST "http://localhost:8080/api/users/signup" -H "Content-Type: application/json" -d 'johndoe'

Dovresti essere in grado di visualizzare l’utente nel tuo progetto Permit, sotto Directory > Tutti i tenant.

Inizialmente, l’utente non ha ruoli, quindi non può fare molto. Ad esempio, cercando di elencare i prodotti risulterà in una risposta 403 Proibito come mostrato qui sotto. Il codice errore 403 significa che l’utente non ha i permessi per accedere alla risorsa richiesta, ovvero i prodotti in questo caso. È possibile leggere di più sul differenza tra i codici di errore 401 e 403 qui.

Per consentire all’utente di visualizzare un elenco di prodotti, assegnargli il ruolo di visualizzatore usando il comando seguente:

curl -X POST "http://localhost:8080/api/users/assign-role" \
-H "Authorization: Bearer johndoe" \
-H "Content-Type: application/json" \
-d 'viewer'

Dovreste vedere che l’utente johndoe è stato assegnato al ruolo di visualizzatore, come mostrato qui sotto:

Prendendo in considerazione che un visualizzatore può creare un prodotto, usate il comando seguente per creare un prodotto con l’utente johndoe.

curl -X POST "http://localhost:8080/api/products" -H "Authorization: Bearer johndoe" -H "Content-Type: application/json" -d 'MacBook'

Dovreste vedere che è stato creato un nuovo prodotto con ID 1 e che l’utente johndoe è stato aggiunto come fornitore.

Aggiunta Recensioni ai Prodotti

Per aggiungere recensioni ai prodotti, create un altro utente chiamato jane.

curl -X POST "http://localhost:8080/api/users/signup" -H "Content-Type: application/json" -d 'jane'

Per consentire all’utente di aggiungere una recensione ai prodotti, assegnargli il ruolo di viewer usando il comando seguente:

curl -X POST "http://localhost:8080/api/users/assign-role" \
-H "Authorization: Bearer jane" \
-H "Content-Type: application/json" \
-d 'viewer'

Poi puoi aggiungere una recensione al prodotto aggiunto da johndoe usando il comando seguente:

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'

Congratulazioni! Hai completato il progetto per questo tutorial.

Prossimi Passi

Ora che hai imparato come implementare l’autorizzazione granulare nei tuoi applicazioni Java e Spring Boot usando Permit.io, potresti voler approfondire.

Qui ci sono alcune risorse preziose:

Prima di concludere

Spero che questo tutorial vi sia stato utile.

Ecco alcuni dei miei altri articoli blog recenti che potreste apprezzare:

Per altri tutorial su strumenti di sviluppo incredibili, assicurati di visitare il mio blog DTA.

Seguimi su Twitter per ricevere aggiornamenti in tempo reale sui miei altri progetti.

Codifica felice.