# Phase 2: 身份与权限模块实施计划 ## Context Phase 1 基础设施已完成(workspace、core types、EventBus、ErpModule trait、AppState、health check、graceful shutdown)。现在需要构建 Phase 2 身份与权限模块,这是所有后续模块的基础——工作流、消息、配置都依赖认证和权限中间件。 **目标**:实现完整的用户认证(JWT)、RBAC 权限模型、多租户中间件、用户/角色/组织管理 CRUD,以及对应的前端页面。 **范围界定**:Phase 2 仅实现用户名/密码认证 + RBAC。OAuth/SSO/TOTP/ABAC 延后到后续 Phase。 --- ## 关键决策 1. **ErpModule trait 路由问题**:当前 trait `register_routes` 使用 `Router`(无状态泛型),但实际路由需要 `Router`。Phase 2 采用**务实方案**:模块暴露独立 `routes() -> Router` 函数,server 手动 merge。不改动 trait,避免核心层依赖 server 类型。 2. **Token 存储**:JWT 的 SHA-256 哈希存入 `user_tokens` 表,支持吊销。前端用 localStorage 存储 access/refresh token(httpOnly cookie 为 Phase 6 优化项)。 3. **中间件方案**:使用 `axum::middleware::from_fn_with_state` 实现 JWT 认证中间件,将 `TenantContext` 注入 `req.extensions()`。 --- ## Task 1: 数据库迁移(10 张表) **目标**:创建所有 Auth 相关的数据库表。 **创建文件**(`crates/erp-server/migration/src/`): - `m20260411_000002_create_users.rs` - `m20260411_000003_create_user_credentials.rs` - `m20260411_000004_create_user_tokens.rs` - `m20260411_000005_create_roles.rs` - `m20260411_000006_create_permissions.rs` - `m20260411_000007_create_role_permissions.rs` - `m20260411_000008_create_user_roles.rs` - `m20260411_000009_create_organizations.rs` - `m20260411_000010_create_departments.rs` - `m20260411_000011_create_positions.rs` **修改文件**:`migration/src/lib.rs` 注册所有新迁移 **表结构要点**: - 所有表含标准字段:id(UUID PK), tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version - **users**: username(VARCHAR, 复合唯一 tenant_id+username), email, phone, display_name, avatar_url, status(CHECK active/disabled/locked), last_login_at - **user_credentials**: user_id FK, credential_type(CHECK password/oauth/sso), credential_data(JSONB), verified(bool) - **user_tokens**: user_id FK, token_hash(VARCHAR UNIQUE), token_type(CHECK access/refresh), expires_at, revoked_at, device_info - **roles**: name, code(复合唯一 tenant_id+code), description, is_system(bool) - **permissions**: code(复合唯一 tenant_id+code), name, resource, action, description - **role_permissions**: role_id + permission_id 复合 PK - **user_roles**: user_id + role_id 复合 PK - **organizations**: name, code, parent_id(自引用 FK), path(TEXT), level, sort_order - **departments**: org_id FK, name, code, parent_id(自引用), manager_id FK users, path, sort_order - **positions**: dept_id FK, name, code, level, sort_order **验证**:`cargo run -p erp-server` → `\dt` 显示全部 11 张表 --- ## Task 2: SeaORM Entity 定义 **目标**:为所有 Auth 表创建类型安全的 Entity/Model/Relation。 **创建文件**(`crates/erp-auth/src/entity/`): - `mod.rs`, `user.rs`, `user_credential.rs`, `user_token.rs`, `role.rs`, `permission.rs`, `role_permission.rs`, `user_role.rs`, `organization.rs`, `department.rs`, `position.rs` **修改文件**: - `crates/erp-auth/Cargo.toml` — 添加 workspace 依赖:jsonwebtoken, argon2, validator, thiserror, utoipa, erp-common **同时创建 DTO 文件**:`crates/erp-auth/src/dto.rs` - `LoginReq` { username, password } — #[derive(Validate)] - `LoginResp` { access_token, refresh_token, expires_in, user } - `RefreshReq` { refresh_token } - `CreateUserReq` { username, password, email?, phone?, display_name? } - `UpdateUserReq` { email?, phone?, display_name?, status? } - `UserResp`, `RoleResp`, `PermissionResp` 等 - `CreateRoleReq`, `UpdateRoleReq`, `AssignPermissionsReq`, `AssignRolesReq` - 所有 DTO 添加 `#[derive(utoipa::ToSchema)]` **验证**:`cargo check -p erp-auth` 通过 --- ## Task 3: 核心服务层(密码 + JWT + 认证) **目标**:实现密码哈希、JWT 签发/验证、登录/刷新/登出逻辑。 **创建文件**(`crates/erp-auth/src/`): - `error.rs` — AuthError 枚举 + From for AppError - `service/mod.rs` - `service/password.rs` — Argon2 hash/verify - `service/token_service.rs` — JWT sign/validate, token DB CRUD - `service/auth_service.rs` — login/refresh/logout - `service/user_service.rs` — user CRUD **修改文件**:`crates/erp-auth/src/lib.rs` — 声明模块 **关键实现**: - `password.rs`: `hash_password(plain) -> Result`, `verify_password(plain, hash) -> Result` - `token_service.rs`: - JWT Claims: { sub: Uuid, tid: Uuid, roles: Vec, permissions: Vec, exp, iat, token_type: String } - `sign_access_token(user_id, tenant_id, roles, permissions, jwt_config) -> Result` - `sign_refresh_token(user_id, tenant_id, db, jwt_config) -> Result<(String, Uuid)>` — 存 SHA-256 哈希到 DB - `validate_refresh_token(token, db, jwt_config) -> Result<(Uuid, Claims)>` — 检查吊销状态 - `revoke_all_user_tokens(user_id, tenant_id, db)` - `auth_service.rs`: - `login(tenant_id, username, password, device_info, db, token_svc, event_bus)` → 查用户 → 验证密码 → 签发双 token → 更新 last_login_at → 发 user.login 事件 - `refresh(refresh_token, db, token_svc)` → 验证 → 吊销旧 refresh → 签发新双 token - `logout(user_id, tenant_id, db, token_svc)` → 吊销全部 token - `user_service.rs`: CRUD,查询始终带 tenant_id 过滤 + deleted_at IS NULL **验证**:`cargo check -p erp-auth` 通过,password hash/verify 单元测试通过 --- ## Task 4: Handler 路由 + AuthModule 注册 **目标**:创建 Axum handler,注册到 server。 **创建文件**(`crates/erp-auth/src/`): - `handler/mod.rs` - `handler/auth_handler.rs` — login/refresh/logout - `handler/user_handler.rs` — user CRUD + 角色分配 - `module.rs` — AuthModule struct + ErpModule impl **修改文件**: - `crates/erp-server/Cargo.toml` — 添加 `erp-auth.workspace = true` - `crates/erp-server/src/main.rs` — 注册 auth 模块路由 - `crates/erp-server/src/config.rs` — 添加 `AuthConfig { super_admin_password }` - `crates/erp-server/config/default.toml` — 添加 `[auth]` 段 **路由设计**: - 公开(无需 JWT):`POST /api/v1/auth/login`, `POST /api/v1/auth/refresh` - 受保护(需 JWT):`POST /api/v1/auth/logout`, 全部 users/roles/permissions/orgs 路由 **main.rs 路由结构**: ``` Router::new() .merge(public_routes) // health + login + refresh .merge(protected_routes) // logout + user/role CRUD, 后续加 JWT 中间件 .with_state(state) ``` **验证**:server 启动,`POST /api/v1/auth/login` 返回 400(无 body)而非 404 --- ## Task 5: JWT 认证中间件 + RBAC 权限检查 **目标**:JWT 验证中间件注入 TenantContext,RBAC 权限检查辅助函数。 **创建文件**(`crates/erp-auth/src/`): - `middleware/mod.rs` - `middleware/jwt_auth.rs` — `jwt_auth_middleware(State, Request, Next)` → 从 Bearer token 解码 Claims → 注入 TenantContext 到 extensions - `middleware/rbac.rs` — `require_permission(perm: &str, ctx: &TenantContext) -> AppResult<()>` **修改文件**: - `crates/erp-server/src/main.rs` — public/protected 路由分离,protected 路由层加 JWT 中间件 **关键实现**: - 中间件用 `axum::middleware::from_fn_with_state(state, jwt_auth_middleware)` - 从 `Authorization: Bearer xxx` 提取 token → decode → 检查过期 → 注入 `TenantContext { tenant_id, user_id, roles, permissions }` - RBAC: 简单检查 `ctx.permissions.contains(&required_permission)` - login/refresh/health 路由跳过 JWT 中间件 **验证**:无 token 访问 `/api/v1/users` 返回 401;login/refresh 不受影响 --- ## Task 6: 租户初始化钩子 + 种子数据 **目标**:实现 `on_tenant_created` 创建默认角色/权限/管理员。 **创建文件**:`crates/erp-auth/src/service/seed.rs` **修改文件**: - `crates/erp-auth/src/module.rs` — 实现 `on_tenant_created` - `crates/erp-server/src/main.rs` — server 启动时自动创建默认租户(开发用) **种子数据**: - 20 个默认权限(user:crud, role:crud, permission:read, organization:crud, department:crud, position:crud) - "admin" 角色(is_system=true)绑定所有权限 - "viewer" 角色(is_system=true)绑定只读权限 - super admin 用户(username="admin",密码从配置读取) **验证**:启动后查询 DB 有 admin 用户、admin/viewer 角色、20 个权限 --- ## Task 7: 前端登录页 + Auth Store + API 层 **目标**:登录 UI、token 管理、路由守卫。 **创建文件**(`apps/web/src/`): - `api/client.ts` — axios 实例 + 请求/响应拦截器(附加 token、401 自动 refresh) - `api/auth.ts` — login/refresh/logout API 调用 - `api/users.ts` — 用户 CRUD API - `stores/auth.ts` — auth Zustand store(user, permissions, tokens, login/logout) - `pages/Login.tsx` — 登录表单页 - `pages/Home.tsx` — 首页(从 App.tsx 抽取) **修改文件**: - `apps/web/src/App.tsx` — 添加 PrivateRoute 守卫、/login 路由 - `apps/web/src/stores/app.ts` — 移除 isLoggedIn stub(由 auth store 接管) **验证**:打开 `/#/` 自动跳转 `/login`,输入 admin/Admin@2026 登录后跳转到首页,刷新保持登录状态 --- ## Task 8: 角色/权限管理 **目标**:角色 CRUD + 权限分配后端 + 前端页面。 **创建文件**: - Backend: `crates/erp-auth/src/service/role_service.rs`, `service/permission_service.rs`, `handler/role_handler.rs`, `handler/permission_handler.rs` - Frontend: `apps/web/src/pages/Roles.tsx`, `apps/web/src/api/roles.ts` **修改文件**:`module.rs` 注册新路由, `App.tsx` 添加路由 **验证**:admin 登录 → 角色管理 → 创建角色 → 分配权限 → DB 验证 --- ## Task 9: 组织/部门/岗位管理 **目标**:树形组织架构管理。 **创建文件**: - Backend: `service/org_service.rs`, `service/dept_service.rs`, `service/position_service.rs`, `handler/org_handler.rs`, `handler/dept_handler.rs`, `handler/position_handler.rs` - Frontend: `apps/web/src/pages/Organizations.tsx`, `apps/web/src/api/orgs.ts` **关键**:树形结构用 parent_id + path 列实现祖先查询 **验证**:创建根组织 → 子组织 → 部门 → 岗位,验证 path 正确 --- ## Task 10: 用户管理页面 + 整合 **目标**:完整的用户 CRUD 界面 + 角色分配 + 主布局用户信息。 **创建文件**: - `apps/web/src/pages/Users.tsx` — Ant Design Table + 创建/编辑/角色分配 Modal - `apps/web/src/layouts/MainLayout.tsx` — 更新:显示当前用户名、登出菜单 **验证**:admin 登录 → 用户管理 → 创建用户 → 分配角色 → 禁用/启用 → 所有操作即时反映 --- ## 依赖关系 ``` Task 1 (迁移) → Task 2 (Entity) → Task 3 (服务层) → Task 4 (Handler+注册) ↓ Task 5 (JWT 中间件) Task 6 (种子数据) ↓ Task 7 (前端登录) ↓ Task 8 (角色权限) Task 9 (组织部门) ↓ Task 10 (用户管理 UI) ``` ## 验证标准 - [ ] `cargo check --workspace` 零错误零警告 - [ ] `cargo test --workspace` 全部通过 - [ ] server 启动后可 login → 获得 JWT → 带 JWT 访问 /api/v1/users - [ ] 无 JWT 访问受保护端点返回 401 - [ ] 前端登录 → 跳转首页 → 刷新保持 → 登出清除 - [ ] 用户/角色/组织 CRUD 页面功能完整 - [ ] 多租户隔离:每个查询自动带 tenant_id 过滤