A segurança da sua aplicação transcende conceder ou negar acesso apenas no nível superficial. Como desenvolvedor, você precisa implementar autorização de granularidade fina (FGA) para gerenciar permissões a um nível mais detalhado, granular.

A FGA permite que você configure controles de acesso detalhados que especificam quem pode fazer o que e sob quais condições.

Neste tutorial, você aprenderá a implementar autorização de granularidade fina em Java e Spring Boot usando Permit.io.

Aqui está o código fonte (lembrando de deixar uma estrela ⭐).

Espero que você tenha gostado do meu anterior blog sobre construir uma aplicação de vídeo conferência personalizada com Stream e Next.js. Estes blogs refletem minha jornada em criar a DevTools Academy, uma plataforma projetada para ajudar desenvolvedores a descobrir ferramentas de desenvolvimento incríveis.

Este tutorial é outro esforço para apresentar-vos uma ferramenta de desenvolvedor muito útil que explorei recentemente.

Sumário:

O que é Permitir?

Permit.io é uma solução de autorização de nível de aplicativo full stack, pronta a usar, que permite que você implemente uma camada de autorização flexível e segura em poucos minutos, para que você possa concentrar-se no que importa mais.

Pré-requisitos

Para entender o tutorial por completo, você precisa ter um entendimento básico de Java e Spring Boot. Você também precisará dos seguintes:

  • Permit.io: Uma ferramenta de desenvolvimento que simplifica a implementação de FGA.

  • Spring Boot Starter Web: Fornece componentes básicos para a construção de aplicações web, incluindo APIs RESTful.

  • Gradle: Ferramenta de compilação para gerenciar dependências.

  • JDK 11 ou posterior: Versão do Kit de Desenvolvimento Java necessária para compilar e executar sua aplicação Spring Boot.

  • Postman ou cURL: Ferramentas para testar seus pontos finais de API.

O que é Autorização Finemente Granular?

Autorização finemente granular oferece controle de acesso a recursos determinando quem pode acessá-los, em que extensão e sob quais condições específicas.

Contrário à autorização de grãos grossos (que trata do acesso baseado em categorias como funções de usuário tais como “admin” ou “usuário“), a autorização de grãos finos oferece flexibilidade para definir o acesso a um nível granular, para recursos ou ações específicas e até mesmo atributos.

Na Autorização de Grãos Finos existem 3 tipos de modelos de política para gerenciar autorização; Controlo de Acesso baseado em Funções (RBAC), Controlo de Acesso baseado em Atributos (ABAC), e Controlo de Acesso baseado em Relacionamentos (ReBAC).

Vamos olhar para cada uma destas abordagens e ver como você pode implementá-las em seu aplicativo.

Controlo de Acesso baseado em Funções (RBAC)

RBAC é uma abordagem de segurança que controla o acesso a recursos baseado nas funções dos usuários dentro de uma organização. Este modelo padroniza as permissões organizando os usuários em funções e gerenciando o controle de acesso de acordo com estas funções definidas.

Conceitos Chave em RBAC:

Usuários: Pessoas que usam o sistema, como funcionários ou clientes.

Funções: Um conjunto de permissões ou privilégios de acesso atribuídos a um grupo de usuários com base nas suas responsabilidades ou tarefas, como admin, gerente ou cliente.

Permissões: Os direitos concedidos aos usuários para interagir com recursos, como ler, escrever ou excluir.

Controlo de Acesso baseado em Atributos (ABAC)

ABAC é um modelo de controle de acesso versátil e adaptável que decide quem pode ou não acessar recursos baseado em atributos, como detalhes do usuário. O modelo ABAC permite que você defina autorização granular com base em atributos de usuário.

Conceitos Chave em ABAC:

