アプリケーションのセキュリティは、単にアクセスの承认または拒否を行うだけではなく、開発者は詳細な粒度の権限管理を行うための细粒度認証(FGA)を実装する必要があります。

FGAを使用すると、詳細なアクセスコントロールを設定することができ、どのユーザーが何を行い、どのような条件で行うことができるかを指定することができます。

このチュートリアルでは、JavaとSpring BootでPermit.ioを使用して细粒度認証を実装する方法を学びます。

以下はソースコードです(スターをつけてください)。

前回のブログをお楽しんでいたでしょうか。StreamとNext.jsを使用してカスタムビデオコンファレンスアプリを構築することについてのものです。これらのブログは、DevTools Academyの作成過程を反映しています。DevTools Academyは、開発者が素晴らしい開発者ツールを発見するためのプラットフォームです。

このチュートリアルは、最近探索した非常に便利な開発者ツールを紹介するもう一つの努力です。

目次:

Permitとは何か?

Permit.ioは、完全なスタック、プラグアンドプレイのアプリレベルの認可ソリューションであり、数分で安全柔軟認可レイヤーを実装することができ、最も重要なことに集中することができます。

前提条件

このチュートリアルを完全に理解するためには、JavaSpring Bootの基本的理解が必要です。以下も必要です。

  • Permit.io: FGAの実装を簡素化する開発ツール。

  • Spring Boot Starter Web: RESTful APIを含むウェブアプリケーションを構築するための基本コンポーネントを提供します。

  • Gradle: 依存関係を管理するビルドツールです。

  • JDK 11 またはそれ以降: Spring Boot アプリをコンパイルして実行するために必要な Java 開発キットのバージョンです。

  • Postman または cURL: API エンドポイントのテストを行うツールです。

Fine-Grained Authorization とは何ですか?

Fine-grained authorizationは、誰がそのリソースにアクセスできるか、どの程度のアクセスであり、特定の条件の下であるかを決定してアクセスコントロールを提供します。

粗粒度の認可(user rolesによるアクセス管理に例えられるadminuserなどのカテゴリに基づいている)とは対照的に、細粒度の認可は、特定のリソースやアクション、乃至属性に対するアクセスを定義する柔軟性を提供します。

细粒度の認可において、認可を管理するための3つのポリシーモデルが存在します;ロール基盤のアクセスコントロール(RBAC)属性基盤のアクセスコントロール(ABAC)、そして関連性基盤のアクセスコントロール(ReBAC)

これらのアプローチのそれぞれについて見て、アプリケーションにこれらを実装する方法を学びましょう。

ロール基盤のアクセスコントロール(RBAC)

RBACは、組織内のユーザーの役割に基づいてリソースのアクセスを制御するセキュリティアプローチです。このモデルは、ユーザーを役割に分類し、定義された役割に基づいてアクセスコントロールを管理することで、権限の整列を簡略化します。

RBACにおける主要な概念:

ユーザー:システムを使用する人々や、従業員や顧客。

役割:ユーザーの責任や任务に基づいてグループ化され、権限やアクセス権を割り当てることができる設定である。例えば、管理者、マネージャー、または顧客。

権限:ユーザーがリソースとのやり取りを行うための権利が与えられること。例えば、読み取り、書き込み、やり取り。

属性基盤のアクセスコントロール(ABAC)

ABACは、ユーザーの詳細に基づいてリソースにアクセスできるか否かを決定するために属性に基づく多様且つ适应性のあるアクセス制御モデルです。ABACモデルは、ユーザー属性に基づいて細粒度の認可を定義することができます。

ABACにおけるキーコンセプト:

属性:アクセス制御決定に使用される特性や属性です。属性は通常、以下のように分類されます:

  • ユーザー属性:ユーザーに関する情報(例えば、役割、部署、職名、年齢など)。

  • リソース属性:リソースの特性(例えば、ファイルタイプ、データ分類レベル、作成日、所有者)。

  • アクション属性:ユーザーが行う动作(例えば、読む、書く、削除する、承認する)。

  • 環境属性:アクセス要求に関するコンテキスト情報(例えば、時間、場所、デバイスタイプ、IPアドレス)。

関連性に基づくアクセスコントロール (ReBAC)

ReBACは、システム内の实体同士の関連性に基づいてリソースにアクセス権を与えるアクセスコントロールシステムである。この取り組みは、ユーザーがリソースや組織やグループなどの他の实体とどのように関連しているかをマッピングすることで、アクセスコントロールを定義し管理することを強調する。

ReBACの主要概念:

实体: ユーザー、リソース(例如、ファイルやドキュメント)、そして他の实体(例えば、グループや組織单位)。

関連性: 2つの实体間の関連性を指定する接続。例えば、ユーザーがドキュメントの「所有者」やチームの「メンバー」であるということなど。

ポリシー: 関連性を使用してアクセス権を決定するルール。特定の関連性を持っている場合、ユーザーはリソースにアクセスしたり、それに対するアクションを実行することができる。

細粒度の認可を実装する方法

基本的な理解がRBACABACReBACにあったら、私たちはこれらのモデルをe-commerceアプリにどのように実装するか見てみよう。

角色に基づくアクセスコントロールの実装

ステップ1: Permit.ioに移動し、アカウントとワークスペースを作成します。

デフォルトでは、DevelopmentProduction の 2 つの環境を含むプロジェクトが表示されます。

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

ステップ 2: Products という名前のリソースを作成します。リソースを作成するには、左側のサイドバーの Policy タブを開き、次に上部の Resources タブを開きます。次に、Create a Resource ボタンをクリックし、readcreateupdatedelete のアクションを持つ Products という名前のリソースを作成します。

ステップ 3: readcreateupdatedelete のアクションを持つ Reviews という名前の別のリソースを作成します。

ステップ 4: Policy Editor タブを開きます。3 つのロール admineditorviewer が作成されていることがわかります。

  • ロール admin は、createdeleteread、または update の権限を持つプロダクトまたはレビューを作成できます。

  • ロール editor は、createread、または update の権限を持つプロダクトまたはレビューを作成できますが、delete することはできません。

  • ロール viewer には、製品や レビュー作成 または 読み取り する権限があるが、削除 または 更新 する権限はない。

属性ベースのアクセス制御の実装

ステップ 1: リソース タブを開き、属性を追加 ボタンをクリックします。

  • ベンダー という名前の属性を追加します。

  • 顧客 という名前の属性を追加します。

ステップ 2: ABAC ルール タブを開き、自社製品 という名前の新しい ABAC リソース セットを作成します。このリソース セットは、製品リソースに依存します。次に、ベンダー属性に基づいて、製品を作成したユーザーにのみ権限を付与する条件を追加します。

ステップ 3: 自社レビュー という名前の別の ABAC リソース セットを作成します。このリソース セットは、レビュー リソースに依存します。

関係ベースのアクセス制御の実装

ステップ 1: リソース タブを開き、製品リソースを編集します。ReBAC オプション セクションでロール ベンダー を追加します。次に、関係セクションで製品をレビューの親として設定します。

手順2: 以下のようにReBACオプションセクションで「顧客」役割を追加して、レビューリソースを編集します。

手順3: ポリシー エディタタブに移動し、以下の内容を追加します。

  • 役割 販売者 は、自身の製品を更新して削除する权限を持つ。

  • 役割 顧客 は、自身の製品のレビューを更新して削除する权限を持つ。

JavaとSpringBootでFGAを実装する方法

Permit.io WebインターフェースでRBACABACReBACポリシーを定義した後、Permit.io APIを使用してE-Commerce Management Systemアプリケーションにこれらのポリシーを適用する方法を学びましょう。

これから大変多くのコードが出てくるので、各コードブロックに残した详しいコメントを読んでいきましょう。これらは、このコードの内容をより完全に理解するのを助けるでしょう。

手順1: E-commerceアプリケーションの設定

E-commerceアプリケーションを設定し、ソースコードをgit cloneします。

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.

PermitパッケージSDKのインストール

PermitパッケージSDKをインストールするには、build.gradleファイルの依存性ブロックにSDKを追加します。

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

    // 以下の行を追加して、Permit.io Java SDKをプロジェクトにインストールします
    implementation 'io.permit:permit-sdk-java:2.0.0'
}

初期化承认SDK

以下のコードを使用して、Permit SDK クライアントを初期化することができます。

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  // Spring IoC用の設定クラスとしてこのクラスをマークする
public class PermitClientConfig {

    @Value("${permit.api-key}")  // アプリケーションのプロpertiesからPermit APIキーを注入する
    private String apiKey;

    @Value("${permit.pdp-url}")  // アプリケーションのプロpertiesからPermit PDP (Policy Decision Point) URLを注入する
    private String pdpUrl;

