跳到主要内容

后端用户体系架构

本文档详细阐述了井云后端系统的用户(User)体系架构,涵盖了数据模型、多租户结构、认证与授权机制。

1. 核心设计思想

用户体系遵循以下核心设计原则:

  • 统一用户模型: 系统中所有类型的用户(平台管理员、普通成员、租户所有者等)共享一个统一的数据模型。
  • 服务解耦: 用户(User)、认证(Auth)和租户(Tenant)功能被拆分到独立的微服务中,服务之间通过 gRPC 进行通信,数据库层面物理隔离。
  • 网关集中认证: 用户的 JWT Token 由 API 网关(Gateway)进行统一验证。认证成功后,网关将用户的核心信息(如用户ID和角色)注入到 gRPC 请求的元数据(Metadata)中,供下游微服务使用。

2. 数据模型与多租户结构

系统的多租户结构是通过用户表内的一个精巧设计实现的,而非依赖传统的独立租户表。

2.1. User 实体

核心的用户信息存储在 user 服务的 User 表中。其关键字段定义在 services/user/internal/data/ent/schema/user.go

  • id: 全局唯一的用户ID。
  • role: enum 类型,是区分用户身份和实现多租户的关键。
    • admin: 平台超级管理员。
    • member: 平台普通成员(非租户)。
    • tenant: 租户所有者。一个角色为 tenant 的用户代表一个租户的根节点。
    • tenant_user: 租户成员。隶属于某个租户的普通用户。
  • belongs_to_user_id: int64 类型,用于建立租户内的层级关系。
    • 如果一个用户的 roletenant_user,该字段将指向其所属租户的 tenant 用户的 id
    • 对于 admin, member, tenant 类型的用户,此字段为空。

2.2. Tenant 实体

tenant 服务中的 Tenant 表用于存储租户的详细信息(如租户名称、版本等)。

  • 该表通过 user_id 字段与 user 表中的 tenant 角色的用户进行逻辑关联。服务之间的数据一致性在应用层通过 gRPC 调用保证。

2.3. 租户关系图

  • User 2 是一个租户的所有者。
  • tenant 服务中会有一条记录,其 user_id 为 2,代表这个租户的实体。
  • User 3User 4 是该租户下的成员,他们的 belongs_to_user_id 都指向 User 2。

3. 认证流程 (Authentication)

认证流程由 auth 服务和 API 网关共同完成。

3.1. 认证端点

auth 服务通过 gRPC 暴露了一系列认证相关的接口,定义于 services/auth/api/auth_auth.proto。主要包括:

  • Register: 用户注册(支持用户名/密码、短信等)。
  • Login: 用户登录(支持用户名/密码、短信、微信)。
  • Logout: 用户登出。
  • ValidateAndRefreshToken: 验证并刷新 Token。
  • ExchangeTenantToken: 切换租户上下文。允许用户获取一个包含特定租户信息的新 Token,这是实现多租户访问控制的关键。

3.2. Token 生成与传递

  1. 用户通过 API 网关调用登录接口。
  2. 网关将请求路由到 auth 服务。
  3. auth 服务验证用户凭据,生成 JWT Token 并返回给用户。
  4. 该 JWT Token 中包含了用户的 ID、角色以及当前激活的租户 ID。
  5. 用户在后续请求的 Authorization Header 中携带此 Token。
  6. API 网关接收到请求,验证 JWT Token 的有效性
  7. 验证通过后,网关从 Token 中解析出 user-id, user-roles, tenant-id 等信息。
  8. 网关将这些信息作为 gRPC 的元数据(x-user-id, x-user-roles)附加到请求上,然后将请求转发给下游的业务微服务(如 user-service, tenant-service 等)。

4. 授权机制 (Authorization)

授权是在各个微服务内部,基于网关传递过来的用户信息实现的。

4.1. gRPC 认证拦截器

所有需要认证的 gRPC 服务都使用了一个统一的认证拦截器 UnaryAuthInterceptor,定义在 pkg/middleware/auth.go

  • 职责
    1. 检查公共接口isPublicGRPCMethod 函数定义了一份白名单,对于名单内的接口(如登录、注册),拦截器直接放行。
    2. 解析元数据:对于受保护的接口,拦截器从 gRPC 请求的元数据中读取 x-user-idx-user-roles
    3. 注入上下文:将用户ID和角色信息注入到当前请求的 context.Context 中。
    4. 服务间认证 (S2S):拦截器也能处理服务间的内部调用请求。
    5. 如果认证信息不存在,则返回 Unauthenticated 错误。

4.2. 业务逻辑中的权限检查

在具体的业务逻辑代码中,可以通过 pkg/middleware/auth.go 提供的辅助函数来实施访问控制。

  • GetUserID(ctx): 从上下文中获取当前用户的ID。
  • GetUserRole(ctx): 从上下文中获取当前用户的角色。
  • RequireAdminRole(ctx): 一个示例性的检查函数,用于验证当前用户是否为 admin 角色。

业务代码可以根据获取到的用户角色和ID,结合自身的业务逻辑,实现精细化的权限控制。例如,在编辑某个资源时,检查当前用户是否为 admin 或者是否为该资源所属租户的 tenant

5. 总结

井云的用户体系是一个高度解耦、可扩展的系统。它通过统一的用户模型和巧妙的层级关系设计实现了复杂的多租户结构,并通过集中的网关认证和分布式的业务授权,在安全性和灵活性之间取得了良好的平衡。