Atributos: Características ou propriedades usadas para tomar decisões de controle de acesso. Atributos são normalmente categorizados em:

  • Atributos de Usuário: Informações sobre o usuário (por exemplo, papel, departamento, cargo, idade, e assim por diante).

  • Atributos de Recurso: Características do recurso (por exemplo, tipo de arquivo, nível de classificação de dados, data de criação, proprietário).

  • Atributos de Ação: A ação que o usuário está tentando realizar (por exemplo, ler, escrever, excluir, aprovar).

  • Atributos do Ambiente: Informações contextuais sobre a solicitação de acesso (por exemplo, hora do dia, localização, tipo de dispositivo, endereço IP).

Controle de Acesso Baseado em Relacionamentos (ReBAC)

O ReBAC é um sistema de controle de acesso que concede permissões para acessar recursos com base no relacionamento entre entidades dentro de um sistema. O enfoque dá importância à definição e gerenciamento de controle de acesso, mapear como usuários se relacionam com recursos e outras entidades, como organizações ou grupos.

Conceitos Chave do ReBAC:

Entidades: Usuários, recursos (como arquivos e documentos) e outras entidades, como grupos ou unidades organizacionais.

Relações: As conexões que especificam o relacionamento entre duas entidades. Um usuário pode ser o “proprietário” de um documento ou um “membro” de uma equipe, por exemplo.

Políticas: Regras que usam relacionamentos para determinar direitos de acesso. Um usuário pode acessar um recurso ou executar uma ação nele se tiver um relacionamento particular com ele.

Como Implementar Autorização Finamente Granular

Agora que você tem um entendimento básico de RBAC, ABAC e ReBAC, vamos ver como podemos implementar esses modelos em uma aplicação de e-commerce.

Implementando Controle de Acesso Baseado em Papéis

Passo 1: Vá para Permit.io e crie uma conta e seu workspace.

Por padrão, você deve ver um projeto que inclui dois ambientes: Desenvolvimento e Produção.

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

Passo 2: Crie um recurso chamado Produtos. Para criar o recurso, abra a Política na barra lateral esquerda e então abra a Recursos no topo. Depois disso, clique no Criar um Recurso e crie um recurso chamado Produtos com ações leitura, criação, atualização e exclusão.

Passo 3: Crie outro recurso chamado Avaliações com ações leitura, criação, atualização e exclusão.

Passo 4: Abra a Editora de Política na aba. Você verá que 3 papéis chamados admin, editor e visualizador foram criados.

  • O papel admin tem permissão para criação, exclusão, leitura ou atualização de um produto ou uma avaliação.

  • O papel editor tem permissão para criação, leitura ou atualização de um produto ou uma avaliação mas não para exclusão de nenhuma.

  • O papel visualizador tem permissão para criar e ler um produto ou uma resenha, mas não para excluir ou atualizar qualquer coisa.

Implementando Controle de Acesso baseado em Atributos

Passo 1: Abra o separador Recursos, depois clique no botão Adicionar Atributos.

  • Adicione um atributo chamado fornecedor

  • Adicione um atributo chamado o cliente

Passo 2: Abra o separador Regras de Controle de Acesso baseado em Atributos (ABAC), depois crie um novo conjunto de recursos ABAC chamado Produtos Próprios que dependa do recurso Produtos. Depois, adicione uma condição que dê permissão apenas para o usuário que criou um produto baseado no atributo fornecedor.

Passo 3: Crie outro conjunto de recursos ABAC chamado Resenhas Próprias que dependa do recurso Resenhas.

Implementando Controle de Acesso baseado em Relacionamentos

Passo 1: Abra o separador Recursos e edite o recurso Produtos. Adicione o papel fornecedor nas opções do ReBAC e depois defina os produtos como pai das resenhas na seção de relações.

Passo 2: Edite o recurso de avaliações adicionando o papel de cliente nas opções do ReBAC, conforme mostrado abaixo:

Passo 3: Vá para a aba Política Editor e adicione:

  • papel vendedor permissão para atualizar e excluir seus próprios produtos.

  • papel cliente permissão para atualizar e excluir suas próprias avaliações sobre produtos.