    /**
     * カスタム設定でPermitクライアントBeanを作成する
     * @return Permitクライアントインスタンス
     */
    @Bean
    public Permit permit() {
        return new Permit(
                new PermitConfig.Builder(apiKey)  // APIキーを使用してPermitConfigを初期化する
                        .withPdpAddress(pdpUrl)   // PDPアドレスを設定する
                        .withDebugMode(true)      // 詳細なログ记录を行うためのデバッグモードを有効にする
                        .build()                  // PermitConfigオブジェクトを構築する
        );
    }
}

SDKでユーザーの同期

权限を適用するには、まずPermitにユーザーを同期し、その後そのユーザーに役割を割り当てる必要があります。

以下のコードで、UserService クラスは、ユーザーのログイン、サインアップ、役割割り当て、認可に関するメソッドを提供し、Permit APIとのやり取りに際して可能なエラーの例外処理を行います。

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  // このクラスをSpringサービスとしてマークし、コンポーネントスキャンの候補にする
public class UserService {
    private final Permit permit;

    // Permit SDKのコンストラクタ注入
    public UserService(Permit permit) {
        this.permit = permit;
    }

    /**
     * ユーザーの認証を模倣し、Permit Userオブジェクトを作成して返却します。
     * 
     * @param key ユーザーの独自のキー
     * @return ユーザーオブジェクト
     */
    public Object login(String key) {
        return new User.Builder(key).build();
    }

    /**
     * 新しいPermit Userを作成して同期して、ユーザー登録を処理します。
     * 
     * @param key ユーザーの独自のキー
     * @return 作成されたと同期されたユーザーオブジェクト
     */
    public User signup(String key) {
        var user = new User.Builder(key).build();
        try {
            permit.api.users.sync(user);  // 新しいユーザーをPermitサービスと同期させます
        } catch (PermitContextError | PermitApiError | IOException e) {
            throw new RuntimeException("Failed to create user", e);  // ユーザー作成中の例外を処理します
        }
        return user;
    }

    /**
     * "default"環境内でユーザーにロールを割り当てます。
     * 
     * @param user ロールを割り当てるユーザーオブジェクト
     * @param role 割り当てるロール
     */
    public void assignRole(User user, String role) {
        try {
            permit.api.users.assignRole(user.getKey(), role, "default");  // "default"環境内でロールを割り当てます
        } catch (PermitApiError | PermitContextError | IOException e) {
            throw new RuntimeException("Failed to assign role to user", e);  // ロール割り当て中の例外を処理します
        }
    }

    /**
     * 特定のアクションをリソースに対して実行するために、ユーザーが認証されているかを確認します。
     * 
     * @param user 認証を要求するユーザーオブジェクト
     * @param action 認証するアクション
     * @param resource アクションを行うリソース
     * @throws UnauthorizedException ユーザーがログインしていない場合
     * @throws ForbiddenAccessException ユーザーがアクセス拒否されている場合
     */
    public void authorize(User user, String action, Resource resource) {
        if (user == null) {
            throw new UnauthorizedException("Not logged in");  // ユーザーがログインしていない場合、例外を投げます
        }
        try {
            var permitted = permit.check(user, action, resource);  // 認証を確認します
            if (!permitted) {
                throw new ForbiddenAccessException("Access denied");  // アクセスが拒否されている場合、例外を投げます
            }
        } catch (PermitApiError | IOException e) {
            throw new RuntimeException("Failed to authorize user", e);  // 認証中の例外を処理します
        }
    }
}

以下のコードでは、UserControllerクラスは、ユーザー登録と役割割り当てのREST APIエンドポイントを露出しています。これはUserServiceクラスとやりとりして、ユーザー関連のビジネスロジックを処理し、適切なHTTP応答を提供します。

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  // HTTPリクエストを処理し、JSON応答を返すことを示します
@RequestMapping("/api/users")  // すべてのユーザー関連操作の基本URLパス
public class UserController {
    private final UserService userService;

    // UserServiceをコンストラクタ注入し、ユーザー操作のビジネスロジックを含みます
    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * ユーザー登録のリクエストを処理します。
     * エンドポイント: POST /api/users/signup
     * 
     * @param key 新しいユーザーの唯一のキー
     * @return 作成されたUserオブジェクト
     */
    @PostMapping("/signup")
    public User signup(@RequestBody String key) {
        return userService.signup(key);  // UserServiceのsignupメソッドを呼び出して新しいユーザーを作成します
    }

