LESSON 30分

ストーリー

高橋アーキテクト
認証で”誰か”が分かった。次は”何ができるか”を決める認可の設計だ

高橋アーキテクトが権限マトリクスを広げました。

高橋アーキテクト
認可モデルには主にRBACとABACがある。システムの要件に応じて適切なモデルを選択し、場合によっては組み合わせて使うことが重要だ

RBAC(ロールベースアクセス制御)

ユーザーにロール(役割)を割り当て、ロールに対して権限を定義するモデルです。

// RBAC の設計
type Role = "viewer" | "editor" | "admin" | "super_admin";
type Permission = string; // "resource:action" 形式

// ロールと権限のマッピング
const rolePermissions: Record<Role, Permission[]> = {
  viewer: [
    "articles:read",
    "comments:read",
  ],
  editor: [
    "articles:read",
    "articles:write",
    "articles:update",
    "comments:read",
    "comments:write",
  ],
  admin: [
    "articles:read",
    "articles:write",
    "articles:update",
    "articles:delete",
    "comments:read",
    "comments:write",
    "comments:delete",
    "users:read",
    "users:update",
  ],
  super_admin: [
    "**:*", // 全権限
  ],
};

// ロール階層(上位ロールは下位の権限を継承)
const roleHierarchy: Record<Role, Role[]> = {
  viewer: [],
  editor: ["viewer"],
  admin: ["editor", "viewer"],
  super_admin: ["admin", "editor", "viewer"],
};

// 権限チェック
const hasPermission = (userRole: Role, permission: Permission): boolean => {
  const allRoles = [userRole, ...roleHierarchy[userRole]];
  return allRoles.some(role => {
    const permissions = rolePermissions[role];
    return permissions.includes(permission) || permissions.includes("**:*");
  });
};

// ミドルウェアとして使用
const requirePermission = (permission: Permission) => {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!hasPermission(req.user.role, permission)) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }
    next();
  };
};

app.delete("/api/articles/:id",
  authenticate,
  requirePermission("articles:delete"),
  deleteArticleHandler
);

ABAC(属性ベースアクセス制御)

ユーザー、リソース、環境の属性に基づいて動的にアクセスを制御するモデルです。

// ABAC の設計
interface AccessRequest {
  subject: {
    userId: string;
    role: string;
    department: string;
    clearanceLevel: number;
  };
  resource: {
    type: string;
    id: string;
    ownerId: string;
    classification: "public" | "internal" | "confidential" | "secret";
  };
  action: "read" | "write" | "delete";
  environment: {
    time: Date;
    ipAddress: string;
    deviceType: "managed" | "unmanaged";
  };
}

// ポリシーの定義
interface AbacPolicy {
  name: string;
  description: string;
  condition: (req: AccessRequest) => boolean;
  effect: "allow" | "deny";
}

const policies: AbacPolicy[] = [
  {
    name: "所有者は自分のリソースを編集可能",
    description: "リソースの所有者はread/write可能",
    condition: (req) =>
      req.subject.userId === req.resource.ownerId &&
      ["read", "write"].includes(req.action),
    effect: "allow",
  },
  {
    name: "機密データは営業時間内のみアクセス可能",
    description: "confidential以上は9-18時のみ",
    condition: (req) => {
      const hour = req.environment.time.getHours();
      return (
        ["confidential", "secret"].includes(req.resource.classification) &&
        (hour < 9 || hour >= 18)
      );
    },
    effect: "deny",
  },
  {
    name: "秘密情報はクリアランスレベル3以上のみ",
    description: "secret分類はclearanceLevel >= 3が必要",
    condition: (req) =>
      req.resource.classification === "secret" &&
      req.subject.clearanceLevel < 3,
    effect: "deny",
  },
  {
    name: "非管理端末からの書き込み禁止",
    description: "unmanaged端末はreadのみ許可",
    condition: (req) =>
      req.environment.deviceType === "unmanaged" &&
      req.action !== "read",
    effect: "deny",
  },
];

// ポリシー評価エンジン
const evaluateAccess = (request: AccessRequest): boolean => {
  // deny-first: 1つでもdenyがあれば拒否
  for (const policy of policies) {
    if (policy.condition(request) && policy.effect === "deny") {
      console.log(`Access denied by policy: ${policy.name}`);
      return false;
    }
  }

  // 明示的なallowがあるか確認
  const allowed = policies.some(
    p => p.condition(request) && p.effect === "allow"
  );
  return allowed;
};

RBAC vs ABAC の比較

項目RBACABAC
複雑さシンプル複雑
柔軟性低い(ロール単位)高い(属性の組み合わせ)
管理コストロール数に比例ポリシー数に比例
適用場面ロールが明確なシステム動的・細粒度の制御が必要なシステム
デバッグ容易困難(ポリシーの競合)
パフォーマンス高速ポリシー評価にコストがかかる

ハイブリッドアプローチ

// RBAC + ABAC のハイブリッド
const checkAccess = async (req: Request, permission: string): Promise<boolean> => {
  // Step 1: RBAC チェック(粗い粒度)
  if (!hasPermission(req.user.role, permission)) {
    return false;
  }

  // Step 2: ABAC チェック(細かい粒度)
  const accessRequest: AccessRequest = {
    subject: {
      userId: req.user.id,
      role: req.user.role,
      department: req.user.department,
      clearanceLevel: req.user.clearanceLevel,
    },
    resource: await getResourceAttributes(req.params.id),
    action: methodToAction(req.method),
    environment: {
      time: new Date(),
      ipAddress: req.ip,
      deviceType: req.headers["x-device-trust"] as "managed" | "unmanaged",
    },
  };

  return evaluateAccess(accessRequest);
};

選択の指針

ユースケース推奨モデル
シンプルなWeb管理画面RBAC
企業の基幹業務システムRBAC + ABAC ハイブリッド
医療・金融の機密データ管理ABAC
マルチテナントSaaSRBAC + テナント属性

まとめ

ポイント内容
RBACロールに権限を付与、シンプルで管理しやすい
ABAC属性の組み合わせで動的に制御、柔軟だが複雑
ハイブリッドRBACで粗い制御 + ABACで細かい制御
選択基準システムの複雑さと要件に応じて選択

チェックリスト

  • RBACのロール階層と権限マッピングを理解した
  • ABACのポリシー定義と評価エンジンを理解した
  • RBAC vs ABACの使い分けを把握した
  • ハイブリッドアプローチの設計方法を理解した

次のステップへ

次は「マルチテナントセキュリティ」を学びます。複数の組織が同じシステムを共有する場合の、データ分離と認可の設計を身につけましょう。


推定読了時間: 30分