まとめ:
複雑な B2B/SaaS システムでは、従来のロールベース (RBAC) アクセス制御は、「許可の粒度が粗い、不正な操作が多い、データベースに負担をかける高頻度の検証」などの致命的な問題に直面することがよくあります。
この記事は、FastAPI + SQLAlchemy (Async) + Redis テクノロジー スタックに基づいており、「システム-組織-プロジェクト」の 3 層 RBAC 権限モデルのアーキテクチャの進化を詳細にレビューします。データ モデルの設計、依存関係注入 (DI) インターセプト、および不正分離の 5 つの鉄則から始めて、同時実行性の高いシナリオで「グローバル バージョン番号」メカニズムを使用して、アクセス許可の変更によって引き起こされるキャッシュ雪崩や一貫性障害を完全に解決する方法の分析に焦点を当てます。
1. はじめに: エンタープライズ レベルのシナリオで単層 RBAC が失敗するのはなぜですか?
初期のシステム (単純なバックエンド管理システムなど) では、通常、権限はフラットです (管理者、ユーザー、ゲストなど)。しかし、実際の SaaS 環境や大企業内では、ユーザー ID は非常に多次元的です。
実際の例を挙げると、Wang Gong は R&D センター (ワークスペース) のシステム管理者ですが、「コア ペイメント プロジェクト (プロジェクト)」の開発者でもあります。単一の文字レイヤーのみを使用した場合、次のような組み合わせを無限に作成する必要があります。 workspace_admin_and_project_developer 役割が混在しているため、役割テーブルが急速に拡大し、ビジネス コードは複雑なものでいっぱいです。 if-else 判定。
この問題を完全に解決するために、「システムレベル(System) – 組織レベル(Workspace) – プロジェクトレベル(Project)」を設計・実装しました。3 層 RBAC モデル。さらに、リクエストごとにデータベースをチェックして権限を確認するというパフォーマンスのボトルネックを解決するために、非常にエレガントな「バージョン番号キャッシュ無効化メカニズム」を導入しました。。
2. コアデータモデルと役割の重み(Weight)
設計レベルでは、役割を定義するだけでなく、各役割に「レベル」を割り当てます。そして「体重」。重みは、その後のクロスレベル分離の実装の中核となる基礎です。
-
システムレベル(システム層): システム管理者 (重み 100)、最高のグローバル権限を持ち、コードにハードコーディングされています。
is_superadminバイパス。RBAC 検証を直接バイパスします。 -
ワークスペースレベル(組織レベル):組織管理者(80名)、役員(70名)、一般会員(60名)
-
プロジェクトレベル(プロジェクトレベル): プロジェクト管理者 (50)、プロジェクト マネージャー (40)、開発/テスト (30)、読み取り専用メンバー (10)。
システム内のユーザーの最終的な有効な権限 = システム レベルの役割の権限 ∪ スペース レベルの役割の権限 ∪ プロジェクト レベルの役割の権限
3. 権限検証チェーン: FastAPI 依存関係注入 (DI) の究極のアプリケーション
FastAPI アーキテクチャでは、Router (ルーティング層) がビジネス ロジックを作成することはなく、許可検証ロジックはもちろんのこと、という鉄則を遵守します。 すべての検証は依存関係の注入によって完了するため、まったく侵入的ではありません。
1. ファクトリ機能と権限の傍受
ファクトリ モードを使用して権限検証の依存関係を生成しました require_project_permission。ユーザーがリクエストを開始すると、FastAPI は最初に依存関係チェーンを実行します。
# deps_permission.py 源码精简
def require_project_permission(resource_code: str):
async def checker(
project_id: int,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
) -> User:
# 1. 超管直接放行
if user.is_superadmin:
return user
# 2. 获取该用户在此项目下的所有角色集合
role_codes = await get_user_all_roles(db, user, project_id=project_id)
# 3. 核心校验:该用户的所有角色中,是否包含 resource_code 这个权限
has_perm = await check_role_permission(db, role_codes, resource_code)
if not has_perm:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"缺少权限: {resource_code}"
)
return user
return checker
ビジネス ルーティングでは、次の 1 行のコードで認証を完了できます。
@router.post("/projects/{project_id}/apis")
async def create_api(..., user: User = Depends(require_project_permission("api:create"))):
2. 階層間および紫外線遮断の「5 つの鉄則」
インターフェース許可ポイントの検証に加えて、「メンバー役割管理」モジュールでは不正な操作 (ユーザーのキック、役割の変更など) が非常に簡単に発生します。この目的のために、サービス層に実装しました。 check_project_member_operation以下の5種類の不正操作を強制的に阻止します。
-
ターゲットは不可侵です:何も操作できません
system_adminレベルアカウント。 -
自己防衛: オペレーターは、自分の役割を変更または削除することはできません (管理者が誤ってアクセス許可を失い、回復できなくなることを防ぐため)。
-
スーパーコントロールリリース: システム管理者は最も高い免疫力を持っています。
-
クロスレベル分離:
workspaceレベル管理者は操作のみ可能workspace異なるレベルのメンバーがレベルを越えて活動することは固く禁じられています。projectレベルのメンバー。 -
重量は厳密に次よりも大きいです: オペレーターの役割の重みは、厳密に次の値より大きくなければなりません。ターゲット メンバーの重み (つまり、ピア保護、プロジェクト A 管理者はプロジェクト B 管理者を追い出すことはできません)。
4. パフォーマンスの課題: 高頻度の権限検証によるデータベースの危機
数十のモジュールと数百の API を備えたエンタープライズ レベルのシステムで、すべての HTTP リクエストがデータベースに送信された場合 JOIN クエリ (ユーザー -> ロール -> ロール権限) を実行すると、データベースの QPS が即座に過剰になり、インターフェイスの応答時間 (RT) がミリ秒から数百ミリ秒に低下します。
そこで、パーミッションキャッシュとしてRedisを導入しました。しかし、キャッシュの世界で最も厄介な問題は次のとおりです。それはキャッシュの一貫性です。。
管理者がバックグラウンドでロールの権限を変更したり、プロジェクトから誰かを削除したりする場合、ユーザーの権限キャッシュをただちに無効にする必要があります。従来の方法には通常、次の問題点があります。
-
トラバース削除: このロールの下にあるすべてのユーザーを検索し、Redis に 1 人ずつ移動します
DEL彼らの鍵。このロールの下に 10,000 人のユーザーがいる場合、このネットワーク I/O オーバーヘッドの波は非常にひどいものになります。 -
すべてクリア:直接
FLUSHDBまたは、すべての権限キーを削除します。これにより、Redis が即座にダウンし、大量のリクエストが PostgreSQL データベースに送信され、「キャッシュ雪崩」が引き起こされます。
5. 究極の解決策: 「グローバル バージョン番号」に基づいた適切なキャッシュ障害
上記の問題点を解決するために、従来の削除戦略を放棄し、「グローバル バージョン番号 (Version)」を導入しました。機構。
1. デザイン思考: 空間を時間に置き換える
Redis ではグローバル バージョン番号を管理しています (例: permissions:version初期値は 1)。
クライアント側でキャッシュ キーを結合するときは、現在のグローバル バージョン番号を導入します。user:permissions:{user_id}:{version}。
ロールの権限が変更されるたびに、特定のユーザーのキャッシュを削除するのではなく、 INCR グローバルバージョン番号を変更するコマンド 1 なる 2。
この時点で、すべてのユーザーの古いキャッシュ キー ( 1 end) はすぐに「孤立キー」になり、すべてのリクエストは自動的に新しいキーを読み取るか生成します (で終わる 2 終わり)。
2. コアソースコードの実装
存在する permissions_cache.py コアロジックは次のとおりです。
import json
from app.core.redis import redis_manager
DEFAULT_TTL = 1800 # 缓存 30 分钟
async def init_version():
"""应用启动时,初始化版本号(防止重启后版本丢失)"""
if not await redis_manager.exists("permissions:version"):
await redis_manager.set("permissions:version", 1)
async def get_user_permissions_from_cache(user_id: int) -> list[str] | None:
"""从缓存读取用户权限"""
# 1. 动态获取当前的全局版本号
version = await redis_manager.get("permissions:version") or "1"
# 2. 拼接带版本号的 Key
cache_key = f"user:permissions:{user_id}:{version}"
# 3. 尝试读取缓存
cached_data = await redis_manager.get(cache_key)
if cached_data:
return json.loads(cached_data)
return None
async def cache_user_permissions(user_id: int, permissions: list[str]):
"""将计算好的权限写入缓存"""
version = await redis_manager.get("permissions:version") or "1"
cache_key = f"user:permissions:{user_id}:{version}"
await redis_manager.setex(cache_key, DEFAULT_TTL, json.dumps(permissions))
async def invalidate_all_permissions():
"""
全量失效:角色权限变更时调用
只需执行 INCR,所有用户的旧 Key 瞬间失效,极速完成
"""
await redis_manager.incr("permissions:version")
async def invalidate_user_permissions(user_id: int):
"""
精准失效:单用户角色变更(如被踢出项目)时调用
需配合 Lua 脚本或扫描匹配删除(因为不知道旧版本的精确值)
"""
# 简化写法:直接让该用户当前版本的 Key 失效
version = await redis_manager.get("permissions:version") or "1"
cache_key = f"user:permissions:{user_id}:{version}"
await redis_manager.delete(cache_key)
3. プログラムのメリット分析
-
非常に早い失敗: ユーザーを 1 人だけ走査する必要はありません。
INCRコマンドには最大でマイクロ秒かかります。 -
雪崩防止ツール: バージョン番号が変更されると、古いキャッシュ (バージョン 1 など) はまだ存在しますが、読み取られなくなります。リクエストが徐々にバージョン 2 に移行すると、古いキャッシュは 30 分の TTL で自然に期限切れになり、移行は完全にスムーズになります。
-
正確な打撃をサポート: 組み合わせる
invalidate_user_permissions特定のユーザーのロールのみが変更される場合は、グローバル バージョン番号を変更せずにユーザーの専用キーを削除するだけです。
6. まとめと考察
エンタープライズ レベルのシステムには、完璧な特効薬はなく、シナリオに最適な妥協点があるだけです。
3 層の RBAC は、複雑な組織構造下での権限割り当ての問題を解決し、「バージョン番号キャッシュ無効化」メカニズムは、高頻度の変更下でのキャッシュの一貫性の問題を非常に軽量なコストで解決します。
AITest V1.0 の実際の実装では、このアーキテクチャ ソリューションは数万のインターフェイス リクエストをサポートすることに成功し、その高可用性を証明しました。この記事の落とし穴と設計上のアイデアが、パーミッション システムに取り組んでいる同僚の参考になれば幸いです。
著者: ジェイソン。フルスタック開発とアーキテクチャ設計に焦点を当てます。現在、独自に開発したエンタープライズレベルのフルスタック テスト プラットフォーム (AITest) の実践的なレビューが連載中です。詳しくは個人ホームページをご覧ください。コミュニケーションへようこそ。