Files
im_server/docs/third/IM-阿里云实名认证接入方案.md
T

537 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 门户为准。