跳转至

eID —— 管理控制台(运维面板)

面向运维人员与开发者的指南:eID 平台的管理控制台由 Next.js 运维面板 (admin/,端口 3001)与 Go 管理 API(/v3/admin/*)组合而成。运维人员通过它 管理公民、设备、证书、会话、KYC/DAN、组织(法人)、依赖方(RP)、审计日志与系统状态。

独立应用。 管理控制台是与 web/(面向公民的 RP 演示,端口 3000) 完全独立的 Next.js 应用。代码目录:admin/src/

1. 管理范围

模块 能力
公民(/users 检索(etsi / РД / 姓名 / documentNumber)、详情 + 设备 + 证书 + 审计
设备(/devices 列表 / 检索、详情、停用遗失设备
证书(/certificates 列表 / 检索、状态、吊销
PKI(/pki/org-ca CA 状态、OCSP/CRL 路径、e-Seal 的 QSCD 状态
组织(/organizations 法人注册、代表人、签发 e-Seal 证书
RP(/rps 注册 / 编辑 / 停用 / 重新启用依赖方、轮换 secret、管理子系统
审批(/approvals 四眼原则:敏感操作需另一位管理员确认
审计(/audit 所有操作的日志 + CSV 导出(eIDAS/ISO 27001)
管理员(/admins 创建 / 变更角色 / 停用管理员(仅 SUPER_ADMIN)
系统(/system 版本、运行时长,以及配置 / HSM / KYC / PKI 的非敏感状态

2. 架构 —— BFF(浏览器永远看不到 secret)

管理控制台采用 Backend-for-Frontend(BFF) 模式。浏览器绝不会接触到管理会话令牌 —— 令牌仅保存在 Next 服务端的 httpOnly cookie 中。

浏览器 ──fetch──▶ Next BFF(端口 3001)──Bearer──▶ Go 管理 API(/v3/admin/*)
  (cookie:             /api/login、/api/mfa、           adminAuth 中间件
   admin_session,      /api/proxy/[...path]             + requireCap(RBAC)
   httpOnly)
  • 客户端 → BFF。 页面调用 api("users?q=…") 时,请求会发往 /api/proxy/* 路由 (admin/src/lib/client.ts)。
  • BFF → Go。 代理路由从 cookie 中读取会话令牌,并以 Authorization: Bearer <token> 转发至 ${ADMIN_BACKEND_URL}/v3/admin/<path>admin/src/app/api/proxy/[...path]/route.ts)。 令牌不会暴露给浏览器,因此无法通过 XSS 窃取。
  • 防护措施。 路径穿越白名单([A-Za-z0-9._-],禁止 ./..)、 对写操作请求校验 Origin/Referer 主机与 host 是否一致(在 sameSite=strict 之外的 CSRF 纵深防御),以及将上游 5xx 错误统一替换为通用提示。
  • 认证守卫。 (app) layout 在服务端调用 getMe(),若不存在有效会话则重定向到 /loginadmin/src/app/(app)/layout.tsx)。
  • CSP / 安全响应头。 admin/src/middleware.ts —— nonce + strict-dynamicconnect-src 'self'frame-ancestors 'none'、HSTS 等。

信任边界。 Go 侧通过 adminAuth 中间件保护 /v3/admin/*server/internal/httpapi/server.go)。 优先级:(1) Authorization: Bearer <会话令牌> → 完整 RBAC;(2) X-Admin-Key → 应急(break-glass)SUPER_ADMIN(常数时间比较);(3) 仅在 dev 配置下, 当 SMARTID_ADMIN_API_KEY 为空时为开放的 SUPER_ADMIN。在 staging/prod 上 绝不允许 fail-open —— 必须提供 Bearer 或应急密钥。

3. 登录 + MFA

流程:邮箱/密码 → TOTP(MFA)→ 会话令牌。首次登录时,用户通过二维码绑定验证器应用。

步骤 BFF 路由 Go 接口 说明
1. 登录 POST /api/login POST /v3/admin/auth/login 校验邮箱/密码;成功后返回 mfa scope 的令牌
2. MFA POST /api/mfa POST /v3/admin/auth/mfa TOTP 验证码 → session scope 的令牌
3. 登出 POST /api/logout POST /v3/admin/auth/logout 删除 cookie(令牌本身为无状态)
  • 密码。 通过 internal/admin/password.go 哈希存储;错误提示统一为 “邮箱或密码不正确”,以防范时序攻击与账号枚举。
  • TOTP(MFA)。 RFC 6238、HMAC-SHA1、30 秒步长、6 位数字 —— 自行实现,未引入外部库 (server/internal/admin/totp.go)。 首次登录时服务端返回 otpauth:// URI 与密钥;登录页将其渲染为二维码, 供用户添加到 Google/Microsoft Authenticator。验证码允许 ±1 步长的窗口(时钟偏差), 并保存已使用的计数器以阻止重放(RFC 6238 §5.2)。
  • 令牌。 使用 HMAC-SHA256 签名的无状态令牌(未使用 JWT 库), 格式为 <b64url(payload)>.<b64url(HMAC)>server/internal/admin/token.go)。 mfa scope 约 5 分钟,session scope 约 1 小时。
  • Cookie。 admin_mfa(5 分钟,等待 MFA)与 admin_session(1 小时)—— 均为 httpOnlysameSite=strict,在生产环境还带 secure
  • 限流。 登录(按 IP+邮箱)与 MFA(按 IP)均有节流;超出后返回 429 + Retry-After
  • 修改密码。 POST /v3/admin/auth/password —— 已登录管理员修改自己的密码 (需确认当前密码)。无需额外 capability。

4. RBAC —— 角色与 capability

每个路由都声明所需的 capabilityrequireCap 中间件校验当前登录管理员的角色是否具备该 capability(最小权限原则)。权限矩阵见: server/internal/admin/roles.go

Capabilities: rp:readrp:writeorg:readorg:writeuser:readdevice:readdevice:revokecert:readcert:revokesession:readaudit:readadmin:manageconfig:readconfig:write

角色 → capability:

角色 授予的 capability
SUPER_ADMIN 全部 capability(含管理员管理)
RP_OPERATOR rp:read rp:write org:read org:write user:read session:read audit:read
SUPPORT rp:read org:read user:read device:read device:revoke cert:read session:read audit:read
AUDITOR rp:read org:read user:read device:read cert:read session:read audit:read config:read
SECURITY_OFFICER org:read user:read device:read device:revoke cert:read cert:revoke session:read audit:read config:read

若 capability 不足,Go 侧返回 403(“该操作权限不足”)。前端导航会显示全部页面, 但未授权的操作会在后端被 403 拦截。

5. 核心能力与 /v3/admin/* 接口

全部位于 adminAuth 之后。所需 capability 一并列出。

公民 / 设备 / 证书 / 会话(读取)

方法 + 路径 Cap 说明
GET /users?q=&limit=&offset= user:read 检索公民(etsi/РД/姓名);若无结果,则按 documentNumber 查找所有者
GET /users/{etsi} user:read 公民 + 设备 + 证书 + 审计
GET /devices?q=&active= device:read 检索设备
GET /devices/{documentNumber} device:read 设备详情
GET /certificates?q=&status= cert:read 检索证书
GET /users/{etsi}/certificates cert:read 某公民的证书历史
GET /sessions/{sessionId} session:read 会话的安全视图

绝不返回敏感材料。 视图函数(userView/deviceView/certView/sessionViewhandlers_admin_read.go) 会剔除 HSM 句柄、Enc(x_client)、Paillier 模数、会话 secret/token 以及体积庞大的 证书 base64,仅展示安全字段。

RP(依赖方)

方法 + 路径 Cap 说明
POST /relying-parties rp:write 注册 RP —— API secret 仅在此处返回一次
GET /relying-partiesGET /relying-parties/{id} rp:read 列表 / 详情
PATCH /relying-parties/{id} rp:write 编辑
POST /relying-parties/{id}/deactivate | /reactivate rp:write 停用 / 重新启用
POST /relying-parties/{id}/rotate-secret rp:write 新 secret(同样仅此一次)

RP 子系统。 子系统由 RP 自身自动注册(find-or-create);管理员只能 列出 / 重命名 / 停用 / 合并:

方法 + 路径 Cap 说明
GET /relying-parties/{id}/subsystems rp:read RP 内的子系统列表
PATCH /relying-parties/{id}/subsystems/{sid} rp:write 修改子系统的显示名称
POST /relying-parties/{id}/subsystems/{sid}/deactivate | /activate rp:write 停用 / 启用
POST /relying-parties/{id}/subsystems/{sid}/merge rp:write 合并到另一个子系统

完整的 RP 集成指南:RP_INTEGRATION.md。 子系统模型 / 协议:RP_SUBSYSTEMS.md

证书吊销 + PKI

方法 + 路径 Cap 说明
POST /certificates/{serial}/revoke cert:revoke 按序列号吊销(OCSP/CRL + 状态 + 停用设备);{reason} 取值 0–10
POST /devices/{documentNumber}/deactivate device:revoke 停用遗失设备(默认 reason=1 keyCompromise)
GET /pki/status config:read CA 的 subject/序列号/有效期、吊销数量、OCSP/CRL URL、Org CA、e-Seal QSCD

KYC / DAN

KYC 流程主要经由手机应用与 DAN 回调完成(/v3/kyc/*,不在管理控制台之下 —— server.gomountKyc)。 GET /v3/kyc/methods 返回已启用的 KYC 方式(dan / gsign / passport / citizenCard)。 系统接口 GET /v3/admin/system 中的 kyc 区块展示 KYC 提供方的非敏感状态。

组织(法人)

方法 + 路径 Cap 说明
GET /organizations?q= org:read 列表 / 检索
GET /organizations/{etsi} org:read 详情 + 代表人
POST /organizations org:write 注册(PENDING 或直接 ACTIVE)
PATCH /organizations/{etsi} org:write 名称 / 状态(状态机)
POST /organizations/{etsi}/representatives org:write 添加代表人
DELETE /organizations/{etsi}/representatives/{id} org:write 停用某项代表权
POST /organizations/{etsi}/seal-certificate org:write 签发 e-Seal(NTRMN)证书

组织接入:ORG_ONBOARDING.md

审计 + 统计

方法 + 路径 Cap 说明
GET /audit?type=&subject=&limit=&offset= audit:read 审计日志(分页)
GET /audit/export?type=&subject= audit:read CSV 导出(eIDAS/ISO 27001 报告)
GET /stats audit:read 控制台指标与趋势

系统 / HSM 状态(只读)

方法 + 路径 Cap 说明
GET /system config:read 版本、运行时长,以及 security/kyc/pki/hsm/push 配置的非敏感状态

不暴露任何 secret。 adminSystemInfohandlers_admin_system.go) 绝不会暴露密钥 / 密码 / API 密钥 —— 只标明“是否已配置” (例如 swMasterKeyConfigured: true)。

管理员账户管理(仅 SUPER_ADMIN)

方法 + 路径 Cap 说明
GET /admins admin:manage 管理员列表(不含敏感信息的视图)
POST /admins admin:manage 新建管理员(邮箱 + 至少 8 位密码 + 角色)
PATCH /admins/{id} admin:manage 变更角色
POST /admins/{id}/deactivate admin:manage 停用(而非删除 —— 以保留审计记录)

四眼原则审批流程

SMARTID_ADMIN_REQUIRE_4EYES=true 时,敏感操作(吊销证书、停用设备、停用 RP) 不会立即执行 —— 而是转为 PENDING 请求(返回 202),需由另一位管理员确认。

方法 + 路径 Cap 说明
GET /approvals?status= audit:read 待处理请求
POST /approvals/{id}/approve (该操作对应的 capability,动态判定) 批准
POST /approvals/{id}/reject (该操作对应的 capability,动态判定) 驳回

审批人必须 (a) 拥有该操作对应的 capability(例如吊销证书 → cert:revoke), 且 (b) 与发起人不同 —— 试图批准自己的请求会被 ErrSelfApproval(403)阻止 (handlers_admin_approval.go)。

6. 运行

前端(管理控制台):

cd admin
npm run dev     # 端口 3001(next dev -p 3001)
npm run build
npm run lint
npm run test    # vitest

关键环境变量只有一个:ADMIN_BACKEND_URL(Go 后端地址,默认 http://localhost:8080)。

后端(Go 管理 API) —— 在 server/ 目录下运行(go run ./cmd/smartid)。 管理 / RBAC 相关环境变量(server/internal/config/config.go,均以 SMARTID_* 开头):

环境变量 说明
SMARTID_ADMIN_TOKEN_SECRET 会话令牌的 HMAC 密钥(≥16 个字符)。为空时管理端认证被禁用
SMARTID_ADMIN_SEED_EMAIL 首位 SUPER_ADMIN 的邮箱(启动时 seed,幂等)
SMARTID_ADMIN_SEED_PASSWORD 首位管理员的密码(仅用于 seed —— 之后必须修改)
SMARTID_ADMIN_SEED_ROLE seed 角色(默认 SUPER_ADMIN
SMARTID_ADMIN_API_KEY 应急 X-Admin-Key(绕过 RBAC 的 SUPER_ADMIN;生产环境可选)
SMARTID_ADMIN_REQUIRE_4EYES 敏感操作是否需要两名管理员审批(默认 false

dev 配置。dev 配置下,若 SMARTID_ADMIN_API_KEY 为空,/v3/admin/* 将变为开放的 SUPER_ADMIN(无需 Bearer)—— 仅供本地开发使用。staging/prod 绝不会出现这种情况;必须配置 SMARTID_ADMIN_TOKEN_SECRET + seed 或应急密钥。

7. 链接

源码: