# IAM Service 身份与访问管理服务(Identity and Access Management)。 > 版本:v0.2.0(对齐 coord-final-decisions I1-I8 + president §2.15/§2.16/§3.2/§5.5) > 详细设计见 [02-architecture-design.md](./docs/02-architecture-design.md) ## 职责 - 用户注册 / 登录 / 刷新令牌 / 登出 - JWT RS256 签发(access 15min + refresh 7day,jti 黑名单) - 角色(Role)与权限(Permission)管理(三层角色模型:system/organization/temporary) - DataScope 6 级数据范围(self/subject/class/grade/school/all) - 视口(Viewport)配置查询 - 学生-家长关系管理(`GetChildrenByParent`) - 审计日志(`iam_user_audit_log` + `AuditCreated` 事件) - JWKS 公钥暴露(供 api-gateway 验签) - 双入口:REST `/v1/iam/*` + gRPC 50052(package `next_edu_cloud.iam.v1`,12 RPC) ## 技术栈 - NestJS 10 + TypeScript(ESM,NodeNext) - Drizzle ORM + MySQL(10 张表) - bcrypt 密码哈希(cost ≥ 12) - jsonwebtoken RS256(本地文件密钥) - ioredis(权限缓存 TTL 5min + token jti 黑名单) - kafkajs + shared-ts OutboxModule(事务性事件发布) - @nestjs/microservices + @grpc/grpc-js(gRPC server) - pino 日志 / prom-client 指标 / OpenTelemetry 链路 ## 端口 | 服务 | 端口 | 备注 | | -------- | ----- | ------------------------ | | iam HTTP | 3002 | REST 入口(`PORT`) | | iam gRPC | 50052 | gRPC 入口(`GRPC_PORT`) | ## 环境变量 | 变量 | 说明 | 默认值 | | ----------------------------- | ------------------------------ | ---------------- | | `PORT` | HTTP 端口 | `3002` | | `GRPC_PORT` | gRPC 端口 | `50052` | | `DATABASE_URL` | MySQL 连接串 | — | | `REDIS_URL` | Redis 连接串 | — | | `IAM_PRIVATE_KEY_PATH` | RS256 私钥文件路径 | — | | `IAM_PUBLIC_KEY_PATH` | RS256 公钥文件路径 | — | | `JWT_ISSUER` | JWT 签发者 | `next-edu-cloud` | | `JWT_AUDIENCE` | JWT 受众 | `next-edu-cloud` | | `JWT_KEY_ID` | 密钥 ID(kid,用于 JWKS 轮换) | `iam-rs256-v1` | | `ACCESS_TOKEN_TTL` | access_token TTL | `15m` | | `REFRESH_TOKEN_TTL_DAYS` | refresh_token TTL(天) | `7` | | `KAFKA_BROKERS` | Kafka broker 列表(逗号分隔) | — | | `KAFKA_CLIENT_ID` | Kafka client ID | `iam-service` | | `OTEL_EXPORTER_OTLP_ENDPOINT` | OTel OTLP 端点(可选) | — | | `LOG_LEVEL` | 日志级别 | `info` | | `NODE_ENV` | 环境标识 | `development` | ## REST API | Method | Path | 权限 | 说明 | | ------ | ------------------------------- | ----------------- | ---------------------------------- | | POST | `/v1/iam/register` | 公开 | 注册 | | POST | `/v1/iam/login` | 公开 | 登录 | | POST | `/v1/iam/refresh` | 公开 | 刷新令牌(轮换 + 旧 jti 加黑名单) | | POST | `/v1/iam/logout` | `IAM_USER_READ` | 登出 | | GET | `/v1/iam/me` | `IAM_USER_READ` | 当前用户信息 | | GET | `/v1/iam/viewports` | `IAM_USER_READ` | 当前用户视口 | | GET | `/v1/iam/permissions/effective` | `IAM_USER_READ` | 当前用户有效权限 | | GET | `/v1/iam/children` | `IAM_USER_READ` | 家长的孩子列表 | | GET | `/v1/iam/roles` | `IAM_ROLE_MANAGE` | 角色列表 | | GET | `/v1/iam/permissions` | `IAM_ROLE_MANAGE` | 权限点列表 | | GET | `/v1/iam/audit` | `IAM_AUDIT_READ` | 审计日志 | | GET | `/v1/iam/.well-known/jwks.json` | 公开 | RS256 公钥 JWK Set | ## gRPC API 12 RPC,proto 定义见 `packages/shared-proto/proto/iam.proto`,package `next_edu_cloud.iam.v1`: `Register` / `Login` / `RefreshToken` / `Logout` / `GetUserInfo` / `BatchGetUsers` / `GetEffectivePermissions` / `GetEffectiveAccess` / `GetEffectiveDataScope` / `GetViewports` / `GetPublicKey` / `GetChildrenByParent` ## 健康检查 | 端点 | 用途 | 鉴权 | | -------------- | ------------------------------------------------------------------------- | ---- | | `GET /healthz` | 存活探针(liveness),仅返回进程状态 | 无 | | `GET /readyz` | 就绪探针(readiness),检查 DB/Redis/Kafka/JWKS/gRPC 5 依赖,失败返回 503 | 无 | ## 数据表(10 张) | 表名 | 用途 | | ----------------------- | --------------------------- | | `iam_users` | 用户主表 | | `iam_roles` | 角色表(role_type + level) | | `iam_user_roles` | 用户-角色绑定 | | `iam_permissions` | 权限点表 | | `iam_role_permissions` | 角色-权限映射 | | `iam_refresh_tokens` | refresh token(含 jti) | | `iam_role_viewports` | 角色-视口配置(含 level) | | `iam_student_guardians` | 学生-家长关系 | | `iam_user_audit_log` | 审计日志 | | `iam_password_history` | 密码重用限制 | DDL + 种子数据见 `scripts/iam-init.sql`。 ## Kafka 事件 | Topic | 事件 | | ----------------------- | -------------- | | `edu.iam.user.events` | `UserCreated` | | `edu.iam.role.events` | `RoleChanged` | | `edu.iam.audit.created` | `AuditCreated` | 通过 shared-ts `OutboxModule` 事务性发布(at-least-once + idempotent producer)。 ## 开发 ```bash # 安装依赖 pnpm install # 初始化数据库(需先启动 MySQL + Redis + Kafka) mysql -u root -p < scripts/iam-init.sql # 生成 RS256 密钥对 openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout -out public.pem # 启动开发服务 pnpm --filter @edu/iam-service dev # 质量校验 pnpm --filter @edu/iam-service lint pnpm --filter @edu/iam-service typecheck ```