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

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

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

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

Espero que tenham gostado do meu último 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 feita para ajudar desenvolvedores a descobrir ferramentas de desenvolvimento incríveis.

Este tutorial é outro esforço para apresentar você a 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 aplicação inteira estacada, 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 é mais importante.

Pré-requisitos

Para entender totalmente o tutorial, você precisa ter um entendimento básico de Java e Spring Boot. Você também precisará do seguinte:

  • 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: uma ferramenta de construção para gerenciar dependências.

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

  • Postman ou cURL: ferramentas para testar suas API de pontos de extremidade.

O que é Autorização de Graino Fino?

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

Contrário à autorização de granularidade coarse (que manipula o acesso com base em categorias como funções de usuário como “administrador” ou “usuário“), a autorização de granularidade fine dá-vos a flexibilidade para definir o acesso a um nível granular, para recursos ou ações específicos e até mesmo atributos.

Na Autorização de Granularidade Fine existem 3 tipos de modelos de política para gerenciar autorização; Controlo de Acesso baseado em Papéis (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 sua aplicação.

Controlo de Acesso baseado em Papéis (RBAC)

RBAC é uma abordagem de segurança que controla o acesso a recursos com base nas funções de usuários dentro de uma organização. Este modelo simplifica as permissões organizando os usuários em papéis e gerenciando o controle de acesso de acordo com esses papéis definidos.

Conceitos Chave em RBAC:

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

Papéis: Uma coleção de permissões ou privilégios de acesso atribuídos a um grupo de usuários com base em 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 adaptativo que decide quem pode ou não acessar recursos com base em atributos, como detalhes do usuário. O modelo ABAC permite que você defina autorizações de grão fino baseadas 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, título de trabalho, 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, dono).

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

  • Atributos de 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 Relacionamento (ReBAC)

O ReBAC é um sistema de controle de acesso que concede permissões para acessar recursos com base nas relações entre entidades dentro de um sistema. O enfoque dá importância à definição e gerenciamento de controle de acesso através do mapeamento das relações entre usuários e recursos, bem como 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 a relação 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 relações para determinar direitos de acesso. Um usuário pode acessar um recurso ou executar uma ação nele se tiver uma relação particular com ele.

Como Implementar Autorização Finamente Granular

Com base no entendimento básico dos modelos RBAC, ABAC e ReBAC, vamos ver como podemos implementar esses modelos em uma aplicação de e-commerce.

Implementando Controle de Acesso Baseado em Papel

Passo 1: Acesse Permit.io e crie uma conta e seu espaço de trabalho.

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 as ações read, create, update, e delete.

Passo 3: Crie outro recurso chamado Avaliações com as ações read, create, update, e delete.

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

  • O papel admin tem permissão para cria, exclui, le ou atualiza um produto ou uma avaliação.

  • O papel editor tem permissão para cria, le ou atualiza um produto ou uma avaliação mas não pode excluir nenhum.

  • O papel viewer 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 a aba Recursos, depois clique no botão Adicionar Atributos.

  • Adicione um atributo chamado fornecedor

  • Adicione um atributo chamado o cliente

Passo 2: Abra a aba Regras de Controle de Acesso de ABAC, depois crie um novo Conjunto de Recursos de ABAC chamado Produtos Próprios que depende do recurso Produtos. Depois, adicione uma condição que dá permissão apenas ao usuário que criou um produto baseado no atributo fornecedor.

Passo 3: Crie outro Conjunto de Recursos de ABAC chamado Próprias Resenhas que depende do recurso Resenhas.

Implementando Controle de Acesso Baseado em Relacionamentos

Passo 1: Abra a aba Recursos e edite o recurso Produtos. Adicione o papel fornecedor na seção de opções de ReBAC. Então 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 de 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 Permit.io, vamos aprender como aplicá-las em um aplicativo de Gerenciamento de Loja Virtual usando a API Permit.io.

Vai haver muito código por vir, então certifique-se de ler as extensas legendas que deixei em cada bloco de código. Estas 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 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 Java do Permit.io 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 esta classe como uma classe de configuração para o Spring IoC
public class PermitClientConfig {

    @Value("${permit.api-key}")  // Injeção da chave de API do Permit de 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 de propriedades de aplicação
    private String pdpUrl;