Como implementar FGA em Java e SpringBoot

Agora que definimos as políticas de RBAC, ABAC e ReBAC na interface web do Permit.io, vamos aprender como aplicá-las em um aplicativo de Gerenciamento de Loja Virtual usando a API do Permit.io.

Vão aparecer muito código, então certifique-se de ler as extensivas anotações deixadas em cada bloco de código. Essas anotações ajudarão você a entender melhor o que está acontecendo neste código.

Passo 1: Configurando o Aplicativo de E-commerce

Para configurar o aplicativo de e-commerce e fazer o git clone do código fonte.

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.

Instalando o pacote SDK do Permit

Para instalar o SDK do Permit, você adiciona o SDK sob o bloco de dependências no arquivo build.gradle.

## 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'

    // Adicione esta linha para instalar o SDK do Permit.io Java em seu projeto
    implementation 'io.permit:permit-sdk-java:2.0.0'
}

Inicializando o SDK de Permissão

Você pode inicializar o Cliente do SDK de Permissão usando o código abaixo:

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 essa classe como uma classe de configuração para o Spring IoC
public class PermitClientConfig {

    @Value("${permit.api-key}")  // Injeção da chave API do Permit das propriedades de aplicação
    private String apiKey;

    @Value("${permit.pdp-url}")  // Injeção da URL do PDP (Ponto de Decisão de Política) do Permit das propriedades de aplicação
    private String pdpUrl;

    /**
     * Cria uma instância de bean de cliente do Permit com configuração customizada
     * @return Instância do cliente do Permit
     */
    @Bean
    public Permit permit() {
        return new Permit(
                new PermitConfig.Builder(apiKey)  // Inicializar PermitConfig com a chave API
                        .withPdpAddress(pdpUrl)   // Definir o endereço do PDP
                        .withDebugMode(true)      // Ativar o modo de depuração para log de detalhes
                        .build()                  // Construir o objeto PermitConfig
        );
    }
}

Sincronizando Usuários com o SDK

Para começar a aplicar permissões, você deve primeiro sincronizar um usuário com o Permit, e então atribuir-lhe um papel.

No código abaixo, a classe UserService fornece métodos para login de usuário, cadastro, atribuição de papéis e autorização, com tratamento de exceções para possíveis erros ao interagir com o API do 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 essa classe como um serviço Spring, tornando-a um candidato para o componente de scanner
public class UserService {
    private final Permit permit;

    // Injeção de construtor para o SDK de Permissão
    public UserService(Permit permit) {
        this.permit = permit;
    }

    /**
     * Simula o login do usuário criando e retornando um objeto de Usuário Permit.
     * 
     * @param key Chave única do usuário
     * @return Objeto de usuário
     */
    public Object login(String key) {
        return new User.Builder(key).build();
    }

    /**
     * Gerencia o cadastro do usuário criando e sincronizando um novo Usuário Permit.
     * 
     * @param key Chave única do usuário
     * @return Usuário criado e sincronizado
     */
    public User signup(String key) {
        var user = new User.Builder(key).build();
        try {
            permit.api.users.sync(user);  // Sincroniza o novo usuário com o serviço de Permissão
        } catch (PermitContextError | PermitApiError | IOException e) {
            throw new RuntimeException("Failed to create user", e);  // Gerencia exceções durante a criação do usuário
        }
        return user;
    }

    /**
     * Atribui um papel ao usuário no "ambiente padrão".
     * 
     * @param user Objeto de usuário para atribuir o papel
     * @param role Papel a ser atribuído
     */
    public void assignRole(User user, String role) {
        try {
            permit.api.users.assignRole(user.getKey(), role, "default");  // Atribui papel no "ambiente padrão"
        } catch (PermitApiError | PermitContextError | IOException e) {
            throw new RuntimeException("Failed to assign role to user", e);  // Gerencia exceções durante a atribuição de papel
        }
    }

