feat: validate personal and enterprise auth tenants
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
# IM 个人/企业注册登录前后端对接说明
|
||||
|
||||
## 1. 目标
|
||||
|
||||
当前客户端没有独立的企业码进入页面,登录页和注册页由用户选择“个人用户”或“企业用户”。前端提交登录和注册时必须显式带类型,后端不能再通过企业码是否为空来推断个人/企业。
|
||||
|
||||
核心规则:
|
||||
|
||||
1. 前端新增字段 `registration_type`,类型为 int,取值只能是 `0` 或 `1`。
|
||||
2. `registration_type=1` 时,后端按个人注册处理,内部租户使用 `default`,数据写入 `default` 租户业务库。
|
||||
3. `registration_type=0` 时,前端必须提交企业码,后端必须校验企业存在、启用且允许注册。
|
||||
4. 登录新增字段 `login_type`,类型为 int,取值同样是 `0` 企业、`1` 个人。
|
||||
5. 企业登录或企业注册都不允许使用 `default`。
|
||||
6. `default` 是系统保留的个人租户码,不允许作为企业码输入。
|
||||
|
||||
一句话规则:**注册类型由 `registration_type` 决定,登录类型由 `login_type` 决定;个人落 `default`,企业必须校验企业码。**
|
||||
|
||||
## 2. 前端注册字段
|
||||
|
||||
注册页提交发码和最终注册时都带同一组注册上下文字段:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `registration_type` | int | 是 | `0` 表示企业注册,`1` 表示个人注册 |
|
||||
| `tenant_code` | string | 企业注册必填 | 企业码;个人注册不填或传空字符串 |
|
||||
|
||||
前端规则:
|
||||
|
||||
| `registration_type` | `tenant_code` | 后端处理 |
|
||||
| --- | --- | --- |
|
||||
| `1` | 空 | 允许提交,后端内部使用 `default` |
|
||||
| `1` | 非空 | 后端拒绝,避免误入库 |
|
||||
| `0` | 空 | 后端拒绝,提示请输入企业码 |
|
||||
| `0` | `default`、`Default`、`DEFAULT` | 后端拒绝 |
|
||||
| `0` | 其他值 | 后端在注册链路中校验 |
|
||||
|
||||
用户在企业注册模式输入 `default` 时,建议提示:
|
||||
|
||||
```text
|
||||
default 是系统保留企业码。个人注册请选择个人用户,企业注册请输入企业提供的企业码。
|
||||
```
|
||||
|
||||
## 3. 企业码校验
|
||||
|
||||
不新增单独的企业码校验接口,也不要求前端在注册前调用 `/v1/im/tenants/resolve`。
|
||||
|
||||
后端在注册发码和最终注册链路中校验:
|
||||
|
||||
1. `registration_type=1`
|
||||
- 不要求企业码。
|
||||
- 即使请求 Header 或内部路由使用 `default`,也属于服务端内部租户,不是用户输入企业码。
|
||||
- 创建用户时写入 `default` 租户业务库。
|
||||
|
||||
2. `registration_type=0`
|
||||
- `tenant_code` 必填。
|
||||
- `tenant_code` 大小写不敏感等于 `default` 时拒绝。
|
||||
- 查询企业是否存在、启用、允许注册。
|
||||
- 通过后在对应企业租户业务库创建用户。
|
||||
|
||||
企业不存在、停用或不允许注册时,客户端统一提示:
|
||||
|
||||
```text
|
||||
企业不存在或不可用
|
||||
```
|
||||
|
||||
客户端不要区分不存在、停用、禁用注册等内部原因。
|
||||
|
||||
`/v1/im/tenants/resolve` 只保留给服务发现、品牌配置、历史企业入口等场景使用;当前注册页不依赖它完成主流程。
|
||||
|
||||
## 4. 前端登录字段
|
||||
|
||||
登录页提交 WebSocket `{login}` 时带登录上下文字段:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `login_type` | int | basic 登录必填 | `0` 表示企业登录,`1` 表示个人登录 |
|
||||
| `tenant_code` | string | 企业登录必填 | 企业码;个人登录不填、传空字符串或内部传 `default` |
|
||||
|
||||
登录规则:
|
||||
|
||||
| `login_type` | `tenant_code` / `hi.tenant` | 后端处理 |
|
||||
| --- | --- | --- |
|
||||
| `1` | `default` | 个人登录,允许 |
|
||||
| `1` | 非 `default` | 拒绝,避免个人登录误进企业租户 |
|
||||
| `0` | 空 | 拒绝,提示请输入企业码 |
|
||||
| `0` | `default`、`Default`、`DEFAULT` | 拒绝,统一提示“企业不存在或不可用” |
|
||||
| `0` | 其他值 | 企业登录,后端按该租户认证 |
|
||||
|
||||
企业用户如果在企业入口输入 `default`,后端返回策略错误,错误参数为 `what=tenant_code`、`reason=TENANT_CODE_FORBIDDEN`;客户端统一展示:
|
||||
|
||||
```text
|
||||
企业不存在或不可用
|
||||
```
|
||||
|
||||
个人登录 `{login}` 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"login": {
|
||||
"id": "login-1",
|
||||
"scheme": "basic",
|
||||
"login_type": 1,
|
||||
"tenant_code": "",
|
||||
"secret": "base64(account:password)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
企业登录 `{login}` 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"login": {
|
||||
"id": "login-1",
|
||||
"scheme": "basic",
|
||||
"login_type": 0,
|
||||
"tenant_code": "acme",
|
||||
"secret": "base64(account:password)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 个人注册流程
|
||||
|
||||
个人注册必须显式提交 `registration_type=1`。
|
||||
|
||||
1. 用户选择个人注册。
|
||||
2. 前端提交 `registration_type=1`,`tenant_code` 为空。
|
||||
3. 注册发码、验证码校验、WebSocket `hi`、`acc` 注册链路使用服务端内部租户 `default`。
|
||||
4. 服务端将用户创建到 `default` 租户对应的业务库。
|
||||
|
||||
发码示例:
|
||||
|
||||
```http
|
||||
POST /v1/im/auth/verification-codes
|
||||
X-MeetingIM-APIKey: <api_key>
|
||||
X-IM-Schema: 1
|
||||
X-Tenant-Code: default
|
||||
X-Request-ID: <uuid>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"scene": "register",
|
||||
"registration_type": 1,
|
||||
"tenant_code": "",
|
||||
"phone": "19122421333"
|
||||
}
|
||||
```
|
||||
|
||||
WebSocket 握手示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"hi": {
|
||||
"id": "hi-1",
|
||||
"ver": "0.25",
|
||||
"ua": "ios/1.0.0",
|
||||
"platf": "ios",
|
||||
"lang": "zh-CN",
|
||||
"tenant": "default"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最终注册 `{acc}` 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"acc": {
|
||||
"id": "acc-1",
|
||||
"user": "new",
|
||||
"scheme": "basic",
|
||||
"registration_type": 1,
|
||||
"tenant_code": "",
|
||||
"username": "henry",
|
||||
"phone": "19122421333",
|
||||
"secret": "base64(henry:password)",
|
||||
"login": true,
|
||||
"cred": [
|
||||
{
|
||||
"meth": "tel",
|
||||
"val": "19122421333",
|
||||
"resp": "verification_token",
|
||||
"params": {"challenge_id": "challenge-id"}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 企业注册流程
|
||||
|
||||
企业注册必须显式提交 `registration_type=0`。
|
||||
|
||||
1. 用户选择企业注册。
|
||||
2. 用户填写企业码,例如 `acme`。
|
||||
3. 前端直接提交用户输入的企业码,不做企业码存在性或保留字拦截。
|
||||
4. 获取验证码、提交注册时都带 `registration_type=0` 和 `tenant_code=acme`。
|
||||
5. 后端在注册链路中校验企业是否存在且可用。
|
||||
6. 校验通过后,服务端在该企业租户业务库中创建用户;校验不通过时返回“企业不存在或不可用”。
|
||||
|
||||
注册发码请求:
|
||||
|
||||
```http
|
||||
POST /v1/im/auth/verification-codes
|
||||
X-MeetingIM-APIKey: <api_key>
|
||||
X-IM-Schema: 1
|
||||
X-Tenant-Code: acme
|
||||
X-Request-ID: <uuid>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"scene": "register",
|
||||
"registration_type": 0,
|
||||
"tenant_code": "acme",
|
||||
"phone": "19122421333"
|
||||
}
|
||||
```
|
||||
|
||||
WebSocket `hi`:
|
||||
|
||||
```json
|
||||
{
|
||||
"hi": {
|
||||
"id": "hi-1",
|
||||
"ver": "0.25",
|
||||
"ua": "android/1.0.0",
|
||||
"platf": "android",
|
||||
"lang": "zh-CN",
|
||||
"tenant": "acme"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最终注册 `{acc}` 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"acc": {
|
||||
"id": "acc-1",
|
||||
"user": "new",
|
||||
"scheme": "basic",
|
||||
"registration_type": 0,
|
||||
"tenant_code": "acme",
|
||||
"username": "henry",
|
||||
"phone": "19122421333",
|
||||
"secret": "base64(henry:password)",
|
||||
"login": true,
|
||||
"cred": [
|
||||
{
|
||||
"meth": "tel",
|
||||
"val": "19122421333",
|
||||
"resp": "verification_token",
|
||||
"params": {"challenge_id": "challenge-id"}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 后端校验规则
|
||||
|
||||
后端负责注册链路的最终校验,前端不做企业码拦截。
|
||||
|
||||
1. `registration_type` 缺失或不是 `0|1`
|
||||
- 拒绝注册。
|
||||
- 返回 `MALFORMED_REQUEST`,提示客户端升级或检查参数。
|
||||
|
||||
2. `registration_type=1`
|
||||
- 不从空企业码推断,而是由该字段明确进入个人注册。
|
||||
- 内部租户码设置为 `default`。
|
||||
- 拒绝用户提交的非空 `tenant_code`,避免误入库。
|
||||
- 注册时写入 `default` 租户业务库。
|
||||
|
||||
3. `registration_type=0`
|
||||
- `tenant_code` 必填。
|
||||
- 先 trim 首尾空格。
|
||||
- 如果大小写不敏感等于 `default`,拒绝注册。
|
||||
- 否则查询租户是否存在、是否启用、是否允许注册。
|
||||
- 校验通过后绑定对应租户。
|
||||
|
||||
建议错误:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 30000,
|
||||
"reason": "MALFORMED_REQUEST",
|
||||
"message": "invalid registration_type or tenant_code"
|
||||
}
|
||||
```
|
||||
|
||||
企业不可用时,客户端展示:
|
||||
|
||||
```text
|
||||
企业不存在或不可用
|
||||
```
|
||||
|
||||
## 8. 缓存和本地数据隔离
|
||||
|
||||
客户端本地缓存、Token、会话、草稿、附件缓存必须继续按 `tenant_code + user_id` 隔离。
|
||||
|
||||
个人注册用户登录后,缓存键中的 `tenant_code` 使用 `default`。企业用户登录后,使用对应企业码。切换个人/企业身份时必须清理当前连接并重新 `hi`,不能在同一个 WebSocket 中切换租户。
|
||||
|
||||
## 9. 验收用例
|
||||
|
||||
| 场景 | 预期 |
|
||||
| --- | --- |
|
||||
| `login_type=1`,`hi.tenant=default` | 个人登录成功进入 `default` 租户认证 |
|
||||
| `login_type=1`,`hi.tenant=acme` | 拒绝登录 |
|
||||
| `login_type=0`,企业码空 | 拒绝登录,提示请输入企业码 |
|
||||
| `login_type=0`,企业码为 `default` | 拒绝登录,提示企业不存在或不可用 |
|
||||
| `login_type=0`,企业码有效 | 在该企业租户认证 |
|
||||
| `registration_type=1`,企业码空,手机号注册 | 成功进入 `default` 租户注册链路 |
|
||||
| `registration_type` 缺失 | 拒绝注册,返回参数错误 |
|
||||
| `registration_type=1`,企业码非空 | 拒绝或忽略,建议拒绝,避免误入库 |
|
||||
| `registration_type=0`,企业码空 | 拒绝注册,提示请输入企业码 |
|
||||
| `registration_type=0`,企业码为 `default` | 拒绝注册 |
|
||||
| `registration_type=0`,企业码不存在 | 发码或注册失败,提示企业不存在或不可用 |
|
||||
| `registration_type=0`,企业码已停用 | 统一提示企业不存在或不可用 |
|
||||
| `registration_type=0`,企业码有效 | 注册写入该企业租户库 |
|
||||
| 个人用户注册成功后登录 | `tenant_code=default`,可正常登录、收发消息 |
|
||||
|
||||
## 10. 对客户端的明确要求
|
||||
|
||||
1. 登录页和注册页都必须明确区分个人和企业。
|
||||
2. basic 登录必须提交 `login_type`。
|
||||
3. 发码和最终注册都必须提交 `registration_type`。
|
||||
4. 个人登录/注册提交类型 `1`,不要让用户输入或看到 `default`。
|
||||
5. 企业登录/注册提交类型 `0` 和用户输入的企业码。
|
||||
6. 企业用户手动输入 `default` 也直接提交,由后端返回错误,客户端按后端错误展示提示。
|
||||
7. 不需要为了注册主流程调用 `/v1/im/tenants/resolve`。
|
||||
|
||||
## 11. 对后端的明确要求
|
||||
|
||||
1. basic 登录链路必须以 `login_type` 作为个人/企业登录的判定来源。
|
||||
2. 注册链路必须以 `registration_type` 作为个人/企业注册的判定来源。
|
||||
3. 不允许仅根据企业码是否为空推断个人注册或个人登录。
|
||||
4. 登录、注册、发码、验证码校验、WebSocket `hi/login/acc` 链路保持同一租户上下文。
|
||||
5. 显式企业码 `default` 必须拒绝,防止用户绕过企业码校验。
|
||||
6. 企业登录/注册的企业码必须校验存在、启用、允许登录/注册。
|
||||
7. 日志中要能看出来源是 `personal_default` 还是 `enterprise_code`,方便排查误入库问题。
|
||||
Reference in New Issue
Block a user