# 壹证通 UCenter · API 开发文档（v1.0）

> 统一用户管理中心：平台接入一次，用户一号通行。在线版：https://uc.3707.wang/docs
> 基础地址（下称 `BASE`）：`https://uc.3707.wang`

---

## 1. 概述

- 平台不自建用户体系；用户在任意平台注册一次即进入中心，所有平台一号通行。
- 密码只存中心（加密存储），平台通过 API 校验或 SSO 确认身份。
- 用户首次经某平台注册/登录/SSO 即自动**绑定**该平台；绑定关系决定该平台可查询的用户范围。
- 平台凭 `app_id` + `app_key` 调用接口；密钥仅存平台**服务端**。

## 2. 快速开始（5 步）

1. 管理员在后台「应用管理」创建应用 → 获得 `app_id` / `app_key`。
2. 平台「登录」按钮指向 `BASE/sso.php?app_id=xxx&back=urlencode(回跳地址)`，回跳页核验票据建立本地会话（最小接入即完成）。
3. （可选）平台自带注册/登录表单的，改调 `user.register` / `user.login`。
4. （推荐）密码登录成功后输出 `uc_synlogin_script($uid)` 广播，其它平台免密通行。
5. 后台「API 调试台」联调自测后上线。

## 3. 签名算法

每个请求携带 `action`、`app_id`、`ts`（10 位秒级时间戳）、`sign`：

```
data = 请求字段中除 action / ts / sign 外的全部字段，按 key 升序排序
sign = sha256( app_id + "|" + app_key + "|" + ts + "|" + md5(json_encode(data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)) )
```

- ts 与服务器时间差 ≤ 300 秒（请对时 NTP）。
- JSON 序列化必须不转义中文与斜杠；无业务参数时 data 为 `{}`。

### Python

```python
import json, hashlib, time

def uc_sign(app_id, app_key, data):
    ts = str(int(time.time()))
    payload = json.dumps(data, ensure_ascii=False, separators=(',', ':'), sort_keys=True)
    sign = hashlib.sha256(f"{app_id}|{app_key}|{ts}|".encode()
                          + hashlib.md5(payload.encode()).hexdigest().encode()).hexdigest()
    return {'app_id': app_id, 'ts': ts, 'sign': sign, **data}

# r = requests.post(BASE + '/api.php', json={'action': 'app.ping', **uc_sign(..., {})})
```

### Node.js

```js
const crypto = require('crypto');
function ucSign(appId, appKey, data) {
  const ts = String(Math.floor(Date.now() / 1000));
  const payload = JSON.stringify(data, Object.keys(data).sort());
  const sign = crypto.createHash('sha256')
    .update(`${appId}|${appKey}|${ts}|${crypto.createHash('md5').update(payload).digest('hex')}`)
    .digest('hex');
  return { app_id: appId, ts, sign, ...data };
}
```

## 4. SSO 一键登录

```
平台 ──重定向──▶ BASE/sso.php?app_id=xxx&back=urlencode(https://平台/sso.php)
                                  │
                 中心有登录态？──是──▶ 签发一次性票据 302 回跳
                        │否
                        ▼
                 中心登录/注册页 ──完成后──▶ 回跳
回跳：back?ticket=...&uid=...&ts=...&sign=sha256(app_key|ticket|uid|ts)
```

- 平台确认身份二选一：
  1. **本地验签**：校验回跳 `sign`（ts 差 ≤300 秒），不联网；
  2. **票据核销**：调 `ticket.verify`（一次性，同时返回 7 天 token）。
- 回跳地址必须 HTTPS 且在后台配置的「回跳白名单前缀」内。
- 票据 120 秒有效、仅可用一次。

## 5. 同步登录广播（跨平台免密）

平台**账号密码登录成功**后输出（SSO 流程无需此步）：

```html
<script src="BASE/synlogin.php?app_id=xxx&uid=123&ts=1710000000&sign=sha256(app_key|123|1710000000)"></script>
```

中心校验签名后写入免登 Cookie（默认 30 天）→ 用户访问任何接入平台，SSO 自动免密。

## 6. 开放接口

统一入口 `POST BASE/api.php`（JSON 或表单编码）。响应统一：

```
成功：{"ok": true, ...业务字段}
失败：{"ok": false, "error": "原因", "code": "错误码"}
```

### 6.1 app.ping
无业务参数。响应 `app_name`、`server_time`。首次联调用它验证签名实现。

### 6.2 user.register — 注册并绑定
| 参数 | 必填 | 说明 |
|---|---|---|
| username | 是 | 2-32 位中文/字母/数字/_/-，全局唯一 |
| password | 是 | 6-64 位 |
| email / phone / nickname | 否 | 选填 |

