跳到主要内容

RBAC、ABAC 与多租户权限

🛡️ 权限设计影响平台的长期可维护性。过早确定权限模型,后期方能少走弯路。

权限模型对比

模型全称适用场景复杂度
ACL访问控制列表资源级权限
RBAC基于角色的访问控制内部系统
ABAC基于属性的访问控制精细化权限

RBAC(基于角色)

// 核心关系:用户 → 角色 → 权限
type Role = 'admin' | 'editor' | 'viewer'
type Permission = 'read' | 'write' | 'delete'

const rolePermissions: Record<Role, Permission[]> = {
admin: ['read', 'write', 'delete'],
editor: ['read', 'write'],
viewer: ['read'],
}

function hasPermission(user: User, permission: Permission): boolean {
return user.roles.some(
role => rolePermissions[role]?.includes(permission)
)
}

RBAC 资源级权限

// 对特定资源的细粒度控制
type ResourceRole = {
userId: string
resourceId: string
role: 'owner' | 'editor' | 'viewer'
}

async function canEditDocument(userId: string, docId: string): Promise<boolean> {
const membership = await db.getResourceRole(userId, docId)
return ['owner', 'editor'].includes(membership?.role)
}

ABAC(基于属性)

// Policy 规则引擎
type Policy = {
subject: Record<string, string> // 用户属性
resource: Record<string, string> // 资源属性
action: string
effect: 'allow' | 'deny'
}

const policies: Policy[] = [
{
subject: { department: 'finance' },
resource: { type: 'report', classification: 'confidential' },
action: 'read',
effect: 'allow',
}
]

function evaluate(user: User, resource: Resource, action: string): boolean {
return policies.some(p =>
p.effect === 'allow' &&
p.action === action &&
matchAttributes(user, p.subject) &&
matchAttributes(resource, p.resource)
)
}

OPA(Open Policy Agent)

# Rego 策略语言
package authz

default allow = false

allow {
input.user.department == "finance"
input.resource.classification == "confidential"
input.action == "read"
}

多租户权限

租户隔离设计

// 所有资源必须带 tenantId
interface Resource {
id: string
tenantId: string // 必须!
// ...
}

// 所有查询按租户过滤
const docs = await db.query(
'SELECT * FROM documents WHERE tenant_id = ?',
[currentUser.tenantId]
)

// 查询前检查资源归属
async function getDocument(docId: string, tenantId: string) {
const doc = await db.getDocument(docId)
if (doc.tenantId !== tenantId) {
throw new ForbiddenError('Cross-tenant access denied')
}
return doc
}

租户级限流

// 每个租户有独立的资源配额
const quotas: Record<string, { apiCallsPerHour: number }> = {
'tenant-free': { apiCallsPerHour: 100 },
'tenant-pro': { apiCallsPerHour: 10000 },
}

async function checkQuota(tenantId: string, plan: string): Promise<boolean> {
const key = `quota:${tenantId}:${getCurrentHour()}`
const count = await redis.incr(key)
await redis.expire(key, 3600)
return count <= quotas[plan].apiCallsPerHour
}

常见误区

  • 权限检查只在 API 网关层,氪有在服务层添加,内部调用可绕过
  • 忽略创建/修改资源时的租户所属求证,导致跨租户数据污染
  • 角色权限硬编码在代码中,不支持动态配置