登录、授权与会话
星线 App 使用星线 Account 完成身份验证,再由聊天服务签发独立的聊天会话。原生客户端通过系统浏览器参与授权,不把 Account 密码交给聊天服务;当前原生协议同时保护 Account 授权码交换和 App 聊天票据交换。
地址与身份边界
| 项目 | 地址或规则 |
|---|---|
| 聊天 API | https://chat.thestarline.cn |
| Account issuer | https://account.thestarline.cn/oidc |
| 正式 HTTPS 授权回调 | https://chat.thestarline.cn/auth/callback |
| 星线 App 返回入口 | starlineworkbench://chat-auth |
| 星企返回入口 | xingqiworkbench://auth,仅用于独立星企产品 |
| 授权范围 | openid email profile phone |
聊天服务通过 issuer 的 /.well-known/openid-configuration 获取授权、令牌和用户信息端点,检查返回 issuer 完全一致,且这三个端点位于配置 issuer 的同源范围。生产启动还要求正式 Account issuer、HTTPS 公网 URL 和服务端机密 OIDC 客户端配置。具体客户端 ID、密钥和注册记录不属于公开示例。
原生登录的两层证明
- App 使用系统安全随机源生成 32 字节随机值,编码为不带填充的 base64url verifier,并计算
BASE64URL(SHA256(verifier))。 - App 调用
POST /auth/native/start,发送{app:"starline",codeChallenge,screen:"login"}。注册入口可把screen设为register。 - 聊天服务验证挑战是规范的 43 字符 base64url,创建有效期 10 分钟的登录 flow。响应包含
authorizationURL、state、expiresAt。客户端须保存本次 verifier、state 和到期时间,再打开返回 URL;iOS 还保存 API 基地址。 - 聊天服务另行生成自己的 verifier,与 S256 挑战发送给 Account。原生入口设置
prompt=login;注册入口额外设置screen_hint=signup。这层 verifier 留在聊天服务端。 - Account 将授权码和 state 回调到正式 HTTPS 地址。聊天服务一次性消费 flow,使用
client_secret_basic、授权码和服务端 verifier 请求令牌,随后请求用户信息。 - 聊天服务要求
sub非空,并要求邮箱已验证或手机号已验证。已验证手机号会规范化;显示名按 name、邮箱前缀和默认名称取值,最多保存 80 个 UTF-16 代码单元。 - 服务端创建有效期 60 秒的一次性 ticket,将 ticket 与原 state 放入 App 返回 URL。App 对返回入口和参数做校验后,调用
POST /auth/exchange,提交{ticket,state,codeVerifier}。 - 服务端核对 state 哈希与 App verifier 的 S256 值,消费 ticket,签发聊天会话;响应为
{accessToken,expiresAt,user,eventCursor}。
两层 verifier 是不同随机值:服务端的一层保护 Account 授权码,App 的一层让仅截获自定义 scheme ticket 的程序无法单独换取聊天会话。客户端不能直接拿 Account access token 调用聊天业务接口。
到期、消费与失败语义
| 对象 | 默认有效期 | 重放处理 |
|---|---|---|
| 原生登录 flow | 10 分钟 | HTTPS 回调读取时删除;过期或错误回调返回 login_failed |
兼容 /auth/start flow |
40 分钟 | 仅为兼容浏览器入口,不能替代原生证明流程 |
| 聊天 ticket | 60 秒 | 成功换取后删除;错误原生 proof 不签发会话 |
| 聊天 device session | 24 小时 | 每次 Bearer 请求检查哈希与到期时间 |
expiresAt 是 Unix 毫秒时间戳。不要把 Account OIDC token 的期限当成聊天会话期限;当前 Account 源码的访问令牌和 ID token TTL 为 900 秒,授权码为 60 秒。它们用于身份协议,不是 App 业务会话的刷新机制。聊天服务当前没有公开 refresh-token 路由。
原生开始接口可能返回 INVALID_APP、INVALID_CHALLENGE、INVALID_SCREEN 或 LOGIN_UNAVAILABLE。票据缺失、过期、已消费、state 不匹配或 verifier 不匹配会导致 INVALID_TICKET。客户端应重新开始授权;不能删除 proof 参数去尝试兼容 ticket 模式。错误响应形状见API 参考。
客户端回调和持久化
iOS 只接受预期 scheme 与 host,拒绝用户名、密码、端口、fragment、异常 path、重复 state、重复 ticket/error,以及 ticket 与 error 同时出现。回调 state 必须等于当前未到期 flow,且必须属于同一 API 基地址。iOS 在交换前保存 ticket,支持进程中断后恢复;成功后先持久化 session,再清理 pending 状态,避免清理失败把已成功登录改成失败。
Android 的 pending 仅保存 verifier、state、expiresAt,没有 iOS 的 API 基地址和 ticket 字段;回调通过校验后会先清除 pending,再发起换票。它不能据此获得与 iOS 相同的“换票中进程中断后持久恢复”能力,失败时需重新开始授权。两端的实现与调试方法分别见iOS和Android。登录 token 不应写入普通 URL、截图、日志、分析事件或静态网站。iOS 使用 Keychain,Android 使用 Keystore 支持的本地加密存储。
使用与退出
除健康检查和授权入口外,聊天 API 使用 Authorization: Bearer <chat_access_token>。服务端接受规定格式的 Bearer token,并将其 SHA-256 摘要与未过期 session 记录匹配。GET /me 返回当前聊天用户与该用户最新 eventCursor,可用于恢复登录后的状态同步。
POST /auth/logout 成功时删除当前聊天会话;它不等于 Account 全站退出或撤销其他设备。两端都会清除本机状态,并忽略远端退出请求失败;Android 先清本机再尝试服务端退出,iOS 尝试释放通话、注销推送和服务端退出后清本机。所以“已退出界面”不能证明服务端撤销成功:网络失败时,旧 session 可能持续到 24 小时到期。Account 修改密码或撤销 Account 会话也没有自动联动撤销聊天 session 的实现。
VoIP 设备与具体 session 摘要关联,服务端注销或到期后不会继续被查询为该会话的有效推送目标。本机退出还会停止轮询、释放媒体连接并清理当前账号界面状态;这些本机动作与远端 token 撤销应分别验证。
记忆转出许可的独立边界
聊天服务还有 /memory-export/start 与 /memory-export/exchange,用于当前登录用户的额外转出验证,不能用于普通登录。开始流程有效期 5 分钟;服务端向 Account 的 redeem 接口验证 ticket、state、verifier,并检查返回用户与当前 Account subject 一致。
当前作为权威依据的 Account 主源码未包含对应 redeem 路由;App 工作树随附的历史 Account 副本虽然有实现,也不能替代当前主源码或证明其已部署。因此只能确认聊天侧桥接契约,不能认定该跨服务流程当前可用。客户端本地导出和 Account 验证是否完整闭环,应按照测试取得实际结果后再更新状态。