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

10 KiB

Auth 权限系统设计


一、权限码层级

1.1 层级定义

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

1.2 示例

org                      → 第1层 (奇数) - 资源类型
org:orgA                → 第2层 (偶数) - 实例
org:orgA:project        → 第3层 (奇数) - 资源类型
org:orgA:project:projB → 第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 对应的奇数层
  → 例: "org:{orgID}:project" 检查 "org:{orgID}:project" 层

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

四、权限流程示例

场景一:用户 A 创建组织

1. 用户A创建组织 "公司A"
2. 自动创建权限:
   - PermissionID: "org:org_companyA"
   - Level: 7 (创建者完全控制)

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

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

场景三:用户 B 创建项目

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

1. 用户A授予用户B: org:org_companyA:project level 1 (创建项目)
2. 用户B创建项目 "项目X"
3. 自动创建权限:
   - PermissionID: "org:org_companyA:project:project_X"
   - Level: 7

场景四:用户 C 加入项目并创建文档

1. 用户B授予用户C: org:org_companyA:project:project_X level 2 (读)
2. 用户C需要额外授权才能创建文档
3. 用户C创建文档 "文档Y"
4. 自动创建权限:
   - PermissionID: "org:org_companyA:project:project_X:doc:doc_Y"
   - Level: 7

五、Auth 接口设计

package auth

import (
	"context"
	"github.com/veypi/vigo"
)

// ========== 权限等级 ==========

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

// PermFunc 权限检查函数类型
type PermFunc func(x *vigo.X) error

// Auth 权限管理接口
type Auth interface {
	// ========== 上下文 ==========

	// UserID 获取当前用户ID
	UserID(x *vigo.X) string

	// ========== 登录检查 ==========

	// Login 检查用户是否登录
	Login() PermFunc

	// ========== 权限检查 ==========

	// Perm 检查权限
	// code: 权限码,支持动态解析
	//   - 固定写法: "org:orgA"
	//   - 动态解析: "org:{orgID}" 从 path 获取
	//               "org:{orgID@query}" 从 query 获取
	//               "org:{orgID@header}" 从 header 获取
	//               "org:{orgID@ctx}" 从 ctx 获取
	// level: 需要的权限等级
	Perm(code string, level int) PermFunc

	// ========== 快捷方法 ==========

	// PermCreate 检查创建权限 (level 1检查奇数层)
	PermCreate(code string) PermFunc

	// PermRead 检查读取权限 (level 2检查偶数层)
	PermRead(code string) PermFunc

	// PermWrite 检查更新权限 (level 4检查偶数层)
	PermWrite(code string) PermFunc

	// PermAdmin 检查管理员权限 (level 7检查偶数层)
	PermAdmin(code string) PermFunc

	// ========== 权限授予(业务调用) ==========

	// Grant 授予权限
	// 在创建资源、被授权等业务逻辑中调用
	// permissionID: 权限码,如 "org:orgA"
	// level: 权限等级
	Grant(ctx context.Context, userID, permissionID string, level int) error

	// Revoke 撤销权限
	Revoke(ctx context.Context, userID, permissionID string) error

	// ========== 权限查询 ==========

	// Check 检查权限 不支持动态解析
	// permissionID: 完整的权限码,如 "org:orgA"
	Check(ctx context.Context, userID, permissionID string, level int) bool

	// ========== 资源列表查询 ==========

	// ListResources 查询用户在特定资源类型下的详细权限信息
	// 用于解决 "查询我有权限的 org 列表" 等场景
	// userID: 用户ID
	// resourceType: 资源类型 (奇数层),如 "org" 或 "org:{orgID}:project"
	// 返回: map[实例ID]权限等级 (如 {"orgA": 2, "orgB": 7})
	ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error)

	// ListUsers 查询特定资源的所有协作者及其权限
	// 用于解决 "查看这个项目有哪些成员" 等场景
	// permissionID: 资源实例权限码,如 "org:orgA"
	// 返回: map[用户ID]权限等级 (如 {"user1": 2, "user2": 7})
	ListUsers(ctx context.Context, permissionID string) (map[string]int, error)
}