响应：`uid`、`username`、`token`、`expires_in`。用户名已存在 → `user_exists`。

### 6.3 user.login — 登录校验
参数：`username`、`password`。响应：用户公开信息（uid/username/nickname/email/phone/avatar/created_at）+ `token`/`expires_in`。通过即自动绑定本平台。

### 6.4 user.bind — 绑定存量用户（迁移）
参数：`username`、`password`。密码校验通过后把已有中心账号绑定到本平台，响应同 login。

### 6.5 user.exists — 用户名查询
参数：`username`。响应：`exists`(bool)、`uid`。

### 6.6 ticket.verify — 票据核销（一次性）
参数：`ticket`。响应：用户公开信息 + `token`/`expires_in`。

### 6.7 token.verify — 令牌校验
参数：`token`（默认 7 天有效，跨应用隔离）。响应：用户公开信息。

### 6.8 user.info — 用户资料
参数：`uid` / `username` / `token` 三选一。响应：`user` 对象。仅限已绑定本平台的用户。

### 6.9 user.changepwd — 修改密码
参数：`username`、`old_password`、`new_password`。全平台同步生效。

### 6.10 syn.logout — 同步登出
参数：`token`。销毁本平台令牌。

### 6.11 app.stats — 应用自查统计
无业务参数。响应：`binds`、`tokens_active`、`registers_7d`、`logins_7d`、`sso_7d`、`server_time`。

## 7. 错误码

| code | 含义 | 处理建议 |
|---|---|---|
| no_app / app_not_found | app_id 缺失或不存在 | 检查配置 |
| app_disabled | 应用被停用 | 联系管理员 |
| no_action / unknown_action | action 缺失/未知 | 检查接口名 |
| bad_ts / ts_expired | 时间戳缺失/过期 | 对时 NTP |
| sign_error | 签名不匹配 | 核对 app_key 与签名算法 |
| bad_username / bad_password | 格式不合规 | 前端校验 |
| user_exists | 用户名已占用 | 引导登录 |
| user_not_found | 用户不存在 | 提示注册 |
| not_bound | 未绑定本平台 | 从本平台登录一次自动绑定 |
| bad_credentials | 账号或密码错误 | 统一提示 |
| user_disabled | 账号停用 | 联系管理员 |
| locked | 失败过多（10 分钟≥10 次/IP） | 稍后再试 |
| bad_ticket | 票据无效/已用/过期 | 重新走 SSO |
| bad_token | 令牌无效/过期 | 重新登录 |
| register_closed | 中心关闭自助注册 | 联系管理员 |
| network / bad_response | SDK 侧网络异常 | 重试告警 |

## 8. PHP SDK

下载 `https://uc.3707.wang/sdk/uc_client.php.txt` 另存为 `uc_client.php`。

```php
require 'uc_client.php';
uc_init('https://uc.3707.wang', 'app_id', 'app_key');

uc_register($u, $p);            // 注册（自动绑定）
uc_login($u, $p);               // 登录校验
uc_bind($u, $p);                // 绑定存量用户
uc_exists($u);                  // 用户名是否占用
uc_sso_url($back);              // SSO 跳转地址
uc_handle_sso();                // 回跳页票据核验 → 用户数组
uc_callback_verify();           // 回跳本地验签（不联网）
uc_check_token($token);         // 令牌校验
uc_user_info($uid);             // 用户资料
uc_change_password($u,$o,$n);   // 改密
uc_logout($token);              // 同步登出
uc_synlogin_script($uid);       // 同步登录广播脚本
uc_app_stats();                 // 接入统计
uc_ping();                      // 连通自检
uc_api($action, $data);         // 通用调用
```

## 9. 安全规范与联调清单

**安全**
- `app_key` 只存服务端；全链路 HTTPS；回跳白名单必配。
- 票据一次性短期；本地会话定期用 `token.verify` 复核。
- 不区分「账号不存在/密码错误」；密钥泄露立即后台「重置密钥」。

**联调清单**
- ☐ app.ping 返回 ok
- ☐ SSO 全链通（跳转→回跳→建会话）
- ☐ 票据重放被拒
- ☐ 错误密码返回 bad_credentials
- ☐ 密码登录后嵌入 synlogin 广播
- ☐ 退出调用 syn.logout
- ☐ 服务器时间 NTP 对时

---
*文档版本 v1.0 · 在线版与调试台：https://uc.3707.wang/docs · 后台：https://uc.3707.wang/admin*