    /**
     * ログイン中のユーザーに役割を割り当てます。
     * エンドポイント: POST /api/users/assign-role
     * 
     * @param request HTTPリクエスト、現在のユーザーを取得するため
     * @param role 現在のユーザーに割り当てる役割
     */
    @PostMapping("/assign-role")
    public void assignRole(HttpServletRequest request, @RequestBody String role) {
        // HTTPリクエストの属性から現在のユーザーを取得します
        User currentUser = (User) request.getAttribute("user");

        // ログインしていない場合は例外を投げます
        if (currentUser == null) {
            throw new UnauthorizedException("Not logged in");
        }

        // 指定した役割を現在のユーザーに割り当てます
        userService.assignRole(currentUser, role);
    }
}

RBAC、ABAC、およびReBACポリシー実行ポイントの作成

以下のコードでは、ProductServiceクラスが商品とレビューのCRUD操作を管理し、Permit APIを通じて権限と役割を処理します。

各操作には、ユーザーの認可確認が含まれており、Permit APIのエラーとリソースの存在しない状況に适切な例外处理が含まれています。

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  // このクラスをSpringサービスとしてマーク
public class ProductService {

    private final List<Product> products = new ArrayList<>();  // 製品を保存するためのインメモリリスト
    private final AtomicInteger productIdCounter = new AtomicInteger();  // ユニークな製品IDを生成するためのカウンター
    private final AtomicInteger reviewIdCounter = new AtomicInteger();   // ユニークなレビューIDを生成するためのカウンター

    // Permitリソースインスタンス(製品とレビュー)のビルダー
    private final Resource.Builder productResourceBuilder = new Resource.Builder("product");
    private final Resource.Builder reviewResourceBuilder = new Resource.Builder("review");

    private final UserService userService;  // ユーザー関連の操作を処理するサービス
    private final Permit permit;  // 認可とリソース管理を処理するPermit SDKインスタンス

    // 依存関係を注入するためのコンストラクタ
    public ProductService(UserService userService, Permit permit) {
        this.userService = userService;
        this.permit = permit;
    }

    // リソースに対する特定のアクションにユーザーを認可するメソッド
    private void authorize(User user, String action, Resource resource) {
        userService.authorize(user, action, resource);
    }

    // 特定の製品に対するアクションをユーザーに認可する
    private void authorize(User user, String action, Product product) {
        var attributes = new HashMap<String, Object>();
        attributes.put("vendor", product.getVendor());  // 製品にベンダー属性を追加
        userService.authorize(user, action, productResourceBuilder.withKey(product.getId().toString()).withAttributes(attributes).build());
    }

    // 特定のレビューに対するアクションをユーザーに認可する
    private void authorize(User user, String action, Review review) {
        var attributes = new HashMap<String, Object>();
        attributes.put("customer", review.getCustomer());  // レビューに顧客属性を追加
        userService.authorize(user, action, reviewResourceBuilder.withKey(review.getId().toString()).withAttributes(attributes).build());
    }

    // IDで製品を取得し、見つからない場合は例外をスロー
    private Product getProductById(int id) {
        return products.stream().filter(product -> product.getId().equals(id))
                .findFirst().orElseThrow(() -> new ResourceNotFoundException("Product with id " + id + " not found"));
    }

    // すべての製品を取得し、ユーザーが製品を"読む"権限があるかチェック
    public List<Product> getAllProducts(User user) {
        authorize(user, "read", productResourceBuilder.build());  // ユーザーは"読む"権限を持っている必要がある
        return new ArrayList<>(products);  // 製品リストのコピーを返す
    }

    // IDで製品を取得し、ユーザーがその製品を"読む"権限があるかチェック
    public Product getProduct(User user, int id) {
        authorize(user, "read", productResourceBuilder.build());
        return getProductById(id);
    }

    // 新しい製品を追加し、ユーザーを認可し、Permitでリソースインスタンスとロール割り当てを作成
    public Product addProduct(User user, String content) {
        authorize(user, "create", productResourceBuilder.build());  // ユーザーが製品を作成できるかチェック
        Product product = new Product(productIdCounter.incrementAndGet(), user.getKey(), content);  // 新しい製品を作成

        try {
            // Permitでリソースインスタンスを作成し、この製品に対してユーザーに"ベンダー"ロールを割り当て
            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);  // インメモリリストに製品を追加
        return product;
    }

    // 製品の内容を更新し、ユーザーがその製品を"更新"する権限があるかチェック
    public Product updateProduct(User user, int id, String content) {
        Product product = getProductById(id);  // IDで製品を取得
        authorize(user, "update", product);  // ユーザーが製品を更新できるかチェック
        product.setContent(content);  // 製品の内容を更新
        return product;
    }

