登录与登录设备
用户在 App 中登录 IM 后才能收发消息。每次登录都会为当前设备创建一个会话(即一台“登录设备”),并返回 User Token 和 refresh token:User Token 是客户端调用客户端接口、建立长连接的凭据,过期前客户端用 refresh token 续期。
客户端有两种登录方式:
- 登录凭证登录(推荐):用户登录你的业务系统后,业务服务端调用签发登录凭证,把凭证返回给 App,App 再用凭证登录 IM。
- 密码登录:App 直接提交用户名和密码。用户需要先设置密码(创建用户时设置,或调用设置密码)。
推荐使用登录凭证
已有账号体系的 App 建议使用登录凭证:IM 的登录跟随业务系统的登录,用户不需要第二套密码,业务服务端也不必保存 IM 密码。签发时还可以让不存在的用户自动创建,省去单独建号的步骤。
使用登录凭证的流程:
- 用户登录你的 App 和业务系统;
- 业务服务端调用签发登录凭证,把得到的
ticket返回给 App; - App 在 5 分钟内调用客户端登录,以
grant_type为ticket登录,得到 User Token 和 refresh token。
本页还介绍多端登录的规则,以及业务服务端如何查看用户的登录设备、把设备踢下线。
签发登录凭证
为用户签发一个一次性的登录凭证,App 用它登录 IM。
- 凭证 5 分钟内有效,只能使用一次:提交登录后即作废,即使登录失败(如设备数已满)也要重新签发;
- 每次调用都签发一个新凭证,之前签发的在 5 分钟内仍然有效;请求超时后重试,只把最后拿到的凭证交给 App 即可;
- 签发后用户被设置密码、封禁、踢掉全部设备或删除,或者在控制台要求应用内全部用户重新登录的,凭证作废;
- 指定
auto_create时,用户不存在就创建一个没有密码的用户,created_via为openapi。
凭证相当于用户的临时密码,请只通过 HTTPS 返回给该用户的 App,不要写入日志。
/{org_name}/{app_name}/users/{username}/login-tickets路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
username | String | 用户名 |
请求体
请求体可以省略。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
auto_create | Boolean | 否 | 用户不存在时是否自动创建,默认 false |
请求示例
curl -X POST "$IM_API/$ORG/$APP/users/zhangsan/login-tickets" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"auto_create": true
}'响应
成功返回 201 Created:
| 字段 | 类型 | 说明 |
|---|---|---|
ticket | String | 登录凭证,以 ult_ 开头 |
expires_in | Number | 有效期,单位为秒,固定为 300 |
user_created | Boolean | 本次是否自动创建了用户 |
{
"ticket": "ult_RjcQrK8ff-Y0lfQ_7DqodLbw4WJZQNgdwIgOfsaW3gI",
"expires_in": 300,
"user_created": true
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 403 | user_disabled | 用户已被封禁 |
| 403 | app_unavailable | 需要自动创建用户,但应用处于只读状态。为已存在的用户签发不受影响 |
| 404 | not_found | 用户不存在或已删除,且没有指定 auto_create |
| 409 | limit_exceeded | 需要自动创建用户,但应用的注册用户数已达上限 |
客户端登录
App 用登录凭证或密码登录 IM。这是客户端接口,由 App 直接调用:
- 路径以
/client/v1开头,不含org_name和app_name,用请求体中的app_key指明应用; - 不需要
Authorization请求头,不要在 App 中使用 App Token; - 允许浏览器跨域调用,网页可以直接调用。
AppKey 是应用的公开标识,格式为 {org_name}#{app_name},如 1575529652#demo,可以在控制台的应用详情中查看。它不是密钥,可以写在 App 中,凭它只能登录和注册,没有任何管理权限。
/client/v1/sessions请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
app_key | String | 是 | 应用的 AppKey |
grant_type | String | 是 | 登录方式:ticket 凭证登录,password 密码登录 |
ticket | String | 凭证登录时必填 | 业务服务端签发的登录凭证 |
username | String | 密码登录时必填 | 用户名,不区分大小写。凭证登录时可选,给出时必须是凭证所属的用户,否则按凭证无效处理 |
password | String | 密码登录时必填 | 密码 |
device_id | String | 是 | 设备标识,1 到 64 个可见 ASCII 字符。由 App 生成并在设备上持久保存,同一台设备每次登录使用相同的值 |
device_name | String | 否 | 设备名称,如 iPhone 15,最长 64 个字符,超长截断。其他设备被这台设备挤下线时会看到这个名称 |
platform | String | 否 | 平台:ios、android、harmony、windows、macos、linux、web、mini_program,其他值和省略时记为 other。用于按平台限制设备数,见多端登录 |
sdk_version | String | 否 | 客户端版本,最长 32 个字符,超长截断 |
同一个 device_id 再次登录时,这台设备上原来的会话被替换下线。网页端请让同一浏览器的所有标签页共用一个 device_id 和一次登录,各标签页分别登录会互相替换。
请求示例
凭证登录:
curl -X POST "$IM_API/client/v1/sessions" \
-H "Content-Type: application/json" \
-d '{
"app_key": "1575529652#demo",
"grant_type": "ticket",
"ticket": "ult_RjcQrK8ff-Y0lfQ_7DqodLbw4WJZQNgdwIgOfsaW3gI",
"device_id": "5f0c2a9e-7d1b-4c7e-9a51-2f6d0f3b8c11",
"device_name": "iPhone 15",
"platform": "ios",
"sdk_version": "1.0.0"
}'密码登录:
curl -X POST "$IM_API/client/v1/sessions" \
-H "Content-Type: application/json" \
-d '{
"app_key": "1575529652#demo",
"grant_type": "password",
"username": "zhangsan",
"password": "Passw0rd!",
"device_id": "5f0c2a9e-7d1b-4c7e-9a51-2f6d0f3b8c11",
"device_name": "iPhone 15",
"platform": "ios",
"sdk_version": "1.0.0"
}'响应
成功返回 201 Created:
| 字段 | 类型 | 说明 |
|---|---|---|
access_token | String | User Token。客户端调用客户端接口和建立长连接时放在请求头 Authorization: Bearer <User Token> 中 |
access_token_expires_in | Number | User Token 的有效期,单位为秒,默认 86400(1 天)。可为 null,表示不过期 |
refresh_token | String | 用于续期的 refresh token,以 urt_ 开头 |
refresh_token_expires_in | Number | 会话的有效期,单位为秒,默认 604800(7 天);在此之前续期会顺延。可为 null,表示不过期 |
session_id | String | 本次登录的会话 ID,与登录设备对象的 session_id 相同 |
user | Object | 本人资料:username、nickname、avatar_url、attributes、has_password、version,含义同用户对象 |
config | Object | 应用的运行配置,如设备数上限、消息大小上限、撤回时限、是否开启已读回执等,客户端据此决定界面的显示。各项的值由控制台的运行策略决定 |
两个有效期都由应用的运行策略决定。示例中缩短了令牌,config 只列出了部分字段:
{
"access_token": "eyJhbGciOiJFZERTQSIsImtpZCI6ImsxIiwidHlwIjoiSldUIn0.eyJpc3Mi...",
"access_token_expires_in": 86400,
"refresh_token": "urt_99582942128373760_2AhAQZTPK86ahHMlW1waLQP2vOw0wJcGlFTuZPqGSEs",
"refresh_token_expires_in": 604800,
"session_id": "99582942128373760",
"user": {
"username": "zhangsan",
"nickname": "张三丰",
"avatar_url": "",
"attributes": { "gender": "1", "level": "vip" },
"has_password": true,
"version": 3
},
"config": {
"version": "1.1.0",
"max_online_devices_per_user": 4,
"device_limits_by_platform": null,
"single_read_ack_enabled": true,
"max_message_body_bytes": 5120,
"recall_window_seconds": 120,
"message_retention_days": 90
}
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 400 | invalid_argument | grant_type 不是 password 或 ticket;device_id 缺失或格式不对;密码登录时用户名格式不对或密码为空 |
| 401 | invalid_credentials | 用户名或密码错误、用户不存在或没有设置密码;凭证无效、已使用、已过期或已作废。为防止探测用户名,这几种情况不作区分。凭证登录失败时,App 可以向业务系统重新获取一次凭证后重试 |
| 403 | user_disabled | 用户已被封禁。只在密码正确或凭证有效时返回,可以据此提示“账号已被封禁” |
| 403 | tenant_unavailable | 租户已暂停服务 |
| 403 | app_unavailable | 应用已停用或被暂停服务 |
| 404 | not_found | app_key 对应的应用不存在 |
| 409 | device_limit_exceeded | 登录设备数已达上限,且应用设置为拒绝新设备登录,见多端登录 |
| 429 | too_many_attempts | 密码错误次数过多,暂时不能用密码登录,按 Retry-After 响应头等待后再试,不要自动重试。凭证登录不受影响 |
| 429 | rate_limited | 登录请求过于频繁,按 Retry-After 等待后重试。每个客户端 IP 每分钟最多 120 次,另计入应用的客户端请求额度 |
密码登录失败的次数有以下限制,防止猜测密码:同一用户在同一 IP 失败 10 次,该 IP 在 15 分钟内不能再用密码登录这个用户;同一 IP 在 1 小时内对同一应用失败 100 次,该 IP 在这个应用的密码登录全部暂停;同一用户在 1 小时内(不分 IP)失败 100 次,该用户的密码登录暂停。暂停期间已登录的设备和凭证登录都不受影响。
续期与退出
- 续期:User Token 过期前,App 调用
POST /client/v1/sessions/refresh,请求体为{"refresh_token": "..."},返回新的access_token、refresh_token和它们的有效期。每次续期都会换发新的 refresh token,请保存新的值;会话有效期随之顺延,只要用户在有效期内使用过 App,就一直保持登录。 - 退出:用户退出登录时,App 调用
POST /client/v1/sessions/logout,请求体为{"refresh_token": "..."},成功返回204。只在本地清除令牌会让服务端以为这台设备仍在登录,继续占用设备数,也会继续收到这个用户的离线推送。 - 被下线:设备被踢、被挤下线或用户被封禁后,客户端接口返回
401 session_revoked,details.reason说明原因,见下线原因。这时不要续期,清除本地令牌后回到登录页。 - 推送随登录解绑:App 登记的推送令牌属于这次登录。退出登录或被下线后,这台设备的推送登记随之自动解绑,App 不需要另外注销推送;重新登录后再登记。
客户端自注册
用户在 App 中自行注册,不经过业务服务端。只有应用的注册模式(registration_mode)为 open 时允许,可以在控制台的应用管理中修改;新建应用默认为 authorized,只能由服务端创建用户,这时调用返回 403 permission_denied。
自注册任何人都能调用,仅建议在测试应用中开启。正式应用请由业务服务端创建用户,或在签发登录凭证时自动创建。
自注册的用户必须设置密码,created_via 为 client。注册成功后再调用客户端登录。每个客户端 IP 每分钟最多注册 10 次。昵称按客户端提交的内容经过内容安全检查,命中替换规则的保存替换后的昵称。
/client/v1/users请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
app_key | String | 是 | 应用的 AppKey |
username | String | 是 | 用户名,规则见用户名与资料 |
password | String | 是 | 密码,1 到 64 个字符 |
nickname | String | 否 | 昵称,最长 64 个字符 |
请求示例
curl -X POST "$IM_API/client/v1/users" \
-H "Content-Type: application/json" \
-d '{
"app_key": "1575529652#demo",
"username": "xiaoming",
"password": "secret123",
"nickname": "小明"
}'响应
成功返回 201 Created,响应体为本人资料:
{
"username": "xiaoming",
"nickname": "小明",
"avatar_url": "",
"attributes": {},
"has_password": true,
"version": 1
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 400 | invalid_argument | 没有设置密码,或参数不符合用户名与资料中的规则 |
| 403 | permission_denied | 应用的注册模式不是 open,不允许客户端注册 |
| 403 | tenant_unavailable / app_unavailable | 租户或应用暂停服务,或应用处于只读状态 |
| 403 | content_rejected | 昵称未通过内容安全检查,details.field 为 nickname |
| 404 | not_found | app_key 对应的应用不存在 |
| 409 | already_exists | 用户名已被占用 |
| 409 | limit_exceeded | 应用的注册用户数已达上限 |
| 429 | rate_limited | 注册过于频繁,按 Retry-After 等待后重试 |
多端登录
一个用户可以同时在多台设备上登录,每台设备一个会话。设备数有两级上限,由应用的运行策略决定。其中 device_limits_by_platform 目前不能在控制台中修改,需要按平台限制时请联系我们:
| 策略 | 说明 | 默认值 |
|---|---|---|
max_online_devices_per_user | 每个用户同时登录的设备总数,1 到 20 | 4 |
device_limits_by_platform | 同一平台分组内的设备数,每组 1 到 20,用于实现“手机、电脑各一台”这类同平台互踢。分组为 mobile(ios、android、harmony)、desktop(windows、macos、linux)、web、mini_program、other | 不按平台限制 |
device_overflow_policy | 超出上限时的处理:kick_oldest 挤掉最早登录的设备,reject_new 拒绝新设备登录 | kick_oldest |
登录新设备时先检查新设备所在平台分组的上限,再检查总数上限:
kick_oldest:超出分组上限时挤掉该分组内最早登录的设备,超出总数上限时挤掉所有设备中最早登录的设备。被挤掉的设备收到原因device_limit,以及新设备的名称和平台,可以提示“你的账号已在 iPhone 15 上登录”。例如设为{"mobile": 1, "desktop": 1}时,新手机登录会挤掉旧手机,电脑不受影响;reject_new:本次登录返回409 device_limit_exceeded。
设备数按登录会话计算,而不是按是否在线:设备断网期间仍占用名额,退出登录、被踢下线或会话过期后才释放。使用 reject_new 时,如果用户的旧设备丢失,可以调用踢掉单台设备释放名额。调低上限不会踢掉已登录的设备,之后有新设备登录时才按新的上限处理。
查询登录设备
查询用户当前登录的设备,只列出有效的会话(没有被踢下线、退出或过期),按最近活跃时间倒序排列。设备数受上限约束,结果不分页。
/{org_name}/{app_name}/users/{username}/sessions路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
username | String | 用户名 |
请求示例
curl "$IM_API/$ORG/$APP/users/zhangsan/sessions" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 200 OK。items 为登录设备,每项为登录设备对象;没有登录的设备时为空数组。
{
"items": [
{
"session_id": "99583185116987392",
"device_id": "mac-1a2b3c",
"device_name": "MacBook Pro",
"platform": "macos",
"sdk_version": "1.0.0",
"login_method": "password",
"ip": "203.0.113.8",
"created_at": "2026-10-02T19:08:01.501Z",
"last_active_at": "2026-10-02T19:08:01.501Z",
"expires_at": "2026-10-09T19:08:01.501Z"
},
{
"session_id": "99582942128373760",
"device_id": "5f0c2a9e-7d1b-4c7e-9a51-2f6d0f3b8c11",
"device_name": "iPhone 15",
"platform": "ios",
"sdk_version": "1.0.0",
"login_method": "ticket",
"ip": "198.51.100.23",
"created_at": "2026-10-02T19:07:03.568Z",
"last_active_at": "2026-10-02T19:07:53.750Z",
"expires_at": "2026-10-09T19:07:53.750Z"
}
]
}错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 404 | not_found | 用户不存在或已删除 |
踢掉全部设备
让用户的所有设备下线:在线的设备会被断开连接并收到原因 kicked,此前签发、尚未使用的登录凭证作废。各设备登记的推送令牌随之解绑,不再收到这个用户的推送。用户没有登录的设备时同样返回成功。
踢下线不会阻止用户重新登录。需要禁止登录的,请封禁用户。每次调用都会让当时登录的全部设备下线,请求超时后重试,也会让两次调用之间新登录的设备下线。
/{org_name}/{app_name}/users/{username}/sessions路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
username | String | 用户名 |
请求示例
curl -X DELETE "$IM_API/$ORG/$APP/users/zhangsan/sessions" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 204 No Content,没有响应体。
错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 404 | not_found | 用户不存在或已删除 |
踢掉单台设备
让用户的一台设备下线,在线时会被断开连接并收到原因 kicked。这台设备登记的推送令牌随之解绑,不再向它推送,见推送设备与设置。这台设备之后仍可以重新登录。
设备已经下线(被踢、退出或被挤掉)时同样返回成功,可以放心重试。
/{org_name}/{app_name}/users/{username}/sessions/{session_id}路径参数
org_name、app_name 见接入概述。
| 参数 | 类型 | 说明 |
|---|---|---|
username | String | 用户名 |
session_id | String | 会话 ID,取自查询登录设备的 session_id |
请求示例
curl -X DELETE "$IM_API/$ORG/$APP/users/zhangsan/sessions/99583185116987392" \
-H "Authorization: Bearer $APP_TOKEN"响应
成功返回 204 No Content,没有响应体。
错误
| HTTP 状态码 | code | 说明 |
|---|---|---|
| 404 | not_found | 用户不存在或已删除;会话不存在或不属于这个用户 |
下线原因
设备下线后,在线的设备会被断开连接,离线的设备在下一次请求时收到 401 session_revoked,details.reason 说明原因:
reason | 原因 |
|---|---|
replaced | 同一台设备(相同的 device_id)重新登录 |
device_limit | 登录设备数超出上限,被新设备挤掉 |
kicked | 被业务服务端、控制台踢下线,或被用户本人在其他设备上踢下线 |
password_changed | 密码被修改或重置 |
user_disabled | 用户被封禁 |
user_deleted | 用户被删除 |
relogin_required | 在控制台要求应用内全部用户重新登录 |
token_reuse | 已经换下的 refresh token 被再次使用,可能已经泄露 |
expired | 会话过期(超过有效期没有续期) |
原因为 replaced 或 device_limit 时,details 另有 device_name 和 platform,为新登录的设备:
{
"error": {
"code": "session_revoked",
"message": "登录设备数超过上限,你的账号已在其他设备登录",
"details": { "reason": "device_limit", "device_name": "Chrome", "platform": "web" },
"request_id": "LE3YXRXUWZWAUNQY7YODQNV45U"
}
}数据结构
登录设备对象
| 字段 | 类型 | 说明 |
|---|---|---|
session_id | String | 会话 ID |
device_id | String | 设备标识,登录时由客户端提交 |
device_name | String | 设备名称,没有时为空字符串 |
platform | String | 平台:ios、android、harmony、windows、macos、linux、web、mini_program 或 other |
sdk_version | String | 客户端版本,没有时为空字符串 |
login_method | String | 登录方式:ticket 凭证登录,password 密码登录 |
ip | String | 登录时的客户端 IP |
created_at | String | 登录时间 |
last_active_at | String | 最近一次登录或续期的时间 |
expires_at | String | 会话的过期时间,按应用当前的运行策略计算,在此之前续期会顺延。可为 null,表示不过期 |
{
"session_id": "99582942128373760",
"device_id": "5f0c2a9e-7d1b-4c7e-9a51-2f6d0f3b8c11",
"device_name": "iPhone 15",
"platform": "ios",
"sdk_version": "1.0.0",
"login_method": "ticket",
"ip": "198.51.100.23",
"created_at": "2026-10-02T19:07:03.568Z",
"last_active_at": "2026-10-02T19:07:53.750Z",
"expires_at": "2026-10-09T19:07:53.750Z"
}