    /**
     * Verifica se o usuário está autorizado a realizar uma ação específica em um recurso.
     * 
     * @param user Objeto de usuário solicitando autorização
     * @param action Ação a ser autorizada
     * @param resource Recurso no qual a ação será realizada
     * @throws UnauthorizedException se o usuário não estiver logado
     * @throws ForbiddenAccessException se o acesso do usuário for negado
     */
    public void authorize(User user, String action, Resource resource) {
        if (user == null) {
            throw new UnauthorizedException("Not logged in");  // Lança exceção se o usuário não estiver logado
        }
        try {
            var permitted = permit.check(user, action, resource);  // Realiza verificação de autorização
            if (!permitted) {
                throw new ForbiddenAccessException("Access denied");  // Lança exceção se o acesso for negado
            }
        } catch (PermitApiError | IOException e) {
            throw new RuntimeException("Failed to authorize user", e);  // Gerencia exceções durante a autorização
        }
    }
}

Abaixo no código, a classe UserController expõe pontos de extremidade de API REST para o cadastro de usuários e atribuição de papéis. Ela interage com a classe UserService para manipular o lógica de negócios relacionada aos usuários e fornece respostas HTTP apropriadas.

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 que esta classe manipula pedidos HTTP e retorna respostas em JSON
@RequestMapping("/api/users")  // Caminho base da URL para todas as operações relacionadas a usuários
public class UserController {
    private final UserService userService;

    // Injeção de dependência de construtor de UserService, contendo a lógica de negócios para as operações de usuário
    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * Manipula pedidos de cadastro de usuários.
     * Ponto final: POST /api/users/signup
     * 
     * @param key Chave única para o novo usuário
     * @return Objeto de usuário criado
     */
    @PostMapping("/signup")
    public User signup(@RequestBody String key) {
        return userService.signup(key);  // Chama o método signup em UserService para criar um novo usuário
    }

    /**
     * Manipula atribuição de papel ao usuário logado.
     * Ponto final: POST /api/users/assign-role
     * 
     * @param request Pedido HTTP, usado para recuperar o usuário atual
     * @param role Papel a ser atribuído ao usuário atual
     */
    @PostMapping("/assign-role")
    public void assignRole(HttpServletRequest request, @RequestBody String role) {
        // Recupera o usuário atual dos atributos da solicitação
        User currentUser = (User) request.getAttribute("user");

        // Lança exceção se o usuário não estiver logado
        if (currentUser == null) {
            throw new UnauthorizedException("Not logged in");
        }

        // Atribui o papel especificado ao usuário atual
        userService.assignRole(currentUser, role);
    }
}

Criando ponto de aplicação de políticas de RBAC, ABAC e ReBAC

Abaixo do código, a classe ProductService gerencia operações CRUD para produtos e avaliações, lidando com permissões e papéis através da API Permit.

Cada operação inclui verificações de autorização do usuário, com tratamento de exceções apropriado para erros da API Permit e situações de recurso não encontrado.

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  // Marca esta classe como um serviço do Spring

public class ProductService {

    private final List<Product> products = new ArrayList<>();  
// Lista em memória para armazenar produtos

    private final AtomicInteger productIdCounter = new AtomicInteger();  
// Contador para gerar IDs de produtos únicos

    private final AtomicInteger reviewIdCounter = new AtomicInteger();   
// Contador para gerar IDs de avaliações únicos


    
// Construtores para instâncias de recursos (produto e avaliação)

    private final Resource.Builder productResourceBuilder = new Resource.Builder("product");
    private final Resource.Builder reviewResourceBuilder = new Resource.Builder("review");

    private final UserService userService;  
// Serviço para manipular operações relacionadas a usuários

    private final Permit permit;  
// Instância do SDK Permit para manipular autorização e gerenciamento de recursos


    
// Construtor para injetar dependências

    public ProductService(UserService userService, Permit permit) {
        this.userService = userService;
        this.permit = permit;
    }

    
// Método para autorizar um usuário para uma ação específica em um recurso

    private void authorize(User user, String action, Resource resource) {
        userService.authorize(user, action, resource);
    }

    
// Autoriza um usuário a realizar uma ação em um produto específico

    private void authorize(User user, String action, Product product) {
        var attributes = new HashMap<String, Object>();
        attributes.put("vendor", product.getVendor());  
// Adiciona o atributo vendedor ao produto

        userService.authorize(user, action, productResourceBuilder.withKey(product.getId().toString()).withAttributes(attributes).build());
    }

    
// Autoriza um usuário a realizar uma ação em uma avaliação específica

    private void authorize(User user, String action, Review review) {
        var attributes = new HashMap<String, Object>();
        attributes.put("customer", review.getCustomer());  
// Adiciona o atributo cliente à avaliação

        userService.authorize(user, action, reviewResourceBuilder.withKey(review.getId().toString()).withAttributes(attributes).build());
    }

    
// Recupera um produto pelo seu ID, lança uma exceção se não for encontrado

    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 todos os produtos, verifica se o usuário está autorizado a "ler" produtos

    public List<Product> getAllProducts(User user) {
        authorize(user, "read", productResourceBuilder.build());  
// O usuário deve ter permissão de "ler"

        return new ArrayList<>(products);  
// Retorna uma cópia da lista de produtos

    }

    
// Recupera um produto pelo seu ID, verifica se o usuário está autorizado a "ler" o produto

    public Product getProduct(User user, int id) {
        authorize(user, "read", productResourceBuilder.build());
        return getProductById(id);
    }

    
// Adiciona um novo produto, autoriza o usuário e cria instâncias de recursos e atribuições de papéis no Permit

    public Product addProduct(User user, String content) {
        authorize(user, "create", productResourceBuilder.build());  
// Verifica se o usuário pode criar um produto

        Product product = new Product(productIdCounter.incrementAndGet(), user.getKey(), content);  
// Cria um novo produto


        try {
            
// Cria uma instância de recurso no Permit e atribui o papel de "vendedor" ao usuário para este produto

            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);  
// Adiciona o produto à lista em memória

        return product;
    }

    
// Atualiza o conteúdo de um produto, verifica se o usuário está autorizado a "atualizar" o produto

    public Product updateProduct(User user, int id, String content) {
        Product product = getProductById(id);  
// Obtem o produto pelo seu ID

        authorize(user, "update", product);  
// Verifica se o usuário pode atualizar o produto

        product.setContent(content);  
// Atualiza o conteúdo do produto

        return product;
    }

    
// Deleta um produto, verifica se o usuário está autorizado a "deletar" o produto

    public void deleteProduct(User user, int id) {
        boolean isDeleted = products.removeIf(product -> {
            if (product.getId().equals(id)) {
                authorize(user, "delete", product);  
// Verifica se o usuário pode deletar o produto

                return true;
            } else {
                return false;
            }
        });

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

        try {
            permit.api.resourceInstances.delete("product:" + id);  
// Remove a instância do recurso do produto do Permit

        } catch (IOException | PermitApiError | PermitContextError e) {
            throw new RuntimeException(e);
        }
    }

    
// Adiciona uma avaliação a um produto, cria uma instância de recurso e relacionamento no Permit

    public Review addReview(User user, int productId, String content) {
        authorize(user, "create", reviewResourceBuilder.build());  
// Verifica se o usuário pode criar uma avaliação

        Product product = getProductById(productId);  
// Obtem o produto pelo seu ID

        Review review = new Review(reviewIdCounter.incrementAndGet(), user.getKey(), content);  
// Cria uma nova avaliação


        try {
            
// Cria uma instância de recurso para a avaliação e define a relação com o produto

            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);  
// Adiciona a avaliação ao produto

        return review;
    }

    
// Atualiza o conteúdo de uma avaliação, verifica se o usuário está autorizado a "atualizar" a avaliação