    // 製品を削除し、ユーザーがその製品を"削除"する権限があるかチェック
    public void deleteProduct(User user, int id) {
        boolean isDeleted = products.removeIf(product -> {
            if (product.getId().equals(id)) {
                authorize(user, "delete", product);  // ユーザーが製品を削除できるかチェック
                return true;
            } else {
                return false;
            }
        });

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

        try {
            permit.api.resourceInstances.delete("product:" + id);  // Permitから製品のリソースインスタンスを削除
        } catch (IOException | PermitApiError | PermitContextError e) {
            throw new RuntimeException(e);
        }
    }

    // 製品にレビューを追加し、Permitでリソースインスタンスと関係を作成
    public Review addReview(User user, int productId, String content) {
        authorize(user, "create", reviewResourceBuilder.build());  // ユーザーがレビューを作成できるかチェック
        Product product = getProductById(productId);  // IDで製品を取得
        Review review = new Review(reviewIdCounter.incrementAndGet(), user.getKey(), content);  // 新しいレビューを作成

        try {
            // レビューのリソースインスタンスを作成し、製品との関係を設定
            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);  // 製品にレビューを追加
        return review;
    }

    // レビューの内容を更新し、ユーザーがそのレビューを"更新"する権限があるかチェック
    public Review updateReview(User user, int productId, int reviewId, String content) {
        Product product = getProductById(productId);  // 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);  // ユーザーがレビューを更新できるかチェック
        review.setContent(content);  // レビューの内容を更新
        return review;
    }

    // レビューを削除し、ユーザーがそのレビューを"削除"する権限があるかチェック
    public void deleteReview(User user, int productId, int reviewId) {
        Product product = getProductById(productId);  // IDで製品を取得
        boolean isDeleted = product.getReviews().removeIf(review -> {
            if (review.getId().equals(reviewId)) {
                authorize(user, "delete", review);  // ユーザーがレビューを削除できるかチェック
                return true;
            } else {
                return false;
            }
        });

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

        try {
            permit.api.resourceInstances.delete("review:" + reviewId);  // Permitからレビューのリソースインスタンスを削除
        } catch (IOException | PermitApiError | PermitContextError e) {
            throw new RuntimeException(e);
        }
    }
}

以下のコードでは、ProductControllerクラスは、商品とそのレビューに関連するHTTPリクエストを処理しています。これは、商品の管理(作成更新削除取得)と商品レビューの管理に関するエンドポイントを露出しています。

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  // このクラスがSpring RESTコントローラーであることを示します
@RequestMapping("/api/products")  // このコントローラー内のすべてのエンドポイントのベースURL
public class ProductController {

    private final ProductService productService;  // 製品関連の操作を処理するProductServiceインスタンス

    @Autowired  // ProductServiceビーンを自動的に自動装備する
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    // すべての製品を取得するためのGETリクエスト
    @GetMapping
    public List<Product> getAllProducts(HttpServletRequest request) {
        User currentUser = (User) request.getAttribute("user");  // リクエストから認証されたユーザーを取得する
        return productService.getAllProducts(currentUser);  // ProductServiceを呼び出してユーザーのすべての製品を取得する
    }

    // IDによって製品を取得するためのGETリクエスト
    @GetMapping("/{id}")
    public Product getProductById(HttpServletRequest request, @PathVariable("id") int id) {
        User currentUser = (User) request.getAttribute("user");  // リクエストから認証されたユーザーを取得する
        return productService.getProduct(currentUser, id);  // ProductServiceを呼び出してユーザーのIDによる製品を取得する
    }

