262 lines
7.8 KiB
Markdown
262 lines
7.8 KiB
Markdown
|
|
---
|
|||
|
|
tags: [pattern/auth, rbac, role-inheritance, separation-of-duty, access-control]
|
|||
|
|
create time: 2026-08-08 15:30
|
|||
|
|
update time: 2026-08-08 15:30
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# RBAC 权限模型
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
RBAC(Role-Based Access Control)是一种基于角色分配访问权限的模型,是目前企业级应用中最主流的权限管理方案。它的核心思想是**将用户与角色关联,而非将用户与权限直接绑定**——这层间接大大降低了权限管理的复杂度。
|
|||
|
|
|
|||
|
|
## 五张表设计
|
|||
|
|
|
|||
|
|
### 表结构定义
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 1. 用户表
|
|||
|
|
CREATE TABLE users (
|
|||
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
username VARCHAR(64) NOT NULL UNIQUE,
|
|||
|
|
password_hash VARCHAR(128) NOT NULL,
|
|||
|
|
email VARCHAR(128),
|
|||
|
|
status TINYINT DEFAULT 1 COMMENT '1-active 0-disabled',
|
|||
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- 2. 角色表
|
|||
|
|
CREATE TABLE roles (
|
|||
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
name VARCHAR(64) NOT NULL UNIQUE,
|
|||
|
|
description VARCHAR(256),
|
|||
|
|
is_super TINYINT DEFAULT 0 COMMENT '是否超级管理员',
|
|||
|
|
parent_id BIGINT DEFAULT NULL COMMENT '父角色ID(用于继承)',
|
|||
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- 3. 权限表
|
|||
|
|
CREATE TABLE permissions (
|
|||
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
code VARCHAR(128) NOT NULL UNIQUE COMMENT '权限标识如 user:create',
|
|||
|
|
resource VARCHAR(64) NOT NULL COMMENT '资源类型: user/order/bucket',
|
|||
|
|
action VARCHAR(64) NOT NULL COMMENT '操作: create/read/update/delete',
|
|||
|
|
description VARCHAR(256)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- 4. 用户-角色(多对多)
|
|||
|
|
CREATE TABLE user_roles (
|
|||
|
|
user_id BIGINT NOT NULL,
|
|||
|
|
role_id BIGINT NOT NULL,
|
|||
|
|
PRIMARY KEY (user_id, role_id),
|
|||
|
|
FOREIGN KEY (user_id) REFERENCES users(id),
|
|||
|
|
FOREIGN KEY (role_id) REFERENCES roles(id)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- 5. 角色-权限(多对多)
|
|||
|
|
CREATE TABLE role_permissions (
|
|||
|
|
role_id BIGINT NOT NULL,
|
|||
|
|
permission_id BIGINT NOT NULL,
|
|||
|
|
PRIMARY KEY (role_id, permission_id),
|
|||
|
|
FOREIGN KEY (role_id) REFERENCES roles(id),
|
|||
|
|
FOREIGN KEY (permission_id) REFERENCES permissions(id)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ER 关系图
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
erDiagram
|
|||
|
|
users ||--o{ user_roles : "belongs to"
|
|||
|
|
roles ||--o{ user_roles : "assigned via"
|
|||
|
|
roles ||--o{ role_permissions : "has"
|
|||
|
|
permissions ||--o{ role_permissions : "granted through"
|
|||
|
|
|
|||
|
|
users {
|
|||
|
|
BIGINT id PK
|
|||
|
|
varchar username
|
|||
|
|
varchar password_hash
|
|||
|
|
tinyint status
|
|||
|
|
}
|
|||
|
|
roles {
|
|||
|
|
BIGINT id PK
|
|||
|
|
varchar name
|
|||
|
|
varchar description
|
|||
|
|
tinyint is_super
|
|||
|
|
BIGINT parent_id FK
|
|||
|
|
}
|
|||
|
|
permissions {
|
|||
|
|
BIGINT id PK
|
|||
|
|
varchar code UK
|
|||
|
|
varchar resource
|
|||
|
|
varchar action
|
|||
|
|
}
|
|||
|
|
user_roles {
|
|||
|
|
BIGINT user_id PK
|
|||
|
|
BIGINT role_id PK
|
|||
|
|
}
|
|||
|
|
role_permissions {
|
|||
|
|
BIGINT role_id PK
|
|||
|
|
BIGINT permission_id PK
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 核心原理
|
|||
|
|
|
|||
|
|
### 用户 → 角色 → 权限 的两层映射
|
|||
|
|
|
|||
|
|
传统 ACL(Access Control List)直接将用户和权限绑定:
|
|||
|
|
```
|
|||
|
|
Alice → read_order, write_order, delete_user
|
|||
|
|
Bob → read_order
|
|||
|
|
Carol → read_order, write_bucket
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
问题在于当需要批量授权时(比如给所有财务加 20 个权限),必须逐一修改每个用户的权限列表。
|
|||
|
|
|
|||
|
|
RBAC 引入角色层后变成:
|
|||
|
|
```
|
|||
|
|
角色定义:
|
|||
|
|
Finance → 20 个权限(包括 read_order, export_report...)
|
|||
|
|
Operator → 10 个权限(read_order, write_bucket...)
|
|||
|
|
|
|||
|
|
用户分配:
|
|||
|
|
Alice → Finance
|
|||
|
|
Bob → Operator
|
|||
|
|
Carol → Operator
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
批量调整只需改角色的权限映射,不影响用户。
|
|||
|
|
|
|||
|
|
### 超级管理员与普通角色的继承
|
|||
|
|
|
|||
|
|
超级管理员通常拥有所有权限。但直接给 super_admin 角色分配几百条权限记录既繁琐也不优雅。更合理的做法是**继承机制**:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 在 roles 表中用 parent_id 表示层级
|
|||
|
|
INSERT INTO roles (name, is_super, parent_id) VALUES ('super_admin', 1, NULL);
|
|||
|
|
INSERT INTO roles (name, is_super, parent_id) VALUES ('admin', 0, (SELECT id FROM roles WHERE name='super_admin'));
|
|||
|
|
INSERT INTO roles (name, is_super, parent_id) VALUES ('finance', 0, (SELECT id FROM roles WHERE name='admin'));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
查询某用户全部权限时递归展开:
|
|||
|
|
```go
|
|||
|
|
func (m *AuthManager) GetUserPermissions(ctx context.Context, userID int64) ([]string, error) {
|
|||
|
|
// 1. 查用户所有角色
|
|||
|
|
roles, _ := m.db.QueryContext(ctx, `
|
|||
|
|
SELECT r.id, r.is_super, r.parent_id FROM user_roles ur
|
|||
|
|
JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = ?`, userID)
|
|||
|
|
|
|||
|
|
var allCodes []string
|
|||
|
|
for roles.Next() {
|
|||
|
|
var role Role
|
|||
|
|
roles.Scan(&role.ID, &role.IsSuper, &role.ParentID)
|
|||
|
|
|
|||
|
|
if role.IsSuper {
|
|||
|
|
return []string{"*"}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
perms, _ := m.getRolePermissionCodes(ctx, role.ID)
|
|||
|
|
allCodes = append(allCodes, perms...)
|
|||
|
|
}
|
|||
|
|
return deduplicate(allCodes), nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 互斥职责分离(Separation of Duty)
|
|||
|
|
|
|||
|
|
某些场景下同一人不能同时拥有冲突的角色,例如:
|
|||
|
|
- **申请人与审批人不可同一人** — 你不能批准自己的报销单
|
|||
|
|
- **出纳与会计不可兼任** — 财务内控基本要求
|
|||
|
|
|
|||
|
|
实现方式是在角色创建或用户分配时做冲突检测:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 假设 conflict_roles 表定义互斥关系
|
|||
|
|
CREATE TABLE conflict_roles (
|
|||
|
|
role_a BIGINT NOT NULL,
|
|||
|
|
role_b BIGINT NOT NULL,
|
|||
|
|
PRIMARY KEY (role_a, role_b)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- 分配角色前检查
|
|||
|
|
SELECT COUNT(*) FROM conflict_roles cr
|
|||
|
|
JOIN user_roles ur ON ur.role_id IN (cr.role_a, cr.role_b)
|
|||
|
|
WHERE ur.user_id = ? AND cr.role_a = ? AND cr.role_b = ?
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!WARNING]
|
|||
|
|
> 互斥约束应该在**分配角色层**校验,而不是每次鉴权时检查。前者是策略问题,后者是执行问题——在入口处挡住比到处拦击效率高得多。
|
|||
|
|
|
|||
|
|
### 动态权限加载流程
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant User as 用户
|
|||
|
|
participant API as 认证服务
|
|||
|
|
participant DB as 数据库
|
|||
|
|
participant Redis as 缓存
|
|||
|
|
|
|||
|
|
User->>API: 登录
|
|||
|
|
API->>DB: 查询用户所有角色
|
|||
|
|
DB-->>API: 返回角色列表
|
|||
|
|
API->>DB: 查询各角色的权限码
|
|||
|
|
DB-->>API: 返回权限码列表
|
|||
|
|
API->>Redis: 写入用户权限缓存 (TTL=2h)
|
|||
|
|
Redis-->>API: OK
|
|||
|
|
API-->>User: 登录成功 + Token
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!TIP]
|
|||
|
|
> 登录时将完整权限树缓存在 Redis 中,鉴权接口只需 O(1) 读取缓存。权限变更时主动删除对应用户的缓存条目,实现最终一致。不要每次都查库。
|
|||
|
|
|
|||
|
|
## 代码示例:权限中间件(Go)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type ContextKey string
|
|||
|
|
|
|||
|
|
const PermissionKey ContextKey = "permissions"
|
|||
|
|
|
|||
|
|
func AuthMiddleware(h Handler) http.HandlerFunc {
|
|||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
token := extractToken(r)
|
|||
|
|
claims, _ := jwt.Parse(token)
|
|||
|
|
userID := claims.UserID
|
|||
|
|
|
|||
|
|
// 从 Redis 获取权限
|
|||
|
|
perms, err := cache.GetPermissions(r.Context(), userID)
|
|||
|
|
if err != nil {
|
|||
|
|
perms = loadFromDB(userID)
|
|||
|
|
cache.Set(userID, perms, 2*time.Hour)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ctx := context.WithValue(r.Context(), PermissionKey, perms)
|
|||
|
|
h.ServeHTTP(w, r.WithContext(ctx))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查某个权限
|
|||
|
|
func HasPermission(ctx context.Context, required string) bool {
|
|||
|
|
perms := ctx.Value(PermissionKey).([]string)
|
|||
|
|
for _, p := range perms {
|
|||
|
|
if p == "*" || p == required {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 实践场景
|
|||
|
|
|
|||
|
|
| 场景 | 选型建议 |
|
|||
|
|
|------|---------|
|
|||
|
|
| 小型系统(用户 < 100) | 简单角色 + 硬编码判断即可,不必上完整 RBAC |
|
|||
|
|
| 中型业务系统 | 标准五表 RBAC + Redis 缓存 |
|
|||
|
|
| 平台型 SaaS | RBAC + 租户隔离(每租户可自定义角色) |
|
|||
|
|
| 强合规要求(金融/医疗) | RBAC + SoD 互斥约束 + 完整审计日志 |
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[OAuth2 与 JWT]]
|