    public Review updateReview(User user, int productId, int reviewId, String content) {
        Product product = getProductById(productId);  
// Obtem o produto pelo seu 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 o usuário pode atualizar a avaliação

        review.setContent(content);  
// Atualiza o conteúdo da avaliação

        return review;
    }

    
// Deleta uma avaliação, verifica se o usuário está autorizado a "deletar" a avaliação

    public void deleteReview(User user, int productId, int reviewId) {
        Product product = getProductById(productId);  
// Obtem o produto pelo seu ID

        boolean isDeleted = product.getReviews().removeIf(review -> {
            if (review.getId().equals(reviewId)) {
                authorize(user, "delete", review);  
// Verifica se o usuário pode deletar a avaliação

                return true;
            } else {
                return false;
            }
        });

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

        try {
            permit.api.resourceInstances.delete("review:" + reviewId);  
// Remove a instância do recurso da avaliação do Permit
        } catch (IOException | PermitApiError | PermitContextError e) {
            throw new RuntimeException(e);
        }
    }
}

A classe ProductController no código abaixo, gerencia as solicitações HTTP relacionadas a produtos e suas avaliações. Ela expõe pontos de extremidade para gerenciar produtos (como criando, atualizando, excluindo e recuperando) e para gerenciar avaliações de produtos.

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 que essa classe é um controlador REST do Spring
@RequestMapping("/api/products")  // URL base para todos os endpoints desse controlador
public class ProductController {

    private final ProductService productService;  // Instância de ProductService para lidar com operações relacionadas a produtos

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

    // Requisição GET para recuperar todos os produtos
    @GetMapping
    public List<Product> getAllProducts(HttpServletRequest request) {
        User currentUser = (User) request.getAttribute("user");  // Obtém o usuário autenticado da requisição
        return productService.getAllProducts(currentUser);  // Chama ProductService para obter todos os produtos para o usuário
    }

    // Requisição GET para recuperar um produto pelo seu ID
    @GetMapping("/{id}")
    public Product getProductById(HttpServletRequest request, @PathVariable("id") int id) {
        User currentUser = (User) request.getAttribute("user");  // Obtém o usuário autenticado da requisição
        return productService.getProduct(currentUser, id);  // Chama ProductService para obter o produto pelo ID para o usuário
    }

    // Requisição POST para adicionar um novo produto
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)  // Define o status da resposta para 201 (Criado)
    public Product addProduct(HttpServletRequest request, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // Obtém o usuário autenticado da requisição
        return productService.addProduct(currentUser, content);  // Chama ProductService para adicionar um novo produto
    }

    // Requisição PUT para atualizar um produto existente pelo seu ID
    @PutMapping("/{id}")
    public Product updateProduct(HttpServletRequest request, @PathVariable("id") int id, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // Obtém o usuário autenticado da requisição
        return productService.updateProduct(currentUser, id, content);  // Chama ProductService para atualizar o produto pelo ID
    }

    // Requisição DELETE para excluir um produto pelo seu ID
    @DeleteMapping("/{id}")
    public String deleteProduct(HttpServletRequest request, @PathVariable("id") int id) {
        User currentUser = (User) request.getAttribute("user");  // Obtém o usuário autenticado da requisição
        productService.deleteProduct(currentUser, id);  // Chama ProductService para excluir o produto pelo ID
        return "Deleted product with id " + id;  // Retorna uma mensagem de sucesso após a exclusão
    }

    // Requisição POST para adicionar uma nova avaliação a um produto pelo ID do produto
    @PostMapping("/{id}/review")
    public Review addReview(HttpServletRequest request, @PathVariable("id") int id, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // Obtém o usuário autenticado da requisição
        return productService.addReview(currentUser, id, content);  // Chama ProductService para adicionar uma avaliação ao produto
    }

    // Requisição PUT para atualizar uma avaliação existente pelo ID do produto e ID da avaliação
    @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");  // Obtém o usuário autenticado da requisição
        return productService.updateReview(currentUser, id, reviewId, content);  // Chama ProductService para atualizar a avaliação
    }

    // Requisição DELETE para excluir uma avaliação pelo ID do produto e ID da avaliação
    @DeleteMapping("/{id}/review/{reviewId}")
    public String deleteReview(HttpServletRequest request, @PathVariable("id") int id, @PathVariable("reviewId") int reviewId) {
        User currentUser = (User) request.getAttribute("user");  // Obtém o usuário autenticado da requisição
        productService.deleteReview(currentUser, id, reviewId);  // Chama ProductService para excluir a avaliação
        return "Deleted review with id " + reviewId + " from product " + id;  // Retorna uma mensagem de sucesso após a exclusão
    }
}