    // 新しい製品を追加するためのPOSTリクエスト
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)  // レスポンスステータスを201(作成済み)に設定する
    public Product addProduct(HttpServletRequest request, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // リクエストから認証されたユーザーを取得する
        return productService.addProduct(currentUser, content);  // ProductServiceを呼び出して新しい製品を追加する
    }

    // IDによって既存の製品を更新するためのPUTリクエスト
    @PutMapping("/{id}")
    public Product updateProduct(HttpServletRequest request, @PathVariable("id") int id, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // リクエストから認証されたユーザーを取得する
        return productService.updateProduct(currentUser, id, content);  // ProductServiceを呼び出してIDによる製品を更新する
    }

    // IDによって製品を削除するためのDELETEリクエスト
    @DeleteMapping("/{id}")
    public String deleteProduct(HttpServletRequest request, @PathVariable("id") int id) {
        User currentUser = (User) request.getAttribute("user");  // リクエストから認証されたユーザーを取得する
        productService.deleteProduct(currentUser, id);  // ProductServiceを呼び出してIDによる製品を削除する
        return "Deleted product with id " + id;  // 削除後に成功メッセージを返す
    }

    // 製品IDによって製品に新しいレビューを追加するためのPOSTリクエスト
    @PostMapping("/{id}/review")
    public Review addReview(HttpServletRequest request, @PathVariable("id") int id, @RequestBody String content) {
        User currentUser = (User) request.getAttribute("user");  // リクエストから認証されたユーザーを取得する
        return productService.addReview(currentUser, id, content);  // ProductServiceを呼び出して製品にレビューを追加する
    }

    // 製品およびレビューIDによって既存のレビューを更新するためのPUTリクエスト
    @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");  // リクエストから認証されたユーザーを取得する
        return productService.updateReview(currentUser, id, reviewId, content);  // ProductServiceを呼び出してレビューを更新する
    }

    // 製品およびレビューIDによってレビューを削除するためのDELETEリクエスト
    @DeleteMapping("/{id}/review/{reviewId}")
    public String deleteReview(HttpServletRequest request, @PathVariable("id") int id, @PathVariable("reviewId") int reviewId) {
        User currentUser = (User) request.getAttribute("user");  // リクエストから認証されたユーザーを取得する
        productService.deleteReview(currentUser, id, reviewId);  // ProductServiceを呼び出してレビューを削除する
        return "Deleted review with id " + reviewId + " from product " + id;  // 削除後に成功メッセージを返す
    }
}

手順2: 環境APIキーの取得

UIデッシボードから、有効な環境のAPI Keyをコピーします。

次に、application.yamlファイルに環境API keyPDP URLを追加します。

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

手順3: ポリシー決定ポイント(PDP)のデプロイ

ポリシー決定ポイント(PDP)は、あなたのVPCにデプロイされ、認可リクエストの評価を担当します。PDPは、零 lag、高パフォーマンス、高可用性、および安全性の向上を保証します。

以下のコマンドを使用して、Permit.io PDPコンテナをDocker Hubから引き込むことができます。

docker pull permitio/pdp-v2:latest

その後、コンテナを実行します。

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

手順4: アプリの実行

以下のGradleコマンドを使用して、アプリケーションを実行することができます。

./gradlew bootRun

商品の表示と作成

次に、REQBINを使用して、アプリケーションのエンドポイントとインタラクトしましょう。

まず、/api/users/signupエンドポイントを使用して新しいユーザーを作成します。

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

PermitプロジェクトのDirectory > All Tenantsに新しいユーザーを見ることができるはずです。

最初、ユーザーにはロールがないため、多くのことができません。例えば、製品リストの取得を試みると、以下のように403 Forbiddenレスポンスが返されます。403エラーコードは、ユーザーがリクエストされたリソース(この場合は製品)にアクセスする権限がないことを意味します。401と403エラーコードの違いについてはこちらで詳しく学ぶことができます

ユーザーが製品リストを閲覧できるようにするには、以下のコマンドを使用してviewerロールを割り当てます:

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

以下のように、ユーザーjohndoeにviewerロールが割り当てられたことが確認できるはずです:

viewerは製品を作成できるので、以下のコマンドを使用してユーザーjohndoeで製品を作成します。

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

ID 1の新しい製品が作成され、ユーザーjohndoeがベンダーとして追加されたことが確認できるはずです。

製品にレビューを追加する

製品にレビューを追加するには、janeという別のユーザーを作成します。

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

ユーザーが製品にレビューを追加できるようにするには、以下のコマンドを使用してviewerロールを割り当てます:

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

その後、以下のコマンドを使用して、johndoeが追加した製品にレビューを追加できます:

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'

おめでとうございます!このチュートリアルのプロジェクトが完了しました。

次のステップ

これでPermit.ioを使用してJavaとSpring Bootアプリケーションで細粒度の認可を実装する方法を学びました。さらに探求したいかもしれません。

ここにいくつかの贵重なリソースがあります:

終わる前に

このチュートリアルにおいて有用だったと思うかもしれません。

他にも最近のブログ投稿がありますので、お楽しみください:

素晴らしい開発者ツールに関するより多くのチュートリアルを見るには、私のブログDTAを訪れてください。

私の他のプロジェクトについてのライブ更新を取得するには、TwitterにFOLLOWしてください。

编码愉快です。