docs: add aliyun realname integration notes

This commit is contained in:
richjohnrambo
2026-09-21 21:24:25 +08:00
parent b30cce950a
commit fc393cd685
2 changed files with 549 additions and 0 deletions
@@ -0,0 +1,536 @@
# IM 阿里云实名认证接入方案
## 1. 目标
本文记录当前 IM 实名认证能力的实现情况,以及后续接入阿里云实名认证的后端、前端和数据模型改造方案。接入目标是:用户在客户端提交实名信息后,由 IM Server 统一调用阿里云完成身份核验,客户端只感知认证状态和必要的认证跳转参数,不接触阿里云密钥、AccessKey、服务端回调密钥等敏感配置。
阿里云侧建议优先使用金融级实人认证/ID Verification(Cloudauth)能力。产品选型分两类:
1. **姓名 + 身份证二要素核验**:体验最轻,适合只要求姓名和证件号一致性的场景。结果可同步或准同步返回。
2. **增强版实人认证/活体核身**:服务端发起认证并获取 `CertifyId`,客户端通过阿里云 SDK、H5 或认证 URL 完成活体流程,服务端再查询结果或接收回调。适合强实名、风控和合规要求更高的场景。
当前产品原型只有姓名和证件号码输入页,但考虑后续安全要求,后端状态机需要兼容增强版实人认证。
## 2. 当前已有实现
### 2.1 前端对接接口
现有前后端文档已定义实名认证页面的三个核心入口:
| 能力 | 接口 | 当前状态 |
| --- | --- | --- |
| 查询实名状态 | `GET /v1/im/security/realname` | 已实现 |
| 提交实名信息 | `POST /v1/im/security/realname/submissions` | 已实现 |
| 实名更正申诉 | `POST /v1/im/security/realname/appeals` | 已实现 |
查询接口返回:
```json
{
"status": "unverified",
"can_submit": true
}
```
已提交或已通过时返回 `submission_id`、`masked_name`、`masked_id_number`、`verified_at`、`review_reason` 等字段。
提交接口要求:
- 必须携带 `Authorization: Bearer <token>`。
- 必须携带 `Idempotency-Key`,服务端根据用户、租户和幂等键生成 `submission_id`。
- Body 目前为:
```json
{
"name": "张三",
"id_type": "cn_id",
"id_number": "110101199001011234"
}
```
校验规则:
- `name` 长度 2-32 个字符。
- `id_type` 当前只支持 `cn_id`。
- `id_number` 当前只支持 18 位中国大陆身份证号,末位允许 `X`。
### 2.2 服务端实现
当前代码主要在以下位置:
| 模块 | 文件 | 说明 |
| --- | --- | --- |
| HTTP 路由 | `server/main.go` | 注册 `/v1/im/security/realname`、`/realname/submissions`、`/realname/appeals` |
| HTTP Handler | `server/im_http.go` | 查询、提交、申诉逻辑 |
| Store 接口 | `server/store/store.go` | `GetRealName`、`CreateRealNameSubmission`、`CreateRealNameAppeal` |
| MySQL 实现 | `server/db/mysql/tenant_adapter.go` | 租户库读写实名表 |
| 类型定义 | `server/store/types/security.go` | `RealNameVerification`、`RealNameAppeal` |
| 内部后台查询 | `server/internal_admin_query_resources.go` | `realname-verifications` 查询资源 |
当前实名提交成功后只落库为 `pending`,不会调用第三方,也没有自动审核通过。重复提交时,如果最新状态是 `pending` 或 `verified`,服务端会直接返回当前状态,避免重复创建申请。
### 2.3 当前数据表
租户库已有 `im_realname_verifications`:
| 字段 | 说明 |
| --- | --- |
| `id` | 自增主键 |
| `user_id` | 用户 ID |
| `submission_id` | 实名提交 ID,幂等唯一 |
| `status` | 当前状态,已有逻辑使用 `pending`、`verified`、`rejected`、`unverified` |
| `name_cipher` | 姓名保护存储值 |
| `id_type` | 证件类型 |
| `id_number_cipher` | 证件号保护存储值 |
| `id_number_hash` | 证件号哈希,用于重复身份检测 |
| `masked_name` | 脱敏姓名 |
| `masked_id_number` | 脱敏证件号 |
| `review_reason` | 审核原因或驳回原因 |
| `verified_at` | 通过时间 |
| `created_at` / `updated_at` | 创建和更新时间 |
租户库已有 `im_realname_appeals`:
| 字段 | 说明 |
| --- | --- |
| `id` | 雪花 ID |
| `appeal_id` | 申诉 ID |
| `user_id` | 用户 ID |
| `reason` | 申诉原因 |
| `attachments_json` | 附件列表 |
| `status` | 申诉状态,默认 `pending` |
| `review_comment` | 审核备注 |
| `created_at` / `updated_at` | 创建和更新时间 |
### 2.4 已记录的增强项
`docs/IM-未实现后续增强项.md` 和 `docs/IM-需求实现情况审核报告.md` 已明确:实名状态查询、提交、申诉已经具备,但缺少后台审核流转、第三方认证结果回写、审核结果通知和完整申诉处理工单。
当前缺口如下:
| 缺口 | 影响 |
| --- | --- |
| 未接第三方实名供应商 | 提交后长期停留 `pending`,无法自动通过或拒绝 |
| 缺少 provider 字段 | 无法区分人工审核、阿里云二要素、阿里云活体等来源 |
| 缺少第三方任务 ID | 无法通过 `CertifyId` 或外部 request id 查询认证结果 |
| 缺少回调/查询状态机 | 无法安全处理异步认证完成、重试和重复通知 |
| 缺少后台审核动作 | 人工通过、拒绝、申诉处理还没有完整闭环 |
| 缺少结果通知 | 实名通过或拒绝后,还没有统一系统通知事件 |
## 3. 阿里云接入总体方案
### 3.0 当前联调结论
2026-09-20 已按 `docs/third/三方服务依赖信息.md` 中的阿里云 AccessKey 做过真实接口联调:
| 账号 | 调用接口 | 结果 | 结论 |
| --- | --- | --- | --- |
| 子账号 `t1@1624085816137202.onaliyun.com` | `Id2MetaVerify` | `403 Forbidden.NoPermission`,缺少 `yundun-cloudauth:Id2MetaVerify` | 子账号需要 RAM 授权 |
| 主账号 AccessKey | `Id2MetaVerify` | 业务码 `410`,提示“未开通服务” | 当前阿里云账号尚未开通身份证二要素/ID_2META 服务 |
因此代码已接入真实阿里云 API,但生产可用还需要:
1. 在阿里云开通实人认证服务中的身份证二要素核验 `ID_2META`。
2. 给运行时使用的 RAM 子账号授权 `yundun-cloudauth:Id2MetaVerify`,或授予对应实人认证服务策略。
3. 在 `hotgo.hg_sys_config` 的 `im_realname` 分组写入 `realname_provider=aliyun_cloudauth`、`aliyun_access_key_id`、`aliyun_access_key_secret`、`aliyun_region_id`、`aliyun_endpoint`。
4. 租户 `hotgo.im_tenant_config.realname_review_policy=1` 后,IM Server 才会自动调用阿里云;服务未开通、权限不足或返回不一致时都会进入 `manual_review`。
### 3.1 配置来源
实名认证配置分两层:
1. **全局供应商配置**:阿里云 AccessKey、Endpoint、回调密钥等服务商级参数放在 HotGo 控制库 `hotgo.hg_sys_config`,使用独立分组 `im_realname`。
2. **租户审核策略**:审核方式放在 HotGo 控制库的 `im_tenant_config`。该策略每个租户单独控制,默认 `0=不审核`。
全局供应商配置:
| group | key | 建议值 | 说明 |
| --- | --- | --- | --- |
| `im_realname` | `realname_provider` | `aliyun_cloudauth` | 当前供应商 |
| `im_realname` | `aliyun_region_id` | `cn-hangzhou` | 阿里云地域 |
| `im_realname` | `aliyun_endpoint` | `cloudauth.aliyuncs.com` | OpenAPI Endpoint,按实际产品确认 |
| `im_realname` | `aliyun_access_key_id` | 空 | RAM AccessKey ID,禁止下发客户端 |
| `im_realname` | `aliyun_access_key_secret` | 空 | RAM AccessKey Secret,后台脱敏展示 |
| `im_realname` | `aliyun_scene_id` | 空 | 实人认证场景 ID,控制台创建 |
| `im_realname` | `aliyun_product_code` | 空 | 如产品要求传 product code,则配置 |
| `im_realname` | `callback_secret` | 空 | IM Server 校验阿里云回调的本地密钥 |
| `im_realname` | `result_query_interval_seconds` | `10` | 异步查询间隔 |
| `im_realname` | `result_query_max_attempts` | `30` | 最大查询次数 |
| `im_realname` | `mock_enabled` | `0` | 本地/测试环境 mock 开关 |
AccessKey 建议使用最小权限 RAM 子账号,只授权实人认证相关 OpenAPI。生产环境如果后续接入 KMS/Secrets Manager,`hg_sys_config` 中可只保存密钥引用。
租户审核策略建议扩展 `hotgo.im_tenant_config`:
| 字段 | 默认值 | 说明 |
| --- | --- | --- |
| `realname_review_policy` | `0` | 审核方式:`0=不审核`,`1=自动审核;自动审核失败/异常/不确定时进入人工审核` |
| `realname_provider_mode` | `id_card_2factor` | 第三方核验模式:`id_card_2factor` 或 `smart_verify` |
| `realname_required` | `0` | 是否强制实名,影响 `bootstrap.security.realname.required` 和受限写操作 |
| `realname_allow_skip` | `1` | 未实名提醒是否允许跳过 |
| `realname_remind_after` | `24h` | 客户端实名提醒间隔 |
执行规则:
- 租户 `realname_review_policy=0` 时,不做第三方审核,提交实名后写入 `pending`,等待后台人工通过或拒绝。
- 租户 `realname_review_policy=1` 时,IM Server 自动调用阿里云;通过则写 `verified`,失败、异常或结果不确定则写 `manual_review`,等待后台人工复核。
- 阿里云全局配置缺失时,即使租户开启自动审核,也不得阻断系统启动;提交时返回稳定错误,或降级写入 `manual_review` 交给人工审核。
### 3.2 推荐状态机
本地实名状态建议统一为:
| 状态 | 含义 | 前端展示 |
| --- | --- | --- |
| `unverified` | 未提交或无有效记录 | 可提交 |
| `pending` | 已创建本地申请,等待第三方或人工结果 | 审核中 |
| `processing` | 第三方认证流程已开始,等待客户端完成或服务端查询 | 认证中 |
| `verified` | 第三方或人工审核通过 | 已实名 |
| `rejected` | 第三方或人工审核拒绝 | 未通过,可重新提交或申诉 |
| `manual_review` | 第三方结果不确定,需要后台人工处理 | 审核中 |
| `expired` | 客户端未在有效期完成认证 | 可重新提交 |
| `failed` | 第三方调用失败或系统异常 | 可重试 |
对外接口可以继续兼容现有 `unverified|pending|verified|rejected`,把 `processing/manual_review` 展示为 `pending`,把 `expired/failed` 视租户策略映射为 `rejected` 或允许重新提交。
## 4. 两种接入流程
### 4.1 二要素核验流程
适合当前页面只提交姓名和身份证号的产品形态。
1. 客户端调用 `POST /v1/im/security/realname/submissions`,提交姓名、证件类型、证件号。
2. IM Server 完成本地格式校验、幂等校验、重复 pending/verified 校验。
3. IM Server 创建本地 `im_realname_verifications`,状态为 `pending`。
4. 如果租户 `realname_review_policy=0`,流程到此结束,等待后台人工审核。
5. 如果租户 `realname_review_policy=1`,IM Server 调用阿里云二要素核验 API。
6. 阿里云返回一致/不一致/无法验证。
7. IM Server 将本地状态更新为:
- 一致:`verified`,写入 `verified_at`。
- 不一致:进入 `manual_review`,由后台人工复核。
- 无法验证或供应商异常:进入 `manual_review`,由后台人工复核。
8. 客户端继续调用 `GET /v1/im/security/realname` 获取最终状态。
9. 如接入系统通知,服务端投递 `security.realname.updated`。
这个模式对前端改动最小,但实名强度只覆盖姓名和证件号一致性。
### 4.2 增强版实人认证流程
适合要求活体、人脸和证件一致性校验的场景。
1. 客户端调用 `POST /v1/im/security/realname/submissions`,提交姓名、证件类型、证件号,并附带阿里云客户端 SDK 要求的 `meta_info`。
2. IM Server 创建本地申请,状态为 `pending`。
3. IM Server 调用阿里云发起认证接口,例如 `InitSmartVerify` 或当前产品对应的初始化接口。
4. 阿里云返回 `CertifyId`、认证 URL、认证 Token 或 SDK 所需参数。
5. IM Server 保存 `provider_task_id=CertifyId`,并把客户端需要的非敏感参数返回给前端。
6. 客户端调起阿里云 SDK、H5 或认证 URL 完成活体核身。
7. IM Server 通过以下任一方式拿最终结果:
- 阿里云回调 IM Server。
- IM Server 定时调用结果查询接口,例如 `DescribeSmartVerify` 或当前产品对应的查询接口。
8. IM Server 根据结果更新本地状态为 `verified`、`rejected`、`manual_review`、`expired` 或 `failed`。
9. 客户端轮询 `GET /v1/im/security/realname` 或接收系统通知刷新页面。
增强版模式下,客户端仍然不需要知道 AccessKey、AccessKeySecret、回调密钥等服务端凭据。客户端只使用后端返回的 `certify_id`、`certify_url`、`token`、`expires_at` 等短期认证参数。
## 5. 前后端接口建议
### 5.1 提交实名信息
保持现有接口不变:
`POST /v1/im/security/realname/submissions`
二要素模式请求:
```json
{
"name": "张三",
"id_type": "cn_id",
"id_number": "110101199001011234"
}
```
增强版模式请求可扩展:
```json
{
"name": "张三",
"id_type": "cn_id",
"id_number": "110101199001011234",
"meta_info": "{\"zimVer\":\"...\"}",
"return_url": "meetingim://realname/callback"
}
```
二要素模式响应:
```json
{
"submission_id": "rn_xxx",
"status": "verified",
"can_submit": false
}
```
增强版模式响应:
```json
{
"submission_id": "rn_xxx",
"status": "processing",
"provider": "aliyun_cloudauth",
"mode": "smart_verify",
"certify_id": "xxx",
"certify_url": "https://...",
"token": "短期认证参数,如产品返回",
"expires_at": "2026-09-20T10:00:00Z"
}
```
### 5.2 查询实名状态
继续使用:
`GET /v1/im/security/realname`
建议扩展字段:
```json
{
"status": "pending",
"display_status": "pending",
"provider": "aliyun_cloudauth",
"mode": "smart_verify",
"submission_id": "rn_xxx",
"masked_name": "张*",
"masked_id_number": "110***********1234",
"can_submit": false,
"review_reason": "",
"verified_at": null,
"next_action": "wait"
}
```
`next_action` 建议值:
| 值 | 含义 |
| --- | --- |
| `submit` | 可提交 |
| `continue_provider_verify` | 需要继续完成阿里云认证 |
| `wait` | 等待结果 |
| `appeal` | 可申诉 |
| `none` | 无需操作 |
### 5.3 阿里云回调接口
建议新增:
`POST /v1/im/security/realname/providers/aliyun/callback`
要求:
- 只接收阿里云回调。
- 校验签名、时间戳、nonce 或本地 `callback_secret`。
- 根据 `CertifyId/provider_task_id` 幂等更新状态。
- 原始 payload 写入事件表,敏感字段脱敏或加密。
这个接口不直接暴露给客户端。
## 6. 数据库改造建议
### 6.1 扩展实名主表
建议在 `im_realname_verifications` 增加第三方状态字段:
```sql
ALTER TABLE im_realname_verifications
ADD COLUMN provider VARCHAR(32) NOT NULL DEFAULT '' AFTER status,
ADD COLUMN provider_mode VARCHAR(32) NOT NULL DEFAULT '' AFTER provider,
ADD COLUMN provider_scene_id VARCHAR(64) NOT NULL DEFAULT '' AFTER provider_mode,
ADD COLUMN provider_task_id VARCHAR(128) NOT NULL DEFAULT '' AFTER provider_scene_id,
ADD COLUMN provider_request_id VARCHAR(128) NOT NULL DEFAULT '' AFTER provider_task_id,
ADD COLUMN provider_status VARCHAR(32) NOT NULL DEFAULT '' AFTER provider_request_id,
ADD COLUMN provider_error_code VARCHAR(64) NOT NULL DEFAULT '' AFTER provider_status,
ADD COLUMN provider_error_message VARCHAR(256) NOT NULL DEFAULT '' AFTER provider_error_code,
ADD COLUMN provider_result_json JSON NULL AFTER provider_error_message,
ADD COLUMN submitted_at DATETIME(3) NULL AFTER provider_result_json,
ADD COLUMN callback_at DATETIME(3) NULL AFTER submitted_at,
ADD COLUMN expires_at DATETIME(3) NULL AFTER callback_at,
ADD COLUMN manual_review_required TINYINT(1) NOT NULL DEFAULT 0 AFTER expires_at,
ADD COLUMN version BIGINT NOT NULL DEFAULT 1 AFTER manual_review_required,
ADD INDEX idx_im_realname_provider_task (provider, provider_task_id),
ADD INDEX idx_im_realname_status_updated (status, updated_at);
```
注意:当前项目已经改为“一个租户一个库”,租户业务表中不再新增 `tenant_id` 字段。
### 6.2 新增第三方事件表
建议新增 `im_realname_provider_events`,用于记录回调、查询结果和异常响应,便于排查和审计:
```sql
CREATE TABLE IF NOT EXISTS im_realname_provider_events (
id BIGINT NOT NULL AUTO_INCREMENT,
submission_id VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
provider VARCHAR(32) NOT NULL,
provider_task_id VARCHAR(128) NOT NULL DEFAULT '',
event_type VARCHAR(32) NOT NULL,
provider_status VARCHAR(32) NOT NULL DEFAULT '',
request_id VARCHAR(128) NOT NULL DEFAULT '',
signature_valid TINYINT(1) NOT NULL DEFAULT 0,
payload_json JSON NULL,
error_code VARCHAR(64) NOT NULL DEFAULT '',
error_message VARCHAR(256) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
INDEX idx_im_realname_events_submission (submission_id, created_at),
INDEX idx_im_realname_events_provider_task (provider, provider_task_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
### 6.3 配置 SQL 建议
HotGo 控制库增加全局供应商配置项:
```sql
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `remark`, `status`, `created_by`, `created_at`, `updated_at`)
SELECT 'im_realname', '实名认证 Provider', 'string', 'realname_provider', 'aliyun_cloudauth', 'aliyun_cloudauth', 11, '当前实名认证供应商', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group`='im_realname' AND `key`='realname_provider');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `remark`, `status`, `created_by`, `created_at`, `updated_at`)
SELECT 'im_realname', '阿里云 RegionId', 'string', 'aliyun_region_id', 'cn-hangzhou', 'cn-hangzhou', 20, '阿里云实人认证地域', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group`='im_realname' AND `key`='aliyun_region_id');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `remark`, `status`, `created_by`, `created_at`, `updated_at`)
SELECT 'im_realname', '阿里云 Endpoint', 'string', 'aliyun_endpoint', 'cloudauth.aliyuncs.com', 'cloudauth.aliyuncs.com', 21, '阿里云实人认证 OpenAPI Endpoint,按开通产品确认', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group`='im_realname' AND `key`='aliyun_endpoint');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `remark`, `status`, `created_by`, `created_at`, `updated_at`)
SELECT 'im_realname', '阿里云 AccessKey ID', 'string', 'aliyun_access_key_id', '', '', 22, '敏感配置,禁止下发客户端', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group`='im_realname' AND `key`='aliyun_access_key_id');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `remark`, `status`, `created_by`, `created_at`, `updated_at`)
SELECT 'im_realname', '阿里云 AccessKey Secret', 'string', 'aliyun_access_key_secret', '', '', 23, '敏感配置,后台脱敏展示,禁止下发客户端', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group`='im_realname' AND `key`='aliyun_access_key_secret');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `remark`, `status`, `created_by`, `created_at`, `updated_at`)
SELECT 'im_realname', '阿里云认证场景 ID', 'string', 'aliyun_scene_id', '', '', 24, '增强版实人认证场景 ID', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group`='im_realname' AND `key`='aliyun_scene_id');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `remark`, `status`, `created_by`, `created_at`, `updated_at`)
SELECT 'im_realname', '实名认证回调密钥', 'string', 'callback_secret', '', '', 30, 'IM Server 校验回调使用,禁止下发客户端', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group`='im_realname' AND `key`='callback_secret');
```
HotGo 控制库增加租户级审核策略。以下写法用于现有库变更,正式发布时可合并到 `im_tenant_config` 建表 SQL:
```sql
ALTER TABLE hotgo.im_tenant_config
ADD COLUMN realname_review_policy TINYINT(1) NOT NULL DEFAULT 0 COMMENT '实名审核策略:0不审核,1自动审核且失败转人工' AFTER max_group_members,
ADD COLUMN realname_provider_mode VARCHAR(32) NOT NULL DEFAULT 'id_card_2factor' COMMENT 'id_card_2factor 或 smart_verify' AFTER realname_review_policy,
ADD COLUMN realname_required TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否强制实名' AFTER realname_provider_mode,
ADD COLUMN realname_allow_skip TINYINT(1) NOT NULL DEFAULT 1 COMMENT '未实名提醒是否允许跳过' AFTER realname_required,
ADD COLUMN realname_remind_after VARCHAR(32) NOT NULL DEFAULT '24h' COMMENT '实名提醒间隔' AFTER realname_allow_skip;
```
默认值要求:
- `realname_review_policy=0`,新租户默认不调用阿里云,也不自动通过;提交后进入 `pending`,等待后台人工审核。
- `realname_review_policy=1`,自动调用阿里云;通过写 `verified`,失败、异常、不确定全部进入 `manual_review`。
- 审核策略只影响当前租户,不影响其他租户。
## 7. 后端开发拆分
### 7.1 第一阶段:配置和状态机
1. 新增 `server/realname` 或 `server/identity` 包,封装实名服务。
2. 从 HotGo `hg_sys_config` 读取 `im_realname` 分组供应商配置,并从 `im_tenant_config` 读取当前租户的审核策略。
3. 扩展 `RealNameVerification` 类型和 MySQL adapter 字段。
4. 新增状态更新方法,例如 `TenantRealNameUpdateProviderResult`。
5. 新增 provider 事件表写入方法。
6. 保持现有接口兼容,未配置第三方时仍可创建 `pending` 申请,不影响系统启动。
### 7.2 第二阶段:阿里云 Client
1. 封装 `AliyunRealNameClient`,支持 mock client 和真实 client。
2. 二要素模式实现:提交姓名、证件号,返回通过/拒绝/人工审核。
3. 增强版模式实现:
- 发起认证:调用 `InitSmartVerify`、`InitFaceVerify` 或开通产品对应的初始化 API。
- 查询结果:调用 `DescribeSmartVerify`、`DescribeFaceVerify` 或开通产品对应的查询 API。
4. 记录阿里云 `RequestId`、`CertifyId`、错误码和错误消息。
### 7.3 第三阶段:回调和异步查询
1. 新增阿里云回调 Handler。
2. 校验回调签名和时间窗口。
3. 以 `provider_task_id` 幂等更新实名状态。
4. 对没有回调或回调失败的任务,后台 worker 定时查询结果。
5. 查询超过最大次数后进入 `manual_review` 或 `failed`。
### 7.4 第四阶段:后台审核和通知
1. 完善后台审核接口:通过、拒绝、申诉处理、审核备注。
2. 实名通过/拒绝后发送系统通知。
3. `GET /v1/im/bootstrap` 和 `GET /v1/im/me` 统一读取真实实名状态。
4. 接入审计日志,记录管理员和第三方状态变更。
## 8. 安全与合规要求
1. 客户端不接触阿里云 AccessKey、AccessKeySecret、回调密钥。
2. IM Server 普通日志不得打印姓名、身份证号、阿里云完整返回 payload。
3. 身份证号只保存哈希、脱敏值和必要保护存储值;如必须保存原文,应使用 KMS 或应用层加密,并设置严格读取权限。
4. `provider_result_json` 和事件表 payload 需要脱敏或加密,禁止存储身份证原文、人脸图片、活体照片。
5. 回调接口必须校验签名、时间戳和幂等,避免伪造通过。
6. AccessKey 使用最小权限 RAM 子账号,建议按环境隔离。
7. 后台查询接口需要按 `security_auditor` 等角色授权,敏感字段默认脱敏。
8. 实名通过后的状态变更需要写审计日志,便于追踪。
## 9. 异常处理策略
| 场景 | 建议处理 |
| --- | --- |
| 阿里云配置缺失 | 接口返回稳定错误 `REALNAME_PROVIDER_NOT_CONFIGURED`,或按配置退回人工 `pending` |
| 阿里云超时 | 本地状态保持 `pending/processing`,后台 worker 重试 |
| 阿里云明确不一致 | 状态改为 `rejected`,返回脱敏驳回原因 |
| 阿里云结果不确定 | 状态改为 `manual_review` |
| 用户重复提交 | 如果已有 `pending/processing/verified`,直接返回最新申请 |
| 用户认证中断 | 到期后改为 `expired`,允许重新提交 |
| 回调重复到达 | 根据 `provider_task_id + provider_status` 幂等忽略 |
## 10. 客户端改动点
1. 继续使用当前实名提交和查询接口。
2. 如果提交响应包含 `certify_url` 或 `token`,客户端调起阿里云 SDK/H5 认证流程。
3. 认证完成后回到 IM 页面,调用 `GET /v1/im/security/realname` 刷新状态。
4. 客户端不缓存原始姓名和证件号,只展示后端返回的脱敏值。
5. 对 `processing/pending/manual_review` 统一展示“审核中/认证中”。
6. 对 `rejected/expired/failed` 根据 `can_submit` 和 `next_action` 决定是否允许重新提交或申诉。
## 11. 建议验收点
1. 未配置阿里云时,IM Server 能正常启动,现有实名提交仍可落 `pending`。
2. 配置 mock provider 后,提交实名可以覆盖通过、拒绝、异常和超时场景。
3. 二要素模式下,提交后能自动更新为 `verified/rejected`。
4. 增强版模式下,提交后返回 `certify_id/certify_url`,完成认证后状态能回写。
5. 重复 `Idempotency-Key` 不产生重复申请。
6. 阿里云回调重复投递不重复更新、不重复通知。
7. 日志、系统通知、Push、搜索索引中均不出现身份证原文。
8. 后台能按状态查看实名申请,并能人工处理 `manual_review` 和申诉。
## 12. 参考文档
- 阿里云 OpenAPI 开发者门户:`https://api.aliyun.com/`
- 阿里云实人认证服务 API:`https://api.aliyun.com/product/Cloudauth`
- `InitSmartVerify`:发起增强版认证请求,返回认证请求 ID。
- `DescribeSmartVerify`:查询增强版认证结果。
- `InitFaceVerify` / `DescribeFaceVerify`:按开通产品和接入形态选择的实人认证初始化与结果查询接口。
最终使用哪个 API 名称和参数,以实际开通的阿里云产品、地域、控制台场景配置和 API 门户为准。
@@ -0,0 +1,13 @@
-- IM 阿里云实名认证:租户级审核策略字段
-- 执行库:hotgo
-- 说明:
-- 1. realname_review_policy=0:不自动审核,实名提交进入 pending,等待后台人工审核。
-- 2. realname_review_policy=1:自动调用阿里云审核,通过写 verified;失败、异常、不确定写 manual_review,等待人工复核。
-- 3. realname_provider_mode 只表示阿里云核验模式,不承担审核开关含义。
ALTER TABLE hotgo.im_tenant_config
ADD COLUMN realname_review_policy TINYINT(1) NOT NULL DEFAULT 0 COMMENT '实名审核策略:0不审核,1自动审核且失败转人工' AFTER max_group_members,
ADD COLUMN realname_provider_mode VARCHAR(32) NOT NULL DEFAULT 'id_card_2factor' COMMENT 'id_card_2factor 或 smart_verify' AFTER realname_review_policy,
ADD COLUMN realname_required TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否强制实名' AFTER realname_provider_mode,
ADD COLUMN realname_allow_skip TINYINT(1) NOT NULL DEFAULT 1 COMMENT '未实名提醒是否允许跳过' AFTER realname_required,
ADD COLUMN realname_remind_after VARCHAR(32) NOT NULL DEFAULT '24h' COMMENT '实名提醒间隔' AFTER realname_allow_skip;