    /**
     * Cria uma instância de bean de cliente de Permit com configuração personalizada
     * @return instância de cliente de Permit
     */
    @Bean
    public Permit permit() {
        return new Permit(
                new PermitConfig.Builder(apiKey)  // Inicializar PermitConfig com chave de API
                        .withPdpAddress(pdpUrl)   // Definir o endereço do PDP
                        .withDebugMode(true)      // Habilitar 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 depois 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 esta classe como um serviço Spring, tornando-a um candidato para o scanner de componentes
public class UserService {
    private final Permit permit;

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

    /**
     * Simula o login de 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 de usuário criando e sincronizando um novo usuário Permit.
     * 
     * @param key Chave única do usuário
     * @return Objeto de 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 Permit
        } catch (PermitContextError | PermitApiError | IOException e) {
            throw new RuntimeException("Failed to create user", e);  // Gerencia exceções durante a criação de usuário
        }
        return user;
    }

    /**
     * Atribui um papel ao usuário dentro do "default" Ambiente.
     * 
     * @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 "default" Ambiente
        } 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 API REST para o processo de cadastro de usuários e atribuição de papéis. Ela interage com a classe UserService para manipular a lógica de negócios relacionadas 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 solicitações HTTP e retorna respostas em JSON
@RequestMapping("/api/users")  // Caminho de URL base para todas as operações relacionadas a usuários
public class UserController {
    private final UserService userService;

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

    /**
     * Manipula solicitações 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 papéis a usuários logados.
     * Ponto final: POST /api/users/assign-role
     * 
     * @param request Solicitação HTTP, usada 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 uma 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

Na código abaixo, a classe ProductService gerencia operações CRUD para produtos e avaliações, manipulando 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 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 únicos de produtos
    private final AtomicInteger reviewIdCounter = new AtomicInteger();   // Contador para gerar IDs únicos de avaliações

    // Construtores para instâncias de recursos Permit (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 lidar com operações relacionadas ao usuário
    private final Permit permit;  // Instância do SDK Permit para lidar com autorização e gerenciamento de recursos

    // Construtor para injeção de dependências
    public ProductService(UserService userService, Permit permit) {
        this.userService = userService;
        this.permit = permit;
    }

    // Método para autorizar um usuário para uma determinada ação 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 atributo de 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 atributo de 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 "leitura"
        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 função 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 novo produto

        try {
            // Cria instância de recurso no Permit e atribui a função 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);  // Obtém 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;
    }

    // Exclui um produto, verifica se o usuário está autorizado a "excluir" 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 excluir 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);  // Obtém o produto pelo seu ID
        Review review = new Review(reviewIdCounter.incrementAndGet(), user.getKey(), content);  // Cria nova avaliação

        try {
            // Cria uma instância de recurso para a avaliação e estabelece o relacionamento 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);  // Obtém 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;
    }

    // Exclui uma avaliação, verifica se o usuário está autorizado a "excluir" a avaliação
    public void deleteReview(User user, int productId, int reviewId) {
        Product product = getProductById(productId);  // Obtém 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 excluir 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);
        }
    }
}

Abaixo do código, a classe ProductController manipula pedidos HTTP relacionados a produtos e suas avaliações. Ela expõe pontos finais 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 esta classe é um controlador REST do Spring
@RequestMapping("/api/products")  // URL base para todos os endpoints neste controlador
public class ProductController {

    private final ProductService productService;  // Instância do 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 o ProductService para obter todos os produtos do 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 o 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 como 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 o 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 o 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 o 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 o ProductService para adicionar uma avaliação ao produto
    }

    // Requisição PUT para atualizar uma avaliação existente pelo ID do produto e 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 o ProductService para atualizar a avaliação
    }

    // Requisição DELETE para excluir uma avaliação pelo ID do produto e 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 o 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 API do Ambiente

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

Em seguida, adicione a chave de ambiente API key e a URL 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) é implementado em seu VPC e é responsável pela avaliação de suas solicitações de autorização. O PDP garantirá latência zero, ótimo desempenho, alta disponibilidade e melhoria na segurança.

Use o comando abaixo para buscar o contêiner do PDP Permit.io do Hub Docker.

docker pull permitio/pdp-v2:latest

Em seguida, execute o contêiner.

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 o aplicativo usando o seguinte comando Gradle:

./gradlew bootRun

Visualizar e Criar Produtos

Vamos agora interagir com os pontos finais do aplicativo usando REQBIN.

A primeira coisa, 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ê deve 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, como 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 saber 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 um 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, como 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'

Então, 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ê aprenderá 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 informativo.

Aqui estão alguns outros posts recentes do meu blog que você pode curtir:

Para mais tutoriais sobre ferramentas impressionantes para desenvolvedores, certifique-se de ver meu blog DTA.

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

Feliz codificação.