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