// ========== 数据结构 ==========

// Permission 用户权限
type Permission struct {
	ID            string `json:"id"`
	UserID        string `json:"user_id"`
	PermissionID  string `json:"permission_id"`
	Level         int    `json:"level"`
	CreatedAt     int64  `json:"created_at"`
	UpdatedAt     int64  `json:"updated_at"`
}

六、使用示例

6.1 固定写法

var Router = vigo.NewRouter()

func init() {
	// 创建组织 - 需要系统级 org 权限
	Router.Post("/orgs", cfg.Auth.PermCreate("org"), CreateOrg)

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

6.2 动态解析

func init() {
	// 从路径参数获取 orgID (默认)
	// GET /orgs/{orgID}
	Router.Get("/orgs/{orgID}", cfg.Auth.PermRead("org:{orgID}"), GetOrg)

	// 从 query 参数获取
	// GET /orgs?orgID=xxx
	Router.Get("/orgs", cfg.Auth.PermRead("org:{orgID@query}"), GetOrg)

	// 多层嵌套
	// GET /orgs/{orgID}/projects/{projectID}
	Router.Get("/orgs/{orgID}/projects/{projectID}",
		cfg.Auth.PermRead("org:{orgID}:project:{projectID}"),
		GetProject,
	)
}

6.3 完整示例

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

func init() {
	// 创建组织 - 系统级权限
	Router.Post("/orgs", cfg.Auth.PermCreate("org"), CreateOrg)

	// 列出我的组织 - 只需登录
	Router.Get("/orgs", ListMyOrgs)

	// 组织操作 - 从路径获取
	Router.Get("/orgs/{orgID}", cfg.Auth.PermRead("org:{orgID}"), GetOrg)
	Router.Put("/orgs/{orgID}", cfg.Auth.PermWrite("org:{orgID}"), UpdateOrg)
	Router.Delete("/orgs/{orgID}", cfg.Auth.PermAdmin("org:{orgID}"), DeleteOrg)

	// 项目操作 - 嵌套资源
	Router.Post("/orgs/{orgID}/projects", cfg.Auth.PermCreate("org:{orgID}:project"), CreateProject)
	Router.Get("/orgs/{orgID}/projects/{projectID}", cfg.Auth.PermRead("org:{orgID}:project:{projectID}"), GetProject)
	Router.Put("/orgs/{orgID}/projects/{projectID}", cfg.Auth.PermWrite("org:{orgID}:project:{projectID}"), UpdateProject)
	Router.Delete("/orgs/{orgID}/projects/{projectID}", cfg.Auth.PermAdmin("org:{orgID}:project:{projectID}"), DeleteProject)

	// 文档操作
	Router.Post("/orgs/{orgID}/projects/{projectID}/docs", cfg.Auth.PermCreate("org:{orgID}:project:{projectID}:doc"), CreateDoc)
	Router.Get("/orgs/{orgID}/projects/{projectID}/docs/{docID}", cfg.Auth.PermRead("org:{orgID}:project:{projectID}:doc:{docID}"), GetDoc)
}

6.4 动态解析规则

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

七、接口说明

7.1 业务调用 vs 管理端

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

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

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

7.2 Grant 调用时机

// 创建组织时
func CreateOrg(x *vigo.X, req *CreateOrgReq) (*OrgResp, error) {
	org := models.Org{Name: req.Name}
	db.Create(&org)

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

	return &OrgResp{Org: org}, nil
}

7.3 列表与搜索接口设计

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

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

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

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

    • 针对 特定资源实例 的操作URL 中包含 ID/projects/{id})。
    • 针对 共享资源 的访问控制。
    • 针对 管理功能 的鉴权。