Passo 2: Obter sua Chave de API do Ambiente

No painel de gerenciamento da interface gráfica, copie a Chave de API do ambiente ativo.

Em seguida, adicione a Chave de API do ambiente e a URL do PDP no arquivo application.yaml.

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

Passo 3: Implementar Ponto de Decisão de Política (PDP)

O Ponto de Decisão de Política (PDP) é implantado em sua VPC e é responsável pela avaliação de suas solicitações de autorização. O PDP garantirá latência zero, excelente desempenho, alta disponibilidade e melhoria na segurança.

Use o comando abaixo para puxar o container do Permit.io PDP do Hub do Docker.

docker pull permitio/pdp-v2:latest

Em seguida, execute o container.

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

Passo 4: Executar a App

Você pode executar a aplicação usando o seguinte comando Gradle:

./gradlew bootRun

Visualizar e Criar Produtos

Vamos agora interagir com os pontos finais da aplicação usando o REQBIN.

Primeiro, crie um novo usuário usando o ponto final /api/users/signup.

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

Você deveria ser capaz de visualizar o usuário em seu projeto Permit, na pasta Directory > All Tenants.

Inicialmente, o usuário não tem nenhum papel, portanto, não pode fazer muito. Por exemplo, tentar listar os produtos resultará em uma resposta de erro 403 Proibido, conforme mostrado abaixo. O código de erro 403 significa que o usuário não tem permissões para acessar o recurso solicitado, que é os produtos neste caso. Você pode aprender mais sobre a diferença entre os códigos de erro 401 e 403 aqui.

Para o usuário visualizar uma lista de produtos, atribua-lhes o papel de visualizador usando o comando abaixo:

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

Você deveria ver que o usuário johndoe foi atribuído ao papel de visualizador, conforme mostrado abaixo:

Como um visualizador pode criar um produto, use o comando abaixo para criar um produto com o usuário johndoe.

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

Você deveria ver que um novo produto é criado com ID 1 e que o usuário johndoe foi adicionado como fornecedor.

Adicionando Avaliações aos Produtos

Para adicionar avaliações aos produtos, crie outro usuário chamado jane.

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

Para o usuário adicionar uma avaliação aos produtos, atribua-lhes um papel de visualizador usando o comando abaixo:

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

Em seguida, você pode adicionar uma avaliação ao produto adicionado por johndoe usando o comando abaixo:

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'

Parabéns! Você concluiu o projeto deste tutorial.

Próximos Passos

Agora que você aprendeu como implementar autorização granular em seus aplicativos Java e Spring Boot usando Permit.io, você pode querer explorar further.

Aqui estão algumas fontes valiosas:

Antes de Terminarmos

Espero que você encontre este tutorial insípido.

Aqui estão algumas minhas outras postagens recentes no blog que você pode desfrutar:

Para mais tutoriais sobre ferramentas de desenvolvedor incríveis, certifique-se de ver o meu blog DTA.

Segue comigo no Twitter para obter atualizações em tempo real sobre meus outros projetos.

Feliz codificação.