はじめに
「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」24日目の記事は、ちょっと高度な認証周りのの概念についてです。
基本的な「ログイン機能」は実装できるようになったかもしれませんが、これを大規模なアプリケーションへ広げていこうとすると、
- 「ログインはできたけど、『管理者』と『一般ユーザー』で使える機能を分けたいな...」
- 「複数の会社で使うシステムを作りたいけど、A社のデータがB社に見えたら、大問題だよね...」
といった具合に考えることが増えます。
そんな悩みを解決してくれるのが、ロールベースアクセス制御 (RBAC) と マルチテナント という考え方です。
この記事では、これらの概念を、具体的な「どう設計して、どう実装していくか?」という思考プロセスに焦点を当てて解説します。
- 基本的な認証・認可を理解し、次のステップに進みたい方
- BtoBのSaaSのような、複雑な権限管理が必要なアプリケーションの設計に興味がある方
- データベースのテーブル設計と絡めて、実践的な権限管理の方法を学びたい方
この記事で、RBACとマルチテナントを実現するためのテーブル設計やロジックを理解して、大規模SaaSを作ることが怖くないと胸を張れるように、頑張っていきましょう!
ロールベースアクセス制御 (RBAC) とは?
RBAC (Role-Based Access Control) は、ユーザーに直接「記事を削除できる」といった権限を与えるのではなく、「役割(ロール)」という中間層を設けて権限を管理する考え方です。
- ユーザー(Aさん、Bさん)
- ロール(管理者、編集者、閲覧者)
- 権限(記事の作成、記事の削除、ユーザーの招待)
例えば、「編集者」ロールに「記事の作成」権限を紐付けておけば、Aさんを「編集者」にするだけで、Aさんは記事を作成できるようになります。
ユーザーが100人に増えても、権限の設定を見直す必要はありません。
例えば、新しく入ったCさんに「編集者」ロールを与えるだけで、適切な権限が自動的に付与される、といった感じになるので、管理が非常にシンプルになります。
RBACのデータモデルを考える
では、この仕組みをデータベースでどう表現すれば良いでしょうか。
登場人物をテーブルとして定義する
まず、先ほどの3つの登場人物を、それぞれテーブルとして定義します。
-
usersテーブル -
rolesテーブル -
permissionsテーブル
関係性を考える
次に、これらのテーブルの関係を考えます。
- 一人のユーザーは、「編集者」でありながら「特定のプロジェクトのリーダー」のように、複数のロールを持つことができます。 => UsersとRolesは多対多
- 一つのロール(例: 管理者)は、「ユーザー招待」も「記事削除」もできるように、複数の権限を持つことができます。 => RolesとPermissionsは多対多
中間テーブルを導入する
データベースで「多対多」の関係を表現するには、2つのテーブルを繋ぐ**中間テーブル(Join Table)**が必要です。
したがって、最終的なテーブル設計は以下のようになります。
-
users(id, name, email, ...) -
roles(id, name)
例: (1, 'admin'), (2, 'editor') -
permissions(id, name)
例: (1, 'posts:create'), (2, 'posts:delete'), (3, 'users:invite') -
user_roles(user_id, role_id)
usersとrolesを繋ぐ中間テーブル -
role_permissions(role_id, permission_id)
rolesとpermissionsを繋ぐ中間テーブル
この5つのテーブルで、土台が完成しました。
認可ロジックを考える
データモデルができたので、次はAPIリクエストが来た時に、どうやって権限をチェックするかのロジックを考えます。
これは通常、認証ミドルウェアの次に来る認可ミドルウェアで実装します。
- リクエストから認証済みのユーザーIDを取得します。
- その
user_idを元に、DBに問い合わせ、user_roles,role_permissionsテーブルをJOINして、そのユーザーが持つすべての権限(permission.name)のリストを取得します。 - ユーザーが実行しようとしている操作(例: 記事の削除)に必要な権限(例:
posts:delete)をあらかじめ定義しておきます。 - ステップ2で取得した権限リストの中に、ステップ3で定義した必要な権限が含まれているかをチェックします。
- 含まれていればリクエストを許可し、次の処理へ。含まれていなければ「403 Forbidden」エラーを返します。
マルチテナントとは?
マルチテナントは、一つのアプリケーションを、A社、B社、C社のような複数の顧客(テナント)で共有して利用するアーキテクチャです。
多くのSaaS (Software as a Service) がこのモデルを採用しています。
ここで完全に達成されなければいけないことは、「データの完全な分離」 です。
A社のユーザーが、絶対にB社のデータにアクセスできないようにしなければいけません。
マルチテナントのデータモデルを考える
このデータ分離を実現する方法は、すべてのデータテーブルに tenant_id カラムを追加することです。
登場人物と関係性を考える
-
tenantsテーブル: 顧客(会社)の情報を管理します。 -
usersテーブル: ユーザーは、必ずどこか一つのテナントに所属します。 -
postsテーブル(例): 投稿データも、必ずどこか一つのテナントに紐付きます。
テーブル設計を考える
この関係性を元に、テーブルを設計します。
-
tenants(id, name)
例: (1, '株式会社A'), (2, '合同会社B') -
users(id, name, ..., tenant_id)
tenant_idカラムで、どのテナントの所属かを示す -
posts(id, title, ..., tenant_id, user_id)
この投稿がどのテナントのものかを示すtenant_idカラムを追加
このように、テナントに属するすべてのテーブルにtenant_idを持たせることが基本戦略です。
認可ロジックを考える
データモデルができたら、次はロジックです。マルチテナントのセキュリティは、このロジックが肝となります。
-
ユーザーがログインすると、認証サーバーはJWTトークンを発行します。このトークンのペイロード(中身)に、
user_idだけでなく、そのユーザーが所属するtenant_idも含めておきます。 -
ユーザーがAPIリクエストを送ると、ミドルウェアがトークンを検証し、リクエストコンテキストに
user_idとtenant_idを保存します。 -
ユーザーが記事一覧を取得しようとします (
GET /posts)。 -
リポジトリ層(DBアクセス層)は、記事を取得する際に、絶対に
tenant_idが関連したような以下のようなクエリを発行します。SELECT * FROM posts WHERE tenant_id = ??の部分には、ステップ2でコンテキストに保存した、ログイン中ユーザーのtenant_idを入れます。
ポイントは、SELECT * FROM posts のようなクエリは絶対に発行してはいけないことです。
すべてのDBアクセス処理において、必ずログイン中ユーザーのtenant_idで絞り込みを行うというルールを徹底することが、マルチテナントのデータ分離を保証するためです。
おわりに
この記事では、大規模なアプリケーションで必要となる「RBAC」と「マルチテナント」の考え方について、解説しました。
一見すると複雑で難しそうに見えるこれらの要件も、こうしてデータモデルとロジックの思考プロセスに分解してみると、意外と簡単に整理できることに気づき、設計の幅が少し広がった気がしました。
最後までお読みいただき、ありがとうございました!
この「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」では、GoやJavaを使い、APIの作り方、データベースとの接続、テストやDockerといった気になったバックエンド技術の基本を振り返った学びを共有しています。
ぜひ他の記事もチェックして、筆者がこのひとりアドカレを完遂することができるか、確認してみてください(^^)
学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025
それでは、明日の「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」の記事もお楽しみに!
参考文献