iam 下一步工作与上下游依赖(Next Steps v2)
模块:iam(身份与访问管理服务,端口 3002 HTTP + 50052 gRPC)
更新日期:2026-07-14
关联文档:
1. v2 迭代已完成工作
1.1 admin-portal §2.3 P1 阻塞项补齐(用户管理 RPC)
| # |
工作项 |
状态 |
| 1 |
iam.proto 新增 CreateUserRequest / UpdateUserRequest / DeleteUserRequest / DeleteUserResponse 4 个 message |
✅ |
| 2 |
iam.proto service IamService 声明 CreateUser / UpdateUser / DeleteUser 3 个 RPC |
✅ |
| 3 |
iam.repository.ts 实现 createUser / updateUser / deleteUser 数据访问 |
✅ |
| 4 |
iam.service.ts 实现 createUser / updateUser / deleteUser 业务逻辑(含 bcrypt + 审计) |
✅ |
| 5 |
iam.grpc.controller.ts 新增 3 个 @GrpcMethod 实现 |
✅ |
| 6 |
iam.controller.ts 新增 POST /v1/iam/users + DELETE /v1/iam/users/:id REST 端点(@RequirePermission(IAM_USER_MANAGE)) |
✅ |
| 7 |
iam.dto.ts 新增 createUserSchema Zod 校验 |
✅ |
1.2 F12 httpOnly Cookie 模式(refresh_token 安全增强)
依据架构裁决 F12:refresh_token 不再通过响应体明文返回,改为 httpOnly + Secure + SameSite=Strict Cookie 下发。
| # |
工作项 |
状态 |
| 1 |
iam.controller.ts 新增 extractRefreshToken(req):优先读 cookie,回退 body |
✅ |
| 2 |
iam.controller.ts 新增 buildRefreshTokenCookie(token, maxAge) 构造 Set-Cookie |
✅ |
| 3 |
iam.controller.ts 新增 buildClearCookie() 构造清除 Cookie |
✅ |
| 4 |
POST /v1/iam/register 下发 Set-Cookie |
✅ |
| 5 |
POST /v1/iam/login 下发 Set-Cookie |
✅ |
| 6 |
POST /v1/iam/refresh 优先从 cookie 读取 + 轮换 Cookie |
✅ |
| 7 |
POST /v1/iam/logout 清除 Cookie(Max-Age=0) |
✅ |
Cookie 属性:HttpOnly; SameSite=Strict; Max-Age=604800; Path=/; Secure
1.3 Docker 构建修复(bcrypt 原生模块)
| # |
工作项 |
状态 |
| 1 |
Dockerfile 改用 pnpm install --no-frozen-lockfile --filter @edu/iam-service... 过滤安装策略 |
✅ |
| 2 |
不再使用 --ignore-scripts,让 bcrypt install script 正常执行原生编译 |
✅ |
| 3 |
仅安装 iam + workspace 依赖,避免 arch-scan 的 better-sqlite3 原生编译 |
✅ |
| 4 |
镜像 edu/iam:test 构建成功(660MB),bcrypt .node 文件存在 |
✅ |
1.4 本地 Docker 端到端验证(2026-07-14)
测试环境:edu-iam-test 容器(NODE_ENV=production, DEV_MODE=true),接入 edu-full_default 网络,直连 edu-mysql / edu-redis / edu-kafka。
JWT RS256 密钥对挂载在 edu-iam-keys volume(/app/keys/private.pem + /app/keys/public.pem)。
| 验证项 |
状态 |
说明 |
GET /healthz |
✅ |
200 {"status":"ok","service":"iam"} |
GET /readyz |
✅ |
200,5 项依赖全 ok(database / redis / kafka / jwks / grpc) |
GET /v1/iam/.well-known/jwks.json |
✅ |
200,返回 RSA 公钥(kid: iam-rs256-v1, alg: RS256, kty: RSA) |
POST /v1/iam/register |
✅ |
201 Created + Set-Cookie: refresh_token=...; HttpOnly; SameSite=Strict; Max-Age=604800; Path=/; Secure |
POST /v1/iam/login |
✅ |
201 Created + Set-Cookie 下发 |
POST /v1/iam/refresh(cookie 优先) |
✅ |
201 Created + 轮换 Set-Cookie(新 jti) |
POST /v1/iam/logout |
✅ |
201 Created + Set-Cookie: refresh_token=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/; Secure(清除) |
POST /v1/iam/users(createUser) |
✅ |
201 Created + 返回新用户(含 dataScope) |
DELETE /v1/iam/users/:id(deleteUser) |
✅ |
200 OK + {"success":true,"data":{"success":true}} |
2. 上游依赖(iam 依赖谁)
iam 作为身份基础设施服务,运行时依赖以下组件(与 v1 一致,无新增):
2.1 基础设施
| 组件 |
端点 |
用途 |
状态 |
| MySQL |
mysql://edu:changeme@edu-mysql:3306/next_edu_cloud |
用户/角色/权限/视口/审计/Outbox 表持久化 |
✅ |
| Redis |
redis://edu-redis:6379 |
权限缓存(TTL 5min)+ token 黑名单 |
✅ |
| Kafka |
kafka:29092 |
Outbox 事件投递 |
✅ |
| JWT RS256 密钥 |
/app/keys/private.pem + /app/keys/public.pem(edu-iam-keys volume) |
RS256 签发 + JWKS 暴露公钥 |
✅ |
2.2 共享包(协调 AI 维护)
| 包 |
依赖内容 |
状态 |
| shared-proto |
packages/shared-proto/proto/iam.proto(15 RPC + 全部 message) |
✅ |
| shared-ts |
@edu/shared-ts/outbox OutboxModule + OutboxService |
✅ |
3. 下游依赖(谁依赖 iam)
3.1 api-gateway(ai01 负责)— P0
| # |
依赖项 |
用途 |
状态 |
| 1 |
GET /v1/iam/.well-known/jwks.json |
RS256 公钥集(JWKS),TTL 5min 缓存 |
✅ 已就绪 |
| 2 |
RS256 JWT 签发 |
api-gateway 用 JWKS 公钥校验 access_token |
✅ 已就绪 |
| 3 |
GET /healthz 端点 |
/readyz 下游健康检查 |
✅ 已就绪 |
| 4 |
JWT claims 含 role/data_scope |
api-gateway 注入 x-user-roles / x-user-data-scope 头 |
✅ 已就绪 |
3.2 push-gateway(ai09 负责)— P0
| # |
依赖项 |
用途 |
状态 |
| 1 |
GET /v1/iam/.well-known/jwks.json |
WebSocket /ws 连接时校验客户端 JWT 签名 |
✅ 已就绪 |
| 2 |
shared-go/jwks Fetcher 每 5 分钟刷新 |
JWKS 缓存刷新 |
✅ 已就绪 |
3.3 teacher-bff(ai03 负责)— P0
| # |
依赖项 |
用途 |
状态 |
| 1 |
gRPC GetUserInfo(userId) :50052 |
currentUser/me 查询 |
✅ 已就绪 |
| 2 |
gRPC BatchGetUsers(userIds) :50052 |
adminUsers 查询 |
✅ 已就绪 |
| 3 |
gRPC GetEffectivePermissions(userId) :50052 |
用户有效权限列表 |
✅ 已就绪 |
| 4 |
gRPC GetEffectiveDataScope(userId) :50052 |
用户数据范围 |
✅ 已就绪 |
| 5 |
gRPC GetViewports(userId) :50052 |
adminViewports 查询 |
✅ 已就绪 |
| 6 |
gRPC GetPublicKey() :50052 |
RS256 公钥 |
✅ 已就绪 |
| 7 |
gRPC GetChildrenByParent(parentId) :50052 |
家长端学生列表 |
✅ 已就绪 |
| 8 |
gRPC CreateUser / UpdateUser / DeleteUser |
用户管理 CRUD(admin-portal) |
✅ 已就绪 |
| 9 |
REST 全套 RBAC CRUD(roles/permissions/viewports/audit) |
admin-portal 管理 |
✅ 已就绪 |
3.4 student-bff(ai04 负责)— P0
| # |
gRPC 方法 |
用途 |
状态 |
| 1 |
GetUserProfile |
myProfile Query |
✅ 已就绪 |
| 2 |
UpdateProfile |
updateProfile Mutation |
✅ 已就绪 |
| 3 |
ChangePassword |
changePassword Mutation |
✅ 已就绪 |
3.5 parent-bff(ai05 负责)— P0
| # |
RPC 方法 |
用途 |
状态 |
| 1 |
getUserInfo(userId) |
获取家长个人信息 |
✅ 已就绪 |
| 2 |
getChildrenByParent(parentId) |
获取家长绑定的孩子列表 |
✅ 已就绪 |
| 3 |
getViewports(userId) |
获取家长可见视口 |
✅ 已就绪 |
| 4 |
getEffectivePermissions(userId) |
获取家长有效权限 |
✅ 已就绪 |
| 5 |
GET /healthz 端点 |
/readyz 下游健康检查 |
✅ 已就绪 |
3.6 admin-portal(ai16 负责)— P0
| # |
依赖项 |
用途 |
状态 |
| 1 |
gRPC CreateUser / UpdateUser / DeleteUser |
用户管理 CRUD(v2 新增) |
✅ 已就绪 |
| 2 |
REST POST /v1/iam/users + DELETE /v1/iam/users/:id |
用户管理 REST 端点(v2 新增) |
✅ 已就绪 |
| 3 |
GET /v1/iam/users |
用户管理列表(分页 + 搜索) |
✅ 已就绪 |
| 4 |
PATCH /v1/iam/users/:id |
用户更新 |
✅ 已就绪 |
| 5 |
PATCH /v1/iam/users/:id/status |
用户状态切换 |
✅ 已就绪 |
| 6 |
POST/PATCH/DELETE /v1/iam/roles |
角色 CRUD |
✅ 已就绪 |
| 7 |
POST/PATCH/DELETE /v1/iam/permissions |
权限 CRUD |
✅ 已就绪 |
| 8 |
POST/PATCH/DELETE /v1/iam/viewports |
视口 CRUD |
✅ 已就绪 |
| 9 |
POST /v1/iam/totp/enable / verify / disable |
TOTP 2FA 管理 |
✅ 已就绪 |
| 10 |
GET /v1/iam/audit |
审计日志查询 |
✅ 已就绪 |
3.7 teacher-portal(ai13 负责)— P1
| # |
依赖项 |
用途 |
状态 |
| 1 |
JWT RS256 签发 |
登录后获取 access_token + refresh_token |
✅ 已就绪 |
| 2 |
F12 httpOnly Cookie 模式 |
refresh_token 通过 Set-Cookie 下发(v2 新增) |
✅ 已就绪 |
| 3 |
POST /v1/iam/login |
教师登录 |
✅ 已就绪 |
| 4 |
POST /v1/iam/refresh |
刷新 token(从 cookie 读取) |
✅ 已就绪 |
| 5 |
POST /v1/iam/logout |
登出(清除 cookie) |
✅ 已就绪 |
| 6 |
GET /v1/iam/me |
获取当前用户信息 |
✅ 已就绪 |
| 7 |
GET /v1/iam/viewports |
获取视口配置 |
✅ 已就绪 |
| 8 |
GET /v1/iam/permissions/effective |
获取有效权限 |
✅ 已就绪 |
3.8 student-portal(ai14 负责)— P1
| # |
依赖项 |
用途 |
状态 |
| 1 |
JWT RS256 签发 |
学生登录 |
✅ 已就绪 |
| 2 |
F12 httpOnly Cookie 模式 |
refresh_token 通过 Set-Cookie 下发(v2 新增) |
✅ 已就绪 |
| 3 |
POST /v1/iam/login |
学生登录 |
✅ 已就绪 |
| 4 |
PATCH /v1/iam/me |
更新个人资料 |
✅ 已就绪 |
| 5 |
POST /v1/iam/change-password |
修改密码 |
✅ 已就绪 |
3.9 parent-portal(ai15 负责)— P1
| # |
依赖项 |
用途 |
状态 |
| 1 |
JWT RS256 签发 |
家长登录 |
✅ 已就绪 |
| 2 |
F12 httpOnly Cookie 模式 |
refresh_token 通过 Set-Cookie 下发(v2 新增) |
✅ 已就绪 |
| 3 |
POST /v1/iam/login |
家长登录 |
✅ 已就绪 |
| 4 |
GET /v1/iam/children |
获取绑定的学生列表 |
✅ 已就绪 |
| 5 |
GET /v1/iam/me |
获取家长个人信息 |
✅ 已就绪 |
3.10 同级服务
| # |
模块 |
依赖项 |
用途 |
状态 |
| 1 |
ai |
gRPC GetEffectiveDataScope(userId) |
解析用户可见数据范围(DataScope 6 级) |
✅ 已就绪 |
| 2 |
core-edu |
gRPC BatchGetUsers / GetUserInfo |
DashboardService 学生计数(P4+ 未来工作) |
✅ 已就绪 |
| 3 |
msg |
Kafka 事件 edu.iam.user.events |
用户注册/登录/角色变更事件(msg 消费方需对齐) |
✅ 已就绪 |
| 4 |
data-ana |
gRPC GetEffectiveDataScope(userId) |
数据分析查询过滤范围 |
✅ 已就绪 |
| 5 |
content |
无直接依赖 |
— |
— |
4. 需要上下游实现的协调事项
4.1 上游(Gateway / BFF / Portal)需实现
| # |
协调项 |
对端模块 |
说明 |
| 1 |
F12 Cookie 透传:Cookie / Set-Cookie 头透传 |
api-gateway / teacher-bff / student-bff / parent-bff |
F12 httpOnly cookie 模式要求 Gateway 和 BFF 透传 Cookie 请求头和 Set-Cookie 响应头。Gateway 当前已透传 x-user-* 头,需确认 Cookie 头也在透传白名单中。BFF GraphQL 网关需在 fetch 调用 iam 时带上客户端的 Cookie 头。 |
| 2 |
Cookie Domain 配置 |
api-gateway |
生产环境跨子域共享 cookie 时,需在 iam 环境变量 REFRESH_TOKEN_COOKIE_DOMAIN 配置根域(如 .edu.example.com)。当前默认不设置 Domain。 |
| 3 |
HTTPS 终止点确认 |
api-gateway / push-gateway |
Cookie 带 Secure 属性,浏览器仅在 HTTPS 下发送。开发环境(HTTP)下浏览器不会自动回传 cookie,需用 curl/Postman 手动传递。生产环境 api-gateway 必须做 HTTPS 终止。 |
| 4 |
admin-portal 切换到 CreateUser/DeleteUser RPC |
admin-portal / teacher-bff |
admin-portal 当前可能使用 PATCH /v1/iam/users/:id 更新用户。新增用户需切换到 gRPC CreateUser(通过 teacher-bff 聚合)或 REST POST /v1/iam/users(通过 gateway 透传)。删除用户使用 DELETE /v1/iam/users/:id。 |
| 5 |
Kafka 消费者对齐 edu.iam.user.events topic |
msg / data-ana |
iam 发布事件到 edu.iam.user.events topic,事件类型:UserRegistered / UserLoggedIn / UserLoggedOut / UserRoleChanged / UserCreated / UserDeleted。msg 服务的 topic 命名期望为 edu.identity.user.*,需协调统一命名。 |
| 6 |
Kafka 消费者对齐 edu.iam.role.events topic |
msg / data-ana |
事件类型:RoleCreated / RoleUpdated / RolePermissionsChanged。下游消费方需订阅此 topic 同步权限缓存。 |
| 7 |
Kafka 消费者对齐 edu.iam.audit.created topic |
data-ana |
审计日志事件,data-ana 可消费后入 ClickHouse 供长期分析。 |
| 8 |
api-gateway JWKS 缓存刷新策略 |
api-gateway |
iam JWKS 端点返回 kid: iam-rs256-v1,公钥轮换时 kid 会变。api-gateway 需在 JWT 验签失败时触发 JWKS 重新拉取(cache miss 回源)。 |
4.2 下游(同级服务)需实现
| # |
协调项 |
对端模块 |
说明 |
| 1 |
msg 服务消费 edu.iam.user.events 创建用户消息视图 |
msg |
iam 发布 UserRegistered / UserCreated 事件后,msg 服务需消费并为新用户初始化消息收件箱 / 通知偏好。事件 payload 见 shared/outbox/event-types.ts |
| 2 |
data-ana 消费 edu.iam.audit.created 入 ClickHouse |
data-ana |
iam 审计日志通过 Outbox 投递到 Kafka,data-ana 需消费并写入 ClickHouse audit_logs 表供长期留存与查询。 |
| 3 |
core-edu DashboardService 调用 iam gRPC BatchGetUsers |
core-edu |
P4+ 工作:core-edu 仪表盘需展示学生/教师计数,通过 iam gRPC BatchGetUsers 批量查询用户信息。gRPC target: iam:50052。 |
| 4 |
ai 服务调用 iam gRPC GetEffectiveDataScope |
ai |
ai 服务在生成个性化内容时需解析用户数据范围,调用 GetEffectiveDataScope(userId) 返回 {level, scope_ids, school_id}。gRPC target: iam:50052。 |
4.3 事件契约(Kafka topic 与 payload)
iam 通过 Outbox 模式发布以下事件,下游消费方需按此契约实现 consumer:
| Topic |
事件类型 |
触发时机 |
关键字段 |
edu.iam.user.events |
UserRegistered |
POST /v1/iam/register |
userId, email, name, timestamp |
edu.iam.user.events |
UserLoggedIn |
POST /v1/iam/login |
userId, email, ip, userAgent, timestamp |
edu.iam.user.events |
UserLoggedOut |
POST /v1/iam/logout |
userId, timestamp |
edu.iam.user.events |
UserCreated |
POST /v1/iam/users |
userId, email, name, dataScope, operatorId, timestamp |
edu.iam.user.events |
UserUpdated |
PATCH /v1/iam/users/:id |
userId, changedFields, operatorId, timestamp |
edu.iam.user.events |
UserDeleted |
DELETE /v1/iam/users/:id |
userId, operatorId, timestamp |
edu.iam.user.events |
UserRoleChanged |
角色授权/撤销 |
userId, roleId, permissionId, action, operatorId, timestamp |
edu.iam.role.events |
RoleCreated |
POST /v1/iam/roles |
roleId, name, level, operatorId, timestamp |
edu.iam.role.events |
RoleUpdated |
PATCH /v1/iam/roles/:id |
roleId, changedFields, operatorId, timestamp |
edu.iam.role.events |
RolePermissionsChanged |
PATCH roles/:id/permissions |
roleId, permissionIds, operatorId, timestamp |
edu.iam.audit.created |
AuditLogCreated |
所有审计操作 |
auditId, userId, action, resource, ip, userAgent, timestamp |
事件 envelope 格式遵循 @edu/shared-ts/outbox:{event_id, event_type, aggregate_id, payload, occurred_at, version}。
幂等性:消费方需基于 event_id 去重(Redis SETNX 或 DB 唯一索引)。
5. iam 自身待办(非阻塞)
| # |
工作项 |
优先级 |
阻塞条件 |
| 1 |
单元测试 + 集成测试覆盖率 ≥ 80% |
P2 |
无 |
| 2 |
生产环境 JWT 密钥轮换流程(kid 变更 + JWKS 缓存失效广播) |
P3 |
生产部署前 |
| 3 |
Kafka topic 创建自动化(edu.iam.user.events / role.events / audit.created) |
P3 |
K8s 部署阶段 |
| 4 |
数据库迁移脚本(drizzle-kit) |
P3 |
生产部署前 |
| 5 |
UpdateUser REST 端点补齐(当前仅有 PATCH /v1/iam/users/:id,无 POST /v1/iam/users/:id 通用更新) |
P3 |
无 |
| 6 |
Cookie Domain 环境变量配置化(REFRESH_TOKEN_COOKIE_DOMAIN) |
P3 |
生产部署前 |
6. 联调待办
| # |
联调项 |
对端模块 |
状态 |
| 1 |
api-gateway JWKS 真实验签 |
api-gateway (ai01) |
✅ 已就绪 |
| 2 |
push-gateway JWKS 真实验签 |
push-gateway (ai09) |
✅ 已就绪 |
| 3 |
teacher-bff gRPC 全量调用(含 CreateUser/DeleteUser) |
teacher-bff (ai03) |
⏳ 待联调 |
| 4 |
student-bff GetUserProfile/UpdateProfile/ChangePassword |
student-bff (ai04) |
✅ 已就绪 |
| 5 |
parent-bff getUserInfo/getChildrenByParent/getViewports |
parent-bff (ai05) |
✅ 已就绪 |
| 6 |
admin-portal RBAC CRUD + 用户管理 RPC 全量 |
admin-portal (ai16) |
⏳ 待联调 |
| 7 |
teacher-portal F12 cookie 模式联调 |
teacher-portal (ai13) |
⏳ 待联调 |
| 8 |
student-portal F12 cookie 模式联调 |
student-portal (ai14) |
⏳ 待联调 |
| 9 |
parent-portal F12 cookie 模式联调 |
parent-portal (ai15) |
⏳ 待联调 |
| 10 |
msg 消费 edu.iam.user.events 事件 |
msg |
⏳ 待联调 |
| 11 |
data-ana 消费 edu.iam.audit.created 事件 |
data-ana |
⏳ 待联调 |
iam 服务 v2 迭代完成。所有 v2 阻塞项(admin-portal 用户管理 RPC + F12 cookie 模式 + Docker bcrypt 修复)已就绪,下游模块可开始 v2 联调。