You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/auth/design.md

9.2 KiB

Auth 权限系统设计


一、权限码层级

1.1 层级定义

层级从 1 开始计数:
- 奇数层第1、3、5层资源类型
- 偶数层第2、4、6层实例

1.2 示例

app                      → 第1层 (奇数) - 资源类型
app:vbase                → 第2层 (偶数) - 实例
app:vbase:role           → 第3层 (奇数) - 资源类型
app:vbase:role:admin     → 第4层 (偶数) - 实例

二、权限等级

2.1 奇数层(资源类型)

level 二进制 含义
0 000 无权限
1 001 可创建该类型的子资源

2.2 偶数层(实例)

level 二进制 含义
0 000 无权限
2 010 读取
4 100 写入(修改,不能删除)
6 110 读写(读取+修改)
7 111 管理员(完全控制:读写+删除+授权)

三、检查规则

3.1 层级与权限对应

权限 level 检查层级 说明
创建 1 奇数层 检查资源类型层
读取 2 偶数层 检查实例层
写入 4 偶数层 检查实例层
读写 6 偶数层 检查实例层
管理 7 偶数层 检查实例层

3.2 具体规则

创建资源 (level 1)
  → 检查当前 permissionID 对应的奇数层
  → 例: "app:{appID}:role" 检查 "app:{appID}:role" 层

读取/更新/删除资源 (level 2,4,6,7)
  → 检查当前 permissionID 对应的偶数层
  → 如无权限,递归向上检查父实例层
  → 注意:只有 Level 7 (管理员) 权限才会向下继承Level 2,4,6 不会继承
  → 例: "app:{appID}:role:{roleID}" 先检查实例层,再检查 "app:{appID}"

四、权限流程示例

场景一:用户 A 创建应用

1. 用户A创建应用 "VBase"
2. 自动创建权限:
   - PermissionID: "app:vbase"
   - Level: 7 (创建者完全控制)

场景二:用户 A 邀请用户 B 加入应用

1. 用户A授予用户B: app:vbase level 2 (读)
2. 用户B权限表:
   - app:vbase level 2
3. 用户B可执行:
   - ✓ 读取 vbase 信息
   - ✗ 修改/删除

场景三:用户 B 创建角色

前置: 用户B有 app:vbase level 2 (读),需要额外授权

1. 用户A授予用户B: app:vbase:role level 1 (创建角色)
2. 用户B创建角色 "Editor"
3. 自动创建权限:
   - PermissionID: "app:vbase:role:editor"
   - Level: 7

五、Auth 接口设计

VBase 的权限系统基于 vigo 框架的 auth SPI 模式,分为两层:

  • Provider 接口:由 VBase 实现 vbaseProvider,负责实际的权限存储和校验逻辑。
  • Auth 结构体:由 vigo 框架提供 (auth.Auth),包装 Provider提供中间件和高层 API用于业务模块直接调用。

5.1 Provider 接口VBase 实现)

// Provider 是实现端需要实现的 SPI
type Provider interface {
	UserID(x *vigo.X) string
	Check(ctx context.Context, userID, permCode string, permLevel int) bool
	Grant(ctx context.Context, userID, permCode string, permLevel int) error
	Revoke(ctx context.Context, userID, permCode string) error
	ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error)
	ListUsers(ctx context.Context, permCode string) (map[string]int, error)
	GrantRole(ctx context.Context, userID, roleCode string) error
	RevokeRole(ctx context.Context, userID, roleCode string) error
	AddRole(roleCode, roleName string, permPolicies ...string) error
}

VBase 通过 auth.Factory.New(scope) 创建作用域隔离的 Provider 实例,实现多应用间权限隔离。

5.2 Auth 结构体(业务调用)

// cfg.Auth 是全局权限管理实例,由 vigo 框架提供
// 中间件方法:
cfg.Auth.Login()                           // 检查登录(不检查权限)
cfg.Auth.Require(permExpr, permLevel)      // 通用权限检查
cfg.Auth.RequireCreate(permExpr)           // 创建权限 (level 1)
cfg.Auth.RequireRead(permExpr)             // 读取权限 (level 2)
cfg.Auth.RequireWrite(permExpr)            // 写入权限 (level 4)
cfg.Auth.RequireAdmin(permExpr)            // 管理员权限 (level 7)

// 业务调用方法:
cfg.Auth.UserID(x)                         // 获取当前用户 ID
cfg.Auth.Check(ctx, userID, permCode, level)  // 静态权限检查
cfg.Auth.Grant(ctx, userID, permCode, level)  // 授予权限
cfg.Auth.Revoke(ctx, userID, permCode)     // 撤销权限
cfg.Auth.GrantRole(ctx, userID, roleCode)  // 授予角色
cfg.Auth.RevokeRole(ctx, userID, roleCode) // 撤销角色
cfg.Auth.ListResources(ctx, userID, resourceType) // 列出资源权限
cfg.Auth.ListUsers(ctx, permCode)          // 列出资源协作者

5.3 权限等级常量

const (
	LevelNone      = 0
	LevelCreate    = 1 // 001 创建 (检查奇数层)
	LevelRead      = 2 // 010 读取 (检查偶数层)
	LevelWrite     = 4 // 100 写入 (检查偶数层)
	LevelReadWrite = 6 // 110 读写 (检查偶数层)
	LevelAdmin     = 7 // 111 管理员 (完全控制)
)

5.4 Permission 数据模型

type Permission struct {
	ID            string     `json:"id"`
	Scope         string     `json:"scope"`         // 作用域
	UserID        *string    `json:"user_id"`       // 用户ID直接授权
	RoleID        *string    `json:"role_id"`       // 角色ID角色授权
	PermissionID  string     `json:"permission_id"` // 权限ID层级结构
	Level         int        `json:"level"`         // 权限等级
	ExpireAt      *time.Time `json:"expire_at"`     // 过期时间(可选)
}

六、使用示例

6.1 固定写法

var Router = vigo.NewRouter()

func init() {
	// 创建应用 - 需要系统级 app 权限
	Router.Post("/apps", cfg.Auth.RequireCreate("app"), CreateApp)

	// 超级管理员接口
	Router.Get("/admin/users", cfg.Auth.RequireAdmin("*"), AdminListUsers)
}

6.2 动态解析

func init() {
	// 从路径参数获取 appID (默认)
	// GET /apps/{appID}
	Router.Get("/apps/{appID}", cfg.Auth.RequireRead("app:{appID}"), GetApp)

	// 从 query 参数获取
	// GET /apps?appID=xxx
	Router.Get("/apps", cfg.Auth.RequireRead("app:{appID@query}"), GetApp)

	// 多层嵌套
	// GET /apps/{appID}/roles/{roleID}
	Router.Get("/apps/{appID}/roles/{roleID}",
		cfg.Auth.RequireRead("app:{appID}:role:{roleID}"),
		GetRole,
	)
}

6.3 完整示例

var Router = vigo.NewRouter().Use(cfg.Auth.Login())

func init() {
	// 创建应用 - 系统级权限
	Router.Post("/apps", cfg.Auth.RequireCreate("app"), CreateApp)

	// 列出我的应用 - 只需登录
	Router.Get("/apps", ListMyApps)

	// 应用操作 - 从路径获取
	Router.Get("/apps/{appID}", cfg.Auth.RequireRead("app:{appID}"), GetApp)
	Router.Put("/apps/{appID}", cfg.Auth.RequireWrite("app:{appID}"), UpdateApp)
	Router.Delete("/apps/{appID}", cfg.Auth.RequireAdmin("app:{appID}"), DeleteApp)

	// 角色操作 - 嵌套资源
	Router.Post("/apps/{appID}/roles", cfg.Auth.RequireCreate("app:{appID}:role"), CreateRole)
	Router.Get("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireRead("app:{appID}:role:{roleID}"), GetRole)
	Router.Put("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireWrite("app:{appID}:role:{roleID}"), UpdateRole)
	Router.Delete("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireAdmin("app:{appID}:role:{roleID}"), DeleteRole)
}

6.4 动态解析规则

语法 来源 示例
{key} path 参数 {appID}
{key@query} query 参数 {appID@query}
{key@header} header {appID@header}
{key@ctx} context {appID@ctx}

七、接口说明

7.1 业务调用 vs 管理端

此接口是业务层调用,用于:

  • 创建资源时自动授予权限
  • 业务逻辑中检查权限

管理端(如权限管理后台)可以通过直接操作数据库实现批量管理。

7.2 Grant 调用时机

// 创建应用时
func CreateApp(x *vigo.X, req *CreateAppReq) (*AppResp, error) {
	app := models.App{Name: req.Name}
	db.Create(&app)

	// 授予创建者完全控制权限
	cfg.Auth.Grant(x.Context(), userID, "app:"+app.ID, auth.LevelAdmin)

	return &AppResp{App: app}, nil
}

7.3 列表与搜索接口设计

对于资源列表List或搜索接口推荐以下设计模式

  1. 全量管理接口(如后台管理系统):

    • 使用 RequireAdmin("*")RequireAdmin("app:*")
    • 这类接口返回所有数据,必须严格控制权限。
  2. 用户侧列表/搜索(如“我的应用”):

    • 方式一(仅所有者)
      • 使用 Login() 确保登录。
      • 业务层:db.Where("owner_id = ?", userID).Find(&apps)
    • 方式二(协作模式 - 使用 ListResources
      • 调用 Auth 接口获取有权限的 ID 列表。
      • perms, _ := auth.ListResources(ctx, userID, "app")
      • ids := keys(perms)
      • 业务层:db.Where("id IN ?", ids).Find(&apps)
    • 方式三(混合模式)
      • 同时查询 owner_id 和 授权列表。
      • perms, _ := auth.ListResources(...)
      • ids := keys(perms)
      • db.Where("owner_id = ? OR id IN ?", userID, ids).Find